diff --git a/CHANGELOG.md b/CHANGELOG.md
index a32dbe01..3ba36c13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [3.10.0] - 2026-07-19
+
+### Added
+- **Format Token Modifiers**: Introduced new capabilities for chained formatting modifiers.
+- **Custom Format Tokens**: Completed the Custom Format Tokens implementation, allowing developers to build custom zero-overhead logic evaluators (like native Intl bridges).
+
+### Changed
+- **Documentation Architecture**: Architectural deep-dives (Localized Parsing, Slick Mutations, Custom Tokens) have been extracted from the Cookbook into specialized Core Concepts guides (e.g. `tempo.parse.md`, `tempo.mutate.md`, `tempo.format.md`) to provide a punchier onboarding experience.
+
## [3.9.0] - 2026-07-14
### Added
diff --git a/package.json b/package.json
index 2d91086e..955787b2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tempo-monorepo",
- "version": "3.9.3",
+ "version": "3.10.0",
"private": true,
"engines": {
"node": ">=20.0.0"
@@ -22,6 +22,7 @@
"clean": "node node_modules/typescript-7/bin/tsc -b --clean",
"version:bump": "node bin/version-bump.mjs",
"version:sync": "node bin/version-sync.mjs",
+ "catalog:sync": "node packages/plugins/.bin/catalog-sync.mjs",
"repl": "npm run repl --workspace=@magmacomputing/tempo",
"repl:plugins": "tsx --import ./packages/plugins/.bin/temporal-polyfill.mts ./packages/plugins/.bin/repl.mts",
"repl:dist": "npm run repl:dist --workspace=@magmacomputing/tempo",
diff --git a/packages/library/package.json b/packages/library/package.json
index 7a441e35..5cd0e264 100644
--- a/packages/library/package.json
+++ b/packages/library/package.json
@@ -1,6 +1,6 @@
{
"name": "@magmacomputing/library",
- "version": "3.9.3",
+ "version": "3.10.0",
"description": "Shared utility library for Tempo",
"author": "Magma Computing Solutions",
"license": "MIT",
diff --git a/packages/plugins/.bin/README.md b/packages/plugins/.bin/README.md
index cf6ac9df..cd815ea0 100644
--- a/packages/plugins/.bin/README.md
+++ b/packages/plugins/.bin/README.md
@@ -5,6 +5,7 @@ This directory (`packages/plugins/bin/`) contains internal support scripts and u
It includes:
- **REPL Environment (`repl.mts`)**: Scripts to initialize an interactive Node.js REPL session with Tempo and Temporal pre-loaded, making it easy to experiment with plugins from the CLI.
- **Polyfill Setup (`temporal-polyfill.mts`)**: Initialization scripts to ensure the `@js-temporal/polyfill` is correctly loaded into the global scope during testing or REPL sessions, allowing plugins to work with native `Temporal` APIs before they are officially adopted by all runtimes.
+- **Catalog Synchronization (`catalog-sync.mjs`)**: A developer utility that scans all local and external plugin `package.json` files and extracts their metadata into a centralized `catalog.json` file. Run via `npm run catalog:sync`.
- **TypeScript Configuration (`tsconfig.json`)**: Specific compiler options for running these support scripts directly via tools like `tsx`.
These files are meant for local monorepo development and testing purposes only. They are not published or distributed with any NPM packages.
diff --git a/packages/plugins/.bin/catalog-sync.mjs b/packages/plugins/.bin/catalog-sync.mjs
new file mode 100644
index 00000000..1d624c70
--- /dev/null
+++ b/packages/plugins/.bin/catalog-sync.mjs
@@ -0,0 +1,81 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const catalogPath = path.resolve(__dirname, '../.setup/catalog.json');
+const pluginsDir = path.resolve(__dirname, '../../plugins');
+const nodeModulesDir = path.resolve(__dirname, '../../../node_modules/@magmacomputing');
+
+let catalog = [];
+if (fs.existsSync(catalogPath)) {
+ try {
+ catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
+ } catch (e) {
+ console.error('Failed to parse catalog.json, starting fresh.', e);
+ }
+}
+
+const catalogMap = new Map();
+catalog.forEach(p => catalogMap.set(p.id, p));
+
+function processPlugin(pluginDir, isExternal) {
+ const pkgPath = path.join(pluginDir, 'package.json');
+ if (!fs.existsSync(pkgPath)) return;
+
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
+
+ // Extract id from directory name, replacing leading dots for VitePress safety
+ let id = path.basename(pluginDir).replace(/^\./, '_');
+ if (isExternal) {
+ id = id.replace('tempo-plugin-', '');
+ }
+
+ // Use the human-readable displayName if we have one, otherwise create a titleized version of the ID
+ const humanName = id.charAt(0).toUpperCase() + id.slice(1) + ' Plugin';
+
+ const entry = catalogMap.get(id) || { id };
+
+ // Update fields from package.json
+ entry.name = entry.name || humanName; // allow manual override of human name
+ entry.description = pkg.description || entry.description || '';
+ entry.packageName = pkg.name;
+ entry.plan = pkg.tempo?.plan || entry.plan || 'community';
+ entry.status = entry.status || 'active';
+ // Note: we do NOT store version here, as build-catalog.mjs injects it dynamically!
+
+ catalogMap.set(id, entry);
+ console.log(`Synced plugin metadata for: ${id} (${isExternal ? 'external' : 'local'})`);
+}
+
+// 1. Process Local Plugins
+if (fs.existsSync(pluginsDir)) {
+ const plugins = fs.readdirSync(pluginsDir);
+ for (const plugin of plugins) {
+ // Skip hidden files/dirs like .setup or .bin unless we want to document them
+ // But we know .setup isn't a plugin, so let's skip it if it doesn't have a package.json
+ const fullPath = path.join(pluginsDir, plugin);
+ if (fs.statSync(fullPath).isDirectory()) {
+ processPlugin(fullPath, false);
+ }
+ }
+}
+
+// 2. Process Node Modules Plugins
+if (fs.existsSync(nodeModulesDir)) {
+ const modules = fs.readdirSync(nodeModulesDir);
+ for (const mod of modules) {
+ if (mod.startsWith('tempo-plugin-')) {
+ const fullPath = path.join(nodeModulesDir, mod);
+ if (fs.statSync(fullPath).isDirectory()) {
+ processPlugin(fullPath, true);
+ }
+ }
+ }
+}
+
+// Write back to catalog.json
+fs.writeFileSync(catalogPath, JSON.stringify(Array.from(catalogMap.values()), null, 2) + '\n');
+console.log(`\nSuccessfully updated ${catalogPath}`);
diff --git a/packages/plugins/.setup/catalog.json b/packages/plugins/.setup/catalog.json
new file mode 100644
index 00000000..3b4938f7
--- /dev/null
+++ b/packages/plugins/.setup/catalog.json
@@ -0,0 +1,50 @@
+[
+ {
+ "id": "astro",
+ "name": "Astro Plugin",
+ "description": "Tempo plugin that calculates precise astronomical seasons (solstices & equinoxes) using the Jean Meeus algorithm — hemisphere-aware, sub-minute accuracy",
+ "packageName": "@magmacomputing/tempo-plugin-astro",
+ "plan": "community",
+ "status": "active"
+ },
+ {
+ "id": "batch",
+ "name": "Batch Plugin",
+ "description": "Tempo community plugin bringing C-level parallelization to massive date arrays via SharedArrayBuffer and Worker Threads.",
+ "packageName": "@magmacomputing/tempo-plugin-batch",
+ "plan": "community",
+ "status": "active"
+ },
+ {
+ "id": "finance",
+ "name": "Finance Plugin",
+ "description": "Tempo Community Plugin: Finance namespace and fiscal year utilities",
+ "packageName": "@magmacomputing/tempo-plugin-finance",
+ "plan": "community",
+ "status": "active"
+ },
+ {
+ "id": "snap",
+ "name": "Snap Plugin",
+ "description": "Snap time to blocks",
+ "packageName": "@magmacomputing/tempo-plugin-snap",
+ "plan": "community",
+ "status": "active"
+ },
+ {
+ "id": "sync",
+ "name": "Sync Plugin",
+ "description": "Tempo community plugin providing lock-free, highly precise cross-thread synchronization via SharedArrayBuffer and Atomics.",
+ "packageName": "@magmacomputing/tempo-plugin-sync",
+ "plan": "community",
+ "status": "active"
+ },
+ {
+ "id": "ticker",
+ "name": "Ticker Plugin",
+ "description": "Tempo plugin that provides a high-performance continuous execution loop (Ticker) based on temporal mathematics.",
+ "packageName": "@magmacomputing/tempo-plugin-ticker",
+ "plan": "pro",
+ "status": "active"
+ }
+]
diff --git a/packages/plugins/.setup/community-plugin-template.md b/packages/plugins/.setup/community-plugin-template.md
index 88659ffb..863e26fe 100644
--- a/packages/plugins/.setup/community-plugin-template.md
+++ b/packages/plugins/.setup/community-plugin-template.md
@@ -8,6 +8,33 @@ Ensure the plugin's `package.json` contains the correct community configuration:
- **Version**: Set to a fresh semantic version (e.g., `"1.0.0"` for the first release).
- **License**: Must strictly be `"MIT"`.
+- **Type**: Set `"type": "module"`.
+- **Files**: Include the published files array:
+ ```json
+ "files": [
+ "dist",
+ "src",
+ "README.md",
+ "CHANGELOG.md",
+ "LICENSE"
+ ]
+ ```
+- **PublishConfig**: Configure public npm publishing:
+ ```json
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org/",
+ "access": "public"
+ }
+ ```
+- **Exports**: Define exports with types and import entrypoints:
+ ```json
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ }
+ ```
- **Scripts**:
- Ensure `"build": "tsup && tsc"` and `"postbuild": "rm -rf dist/src"` are present.
- Include the prepublish safeguard: `"prepublishOnly": "if [ $(git rev-parse --abbrev-ref HEAD) != main ]; then echo 'ERROR: Must be on main branch to publish.'; exit 1; fi && npm run build"`.
diff --git a/packages/plugins/finance/README.md b/packages/plugins/finance/README.md
index 5a236ed0..e084c42f 100644
--- a/packages/plugins/finance/README.md
+++ b/packages/plugins/finance/README.md
@@ -18,10 +18,10 @@ npm install @magmacomputing/tempo-plugin-finance
```typescript
import { Tempo } from '@magmacomputing/tempo';
-import { FinancePlugin } from '@magmacomputing/tempo-plugin-finance';
+import { FinanceNamespace } from '@magmacomputing/tempo-plugin-finance';
// Register the namespace
-Tempo.extend(FinancePlugin);
+Tempo.extend(FinanceNamespace);
const t = new Tempo('2024-07-01');
diff --git a/packages/plugins/finance/doc/index.md b/packages/plugins/finance/doc/index.md
index 067a7f14..c1c7f285 100644
--- a/packages/plugins/finance/doc/index.md
+++ b/packages/plugins/finance/doc/index.md
@@ -16,10 +16,10 @@ npm install @magmacomputing/tempo-plugin-finance
```typescript
import { Tempo } from '@magmacomputing/tempo';
-import { FinancePlugin } from '@magmacomputing/tempo-plugin-finance';
+import { FinanceNamespace } from '@magmacomputing/tempo-plugin-finance';
// Register the namespace
-Tempo.extend(FinancePlugin);
+Tempo.extend(FinanceNamespace);
const t = new Tempo('2024-07-01');
diff --git a/packages/plugins/finance/src/index.ts b/packages/plugins/finance/src/index.ts
index 59906f5d..93da7411 100644
--- a/packages/plugins/finance/src/index.ts
+++ b/packages/plugins/finance/src/index.ts
@@ -15,7 +15,7 @@ export const isFiscalYearStart = (tempo: Tempo) => () => tempo.mm === 1 && tempo
// Wrap the functions in a Namespace Plugin so they can be injected directly
// onto the Tempo instance (e.g., `t.finance.taxYear`) for a fluent experience.
// -----------------------------------------------------------------------------
-export const FinancePlugin: TempoPlugin = defineNamespace({
+export const FinanceNamespace: TempoPlugin = defineNamespace({
name: 'finance',
resolvers: {
fiscalQuarter,
diff --git a/packages/plugins/finance/test/finance.test.ts b/packages/plugins/finance/test/finance.test.ts
index 7a4686db..0a2f51af 100644
--- a/packages/plugins/finance/test/finance.test.ts
+++ b/packages/plugins/finance/test/finance.test.ts
@@ -1,9 +1,9 @@
import { Tempo } from '@magmacomputing/tempo';
-import { FinancePlugin } from '../src/index.js';
+import { FinanceNamespace } from '../src/index.js';
describe('Finance Namespace', () => {
it('should lazy load the finance properties', () => {
- Tempo.extend(FinancePlugin);
+ Tempo.extend(FinanceNamespace);
const t1 = new Tempo('2024-02-15');
expect(t1.finance.fiscalQuarter).toBe(1);
diff --git a/packages/plugins/snap/README.md b/packages/plugins/snap/README.md
index f21653a8..52d6d0aa 100644
--- a/packages/plugins/snap/README.md
+++ b/packages/plugins/snap/README.md
@@ -6,7 +6,7 @@
These plugins are free, open-source extensions that do not require a license token.
{{ plugin.description }}
npm install {{ plugin.packageName }}
-
+ {{ plugin.description }}
npm install {{ plugin.packageName }}
-
+