diff --git a/agent-context/skills/metaobjects-codegen/references/python.md b/agent-context/skills/metaobjects-codegen/references/python.md new file mode 100644 index 000000000..406277c0c --- /dev/null +++ b/agent-context/skills/metaobjects-codegen/references/python.md @@ -0,0 +1,76 @@ +# Python codegen specifics + +The Python port targets FastAPI consumers. Codegen runs through the **`metaobjects` +console-script** (`pip install metaobjects`). As on every port, schema migrations +are **Node-`meta`-owned** (ADR-0015): `meta migrate` / `meta verify --db` run +through the Node `meta` tool — the Python CLI has **no `migrate` subcommand** and +`metaobjects verify --db` is rejected. Everything below is `metaobjects`. + +## Install + +```bash +pip install metaobjects # provides the `metaobjects` console-script +# consumer runtime deps (you provide these — codegen does not pin them): +pip install "pydantic>=2" fastapi +``` + +## Run + +```bash +metaobjects gen ./metadata --out ./generated [--package ] +metaobjects gen ./metadata --out ./generated --generators entity,routes,filter-allowlist +metaobjects verify ./metadata --codegen # codegen-drift gate (regenerate into a + # temp dir + diff vs the committed --out tree) +``` + +`metaobjects verify` defaults to `--codegen` (the codegen-output drift gate; it shares +the exact `gen` code path so the two can't diverge). `--templates` is the prompt/template +drift gate (see the prompts reference). Schema migration + live-DB drift are **not** +`metaobjects` — they run through the Node `meta` tool (see the migration reference). + +## Generators + +Wire generators by their stable name (`--generators `), or run the default set. +Output lands under `--out` (with the `@generated` guard header). Metadata is the same +canonical JSON every port reads (fused-key form, `source.rdb` + `@table`, `@column` for +a renamed physical column). + +| Stable name | Output | +|---|---| +| `entity` | one **Pydantic model** per `object.entity` / projection (the `entity-model` generator): typed fields from the metadata, nullability from `@required`, `@maxLength`/validators, enum fields → a Python `Enum`. This is the typed data model. | +| `routes` | a **FastAPI `APIRouter`** per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). The router declares a repository **`Protocol`** you implement and inject. | +| `filter-allowlist` | per-entity filter allowlist (FR-009 — the server-side field+operator allowlist the routes validate against). | +| `payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper` | the `template.output` prompt-pillar artifacts — see the **prompts** reference. | +| `template` | the generic Mustache `template` primitive. | + +## No ORM — you own persistence (unlike the C# port) + +Python codegen emits the **Pydantic models + the FastAPI routers**, but **no ORM / +persistence layer and no runnable server**. Two things you hand-write: + +1. **The repository** — each generated router depends on a repository `Protocol`; + implement it against your datastore (SQLAlchemy / asyncpg) and inject it. +2. **The app entrypoint** — there is no generated `main.py`. Create one and mount the + routers: + ```python + from fastapi import FastAPI + from generated.author_router import router as author_router + app = FastAPI() + app.include_router(author_router) + ``` + +## Known gaps (current — may require a hand-edit) + +- **Single-field, `int` PKs only.** The generated router/repository assume a single + `int` primary key (`id: int`). Non-`int` single-field PKs and composite PKs need a + hand-edit until specified. +- **DTO = `dict[str, Any]`.** Request bodies for `POST`/`PATCH`/`PUT` are typed + `dto: dict[str, Any]` and responses return `Any`; the repository `Protocol` uses + `Any` for the row type. The typed Pydantic model from the `entity` generator exists — + you can tighten the router signatures to it by hand. + +## Re-scaffold this context + +`metaobjects agent-docs --server python [--out ]` (re)scaffolds the slim always-on +Markdown + these `metaobjects-*` skills into the project — the Python tool bundles the +agent-context tree, so a Python consumer needs no Node `meta`. diff --git a/agent-context/skills/metaobjects-codegen/references/typescript.md b/agent-context/skills/metaobjects-codegen/references/typescript.md index e7d4e0751..c26003554 100644 --- a/agent-context/skills/metaobjects-codegen/references/typescript.md +++ b/agent-context/skills/metaobjects-codegen/references/typescript.md @@ -129,6 +129,10 @@ Generated files carry an `@generated by @metaobjectsdev/codegen-ts` header; the runner overwrites those and refuses to touch files without it. Hand-customizations that metadata can't express live in sibling `.extra.ts` files. +**Output format:** `meta gen` (and the CLI generally) is TTY-aware — human-readable +text on a terminal, TOON on a pipe or agent. Override with `--format toon|json|text`. +TOON is the structured default for agents; `--format json` is also available. + ## Multiple output targets A `targets: { web: { outDir }, api: { outDir } }` registry plus a per-generator diff --git a/agent-context/skills/metaobjects-verify/references/migration.md b/agent-context/skills/metaobjects-verify/references/migration.md index 0671e6498..b85221f34 100644 --- a/agent-context/skills/metaobjects-verify/references/migration.md +++ b/agent-context/skills/metaobjects-verify/references/migration.md @@ -21,8 +21,45 @@ npm install --save-dev @metaobjectsdev/cli @metaobjectsdev/migrate-ts You point the tool at the **same database your server connects to** — its connection is independent of your runtime tier. +## Output format + +`meta migrate` (and the CLI generally) is TTY-aware: when stdout is a terminal it +emits human-readable text; when piped to an agent or CI system it defaults to TOON +(a compact, unambiguous machine-readable format). Override with `--format`: + +```bash +meta migrate ... --format toon # TOON (machine-readable, the pipe/agent default) +meta migrate ... --format json # JSON +meta migrate ... --format text # human-readable text (the TTY default) +``` + +Structured errors and next-step hints are also emitted on stdout (not stderr) in the +active format, so callers can parse them without scraping stderr. + ## The workflow +### Fresh database: baseline first + +The default `meta migrate` path is **offline** — it diffs metadata against a +committed schema snapshot rather than the live DB. On a fresh database there is no +snapshot yet; run the `baseline` step once before the first migration generate: + +```bash +meta migrate baseline --dialect sqlite # seed snapshot from metadata (no DB needed) +meta migrate baseline --dialect postgres # same for Postgres +meta migrate baseline --from-db --db postgresql://... --dialect postgres + # alternative: seed from live DB (for existing schemas) +``` + +`baseline` writes a reference snapshot to `.metaobjects/migrations/` and exits +without emitting any SQL. After this, `meta migrate --dialect --slug ` +operates offline against that snapshot. + +If you run `meta migrate` before baselining, the CLI surfaces a structured +next-step hint pointing to the exact `baseline` command. + +### Generating a migration + 1. **Generate a migration** by diffing metadata vs the prior state (the live DB or a committed snapshot). The engine emits paired `up.sql` + `down.sql`: diff --git a/bun.lock b/bun.lock index 4a8a61724..3e01abe13 100644 --- a/bun.lock +++ b/bun.lock @@ -144,6 +144,7 @@ "meta": "./dist/bin/meta.js", }, "dependencies": { + "@libsql/kysely-libsql": "^0.4.0", "@metaobjectsdev/codegen-ts": "workspace:*", "@metaobjectsdev/codegen-ts-react": "workspace:*", "@metaobjectsdev/codegen-ts-tanstack": "workspace:*", @@ -152,11 +153,11 @@ "@metaobjectsdev/render": "workspace:*", "@metaobjectsdev/runtime-ts": "workspace:*", "@metaobjectsdev/sdk": "workspace:*", + "@toon-format/toon": "^2.3.0", "jiti": "^2.4.0", }, "devDependencies": { "@libsql/client": "^0.14.0", - "@libsql/kysely-libsql": "^0.4.1", "@types/node": "^22.0.0", "@types/pg": "^8.0.0", "bun-types": "latest", @@ -166,12 +167,10 @@ "typescript": "^5.6.0", }, "peerDependencies": { - "@libsql/kysely-libsql": ">=0.4.0", "kysely": ">=0.27.0", "pg": ">=8.0.0", }, "optionalPeers": [ - "@libsql/kysely-libsql", "pg", ], }, @@ -708,6 +707,8 @@ "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], + "@toon-format/toon": ["@toon-format/toon@2.3.0", "", {}, "sha512-/Ew9etdRQKVMnm9fDaCG0JjyAOK/O7T0M97oum1aW4W+UR8ZhVVPBanIV7oWgHBiGlnVxV9M55PWQCHofDV07w=="], + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], "@types/docker-modem": ["@types/docker-modem@3.0.6", "", { "dependencies": { "@types/node": "*", "@types/ssh2": "*" } }, "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg=="], diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 7c891556b..9ba887342 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -174,7 +174,7 @@ Four packages, version-locked at the C# port version (currently `0.11.1`): | `MetaObjects` | Loader + canonical serializer | | `MetaObjects.Render` | Mustache render + payload-VO + `verify` | | `MetaObjects.Codegen` | EF Core + ASP.NET codegen + the runtime filter/dispatch helpers generated code references | -| `MetaObjects.Cli` | The `dotnet meta` .NET tool (`gen` / `verify` / `agent-docs`) | +| `MetaObjects.Cli` | The `dotnet meta` .NET tool (`gen` / `verify`; `agent-docs` is a redirect stub to the Node `meta` CLI) | Shared package metadata lives in [`server/csharp/Directory.Build.props`](../server/csharp/Directory.Build.props); per-package `PackageId`/`Title`/`Description` live in each `.csproj`. Test/integration projects set @@ -220,15 +220,11 @@ lock the repo/owner IDs against resurrection attacks.) *deprecate*. So validate the packed `.nupkg` locally before triggering the workflow. 3. **Bump the version in `Directory.Build.props`** (``), not per-project. The workflow can also override per-run via the `version` dispatch input (`-p:Version=`). -4. **Don't re-add `Pack`/`PackagePath` to the CLI's `agent-context/` Content item.** `PackAsTool` - already bundles build output (the files arrive via `CopyToOutputDirectory`) into - `tools/net8.0/any/`; an explicit `PackagePath` double-adds every file → **NU5118**, which is - fatal under this repo's `TreatWarningsAsErrors`. The item is deliberately `Pack=false`. -5. **The temp key is single-use and ~1 h.** The workflow requests it immediately before push — don't +4. **The temp key is single-use and ~1 h.** The workflow requests it immediately before push — don't move the `NuGet/login` step earlier. -6. **The policy is bound to the org + repo + workflow *filename*.** Renaming `publish-csharp.yml`, or +5. **The policy is bound to the org + repo + workflow *filename*.** Renaming `publish-csharp.yml`, or the policy owner leaving/locking the `metaobjects` org, makes the policy inactive until fixed. -7. **Source Link + symbols are on** (`PublishRepositoryUrl`, `EmbedUntrackedSources`, `snupkg`); CI +6. **Source Link + symbols are on** (`PublishRepositoryUrl`, `EmbedUntrackedSources`, `snupkg`); CI sets `ContinuousIntegrationBuild` for deterministic builds. No action needed — just don't strip them. ## Procedure @@ -283,16 +279,14 @@ subsequent releases keyless.) ## Gotchas (the non-obvious ones) -1. **The `agent-context/` bundle is vendored by a build hook, not a parent-path include.** - `hatch_build.py` copies the repo-root `agent-context/` into - `src/metaobjects/agent_context/_content` at build time, and `[tool.hatch.build] - artifacts` forces it into **both** sdist and wheel. Do **not** revert to a - `force-include` of `../../agent-context` — that builds a direct wheel but breaks - `uv build` (sdist → wheel), because the parent path is absent when building from the sdist. -2. **PyPI versions are immutable** (like npm/NuGet). You can't re-upload a version — only +1. **PyPI versions are immutable** (like npm/NuGet). You can't re-upload a version — only yank. Validate locally first (below). -3. **Bump the version in `pyproject.toml`** (`[project].version`). -4. **The wheel is pure-Python/universal** (`py3-none-any`) — one wheel serves every platform. +2. **Bump the version in `pyproject.toml`** (`[project].version`). +3. **The wheel is pure-Python/universal** (`py3-none-any`) — one wheel serves every platform. + + (No agent-context content is vendored into the wheel anymore — scaffolding moved to the + Node `meta agent-docs` CLI, so `hatch_build.py` is a no-op. Don't re-add a + `force-include` of `../../agent-context`.) ## Procedure diff --git a/docs/features/cli.md b/docs/features/cli.md index 6c0966de5..b8603ec5d 100644 --- a/docs/features/cli.md +++ b/docs/features/cli.md @@ -21,6 +21,7 @@ command surface splits in two: | Capability | Tool | Invocation | Notes | |---|---|---|---| | Project scaffold (`init`) | Node `meta` | `meta init` | TS projects | +| **Agent-context scaffold** (`agent-docs`) | **Node `meta`** | `npx meta agent-docs --server ` | **any backend** — single assembler (ADR-0033); non-Node CLIs redirect here | | **Schema migrate** | **Node `meta`** | `meta migrate` | **any backend** — schema is Node-only (ADR-0015) | | **Schema drift** (`verify --db`) | **Node `meta`** | `meta verify --db` | **any backend** — live-DB drift, Node-only | | **Codegen drift** (`verify --codegen`) | **Node `meta`** | `meta verify --codegen` | TS reference (ADR-0021 D2) — regen-to-temp + diff committed output | @@ -90,6 +91,25 @@ Java, Python, and Kotlin command surfaces are **codegen only** (`gen` + codegen goals and the C#/Python migrate surfaces were removed in the schema-authority consolidation; the only schema entry point anywhere is the Node `meta`. +## Agent-context scaffold is Node-only — by design + +The `.metaobjects/AGENTS.md`/`CLAUDE.md` always-on files and the +`.claude/skills/metaobjects-*/` reference tree are assembled by **one** tool: the +Node `meta agent-docs` command. Per [ADR-0033](../../spec/decisions/ADR-0033-single-agent-context-assembler.md) +the per-port native assemblers (Python/Java/C#) and their byte-identity conformance +gates were removed — that content is effectively one static artifact, and every port +already needs the Node `meta` CLI or its binary for schema ops (ADR-0015). + +```bash +npx meta agent-docs --server # csharp | java | kotlin | python | node +``` + +The C#, Java/Kotlin, and Python CLIs keep a **non-executing `agent-docs` pointer +stub** that prints `agent-context scaffolding moved to the meta CLI — run: npx meta +agent-docs --server ` to stderr and exits non-zero. The **live staleness check** +in `gen`/`verify` stays per-port (it only *reads* the scaffold to nudge when it drifts); +its message now points at `npx meta agent-docs --server `. + ## Running Kotlin codegen via Maven `codegen-kotlin`'s generators extend `MultiFileDirectGeneratorBase` — the same diff --git a/docs/llms/llms-full.txt b/docs/llms/llms-full.txt index bb8992a42..60e3d2bf5 100644 --- a/docs/llms/llms-full.txt +++ b/docs/llms/llms-full.txt @@ -30,7 +30,7 @@ If you are an AI assistant helping someone adopt MetaObjects, do this **first** - Java / Kotlin: add `com.metaobjects:metaobjects-metadata`, `com.metaobjects:metaobjects-codegen-spring` (Java) or `com.metaobjects:metaobjects-codegen-kotlin` (Kotlin), and `com.metaobjects:metaobjects-maven-plugin`, all at `7.4.1`. - Python: `pip install metaobjects` - C#: install the MetaObjects .NET tool (invoked as `dotnet meta`). -2. **Run `meta init`** (the Node `meta` CLI). It works in any project that has Node — including a JVM or Python project with a TypeScript frontend. It scaffolds a slim always-on `.metaobjects/AGENTS.md` + `.metaobjects/CLAUDE.md` (auto-wired via an `@.metaobjects/AGENTS.md` import line into the project's root `CLAUDE.md`/`AGENTS.md`, so it loads automatically) plus five `metaobjects-*` Claude Code skills under `.claude/skills/`, carrying only the project's language reference fragments. Re-run `meta init --refresh-docs` to update the agent docs after a CLI upgrade. (Native per-port scaffold commands for JVM/Python/C#-only projects are forthcoming; today, run the Node `meta init` regardless of backend.) +2. **Run `meta init`** (the Node `meta` CLI). It works in any project that has Node — including a JVM or Python project with a TypeScript frontend. It scaffolds a slim always-on `.metaobjects/AGENTS.md` + `.metaobjects/CLAUDE.md` (auto-wired via an `@.metaobjects/AGENTS.md` import line into the project's root `CLAUDE.md`/`AGENTS.md`, so it loads automatically) plus five `metaobjects-*` Claude Code skills under `.claude/skills/`, carrying only the project's language reference fragments. Re-run `meta init --refresh-docs` to update the agent docs after a CLI upgrade. (The Node `meta` CLI is the single agent-context scaffolder for every backend, by design — ADR-0033; the JVM/Python/C# CLIs deliberately redirect to it. For a non-Node project, scaffold just the agent context with `npx meta agent-docs --server `.) 3. **Load `.metaobjects/AGENTS.md` and the `.claude/skills/metaobjects-*` skills.** They teach metadata authoring, codegen, runtime/UI, prompt construction, and `verify` — for this project's exact stack and version. This file is the short corpus; the deep, version-matched how-to is the scaffolded local context. diff --git a/docs/llms/llms.txt b/docs/llms/llms.txt index cabfecd17..f0f1a068f 100644 --- a/docs/llms/llms.txt +++ b/docs/llms/llms.txt @@ -18,7 +18,7 @@ If you are an AI assistant helping someone adopt MetaObjects in their project, d - a slim always-on `.metaobjects/AGENTS.md` + `.metaobjects/CLAUDE.md` agent context, **auto-wired into the project's root `CLAUDE.md`/`AGENTS.md`** (an `@.metaobjects/AGENTS.md` import line) so it loads automatically; and - five `metaobjects-*` Claude Code skills under `.claude/skills/`, carrying only the language reference fragments for the project's stack. - Re-run `meta init --refresh-docs` to update the scaffolded agent docs after a CLI upgrade. (Native per-port scaffold commands for JVM/Python/C#-only projects are forthcoming; today, run the Node `meta init` regardless of backend language.) + Re-run `meta init --refresh-docs` to update the scaffolded agent docs after a CLI upgrade. (The Node `meta` CLI is the single agent-context scaffolder for every backend, by design — ADR-0033; the JVM/Python/C# CLIs deliberately redirect to it. Run `meta init` regardless of backend language, or `npx meta agent-docs --server ` to scaffold just the agent context.) 3. **After scaffolding, load `.metaobjects/AGENTS.md` and the `.claude/skills/metaobjects-*` skills.** They teach metadata authoring, codegen, runtime/UI, prompt construction, and `verify` — for this project's exact stack and version. diff --git a/docs/superpowers/plans/2026-06-23-agent-context-deploy-all-references.md b/docs/superpowers/plans/2026-06-23-agent-context-deploy-all-references.md new file mode 100644 index 000000000..03676c681 --- /dev/null +++ b/docs/superpowers/plans/2026-06-23-agent-context-deploy-all-references.md @@ -0,0 +1,250 @@ +# Agent-Context Deploy-All References — Implementation Plan (Plan 1 of 5) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make `meta init` deploy **all** per-language skill reference fragments (not just the detected stack's), author the missing `metaobjects-codegen/references/python.md`, and re-scaffold the metaobjects repo's own `.claude/skills` so every language reference is present — fixing the silent "agent flies blind" failure when stack detection comes up empty. + +**Architecture:** The agent-context assembler (`sdk/src/agent-context/assemble.ts`) currently filters `references/.md` by the resolved stack's token set. We remove that filter so every reference deploys; the agent decides which to read (the codegen `SKILL.md` already instructs reading the references). AGENTS.md/CLAUDE.md stack-line behavior is unchanged. The monorepo-root `agent-context/` is the source of truth; `npm run bundle-agent-context` syncs it into the SDK package. + +**Tech Stack:** TypeScript, `bun test`, the `@metaobjectsdev/sdk` + `@metaobjectsdev/cli` packages. + +## Global Constraints + +- Source of truth for agent-context content is the monorepo-root `agent-context/`; the SDK copy at `server/typescript/packages/sdk/agent-context/` is generated by `npm run bundle-agent-context` — never hand-edit the SDK copy. +- Tests run with `bun test` from `server/typescript/packages/sdk/`. +- Public repo: no private/downstream names, no absolute local paths in committed files. +- Deploy-all is intentional and overrides the older stack-selective design in `docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md` (§"references"); note the override in the spec. +- `SERVER_LANGS = ["typescript","java","kotlin","csharp","python"]`; `SKILL_NAMES = [metaobjects-authoring, metaobjects-codegen, metaobjects-runtime-ui, metaobjects-prompts, metaobjects-verify]`. + +--- + +## Task 1: Deploy all reference fragments (drop the stack filter) + +**Files:** +- Modify: `server/typescript/packages/sdk/src/agent-context/assemble.ts:49-62` +- Modify (test): `server/typescript/packages/sdk/test/agent-context/assemble.test.ts` (the "installs a reference fragment IFF its token is in the stack" test) + +**Interfaces:** +- Consumes: `assemble({ contentRoot, stack })` (unchanged signature), `makeStack(servers, clients)` from `resolve.js`. +- Produces: `assemble` now emits `.claude/skills//references/.md` for **every** `*.md` in each skill's `references/` dir, independent of `stack.tokens`. + +- [ ] **Step 1: Update the test to assert deploy-all (write the failing test)** + +In `assemble.test.ts`, replace the `installs a reference fragment IFF its token is in the stack` test with: +```ts + test("installs ALL reference fragments regardless of stack (agent picks)", () => { + const stack = makeStack(["typescript"], ["react"]); // a narrow stack... + const p = paths(assemble({ contentRoot: CONTENT_ROOT, stack })); + // ...still gets every language's codegen reference: + expect(p).toContain(".claude/skills/metaobjects-codegen/references/typescript.md"); + expect(p).toContain(".claude/skills/metaobjects-codegen/references/java.md"); + expect(p).toContain(".claude/skills/metaobjects-codegen/references/kotlin.md"); + expect(p).toContain(".claude/skills/metaobjects-codegen/references/csharp.md"); + expect(p).toContain(".claude/skills/metaobjects-verify/references/migration.md"); + for (const s of ["authoring", "codegen", "runtime-ui", "prompts", "verify"]) { + expect(p).toContain(`.claude/skills/metaobjects-${s}/SKILL.md`); + } + }); +``` + +- [ ] **Step 2: Run it; verify it fails** + +Run: `cd server/typescript/packages/sdk && bun test test/agent-context/assemble.test.ts` +Expected: FAIL — `java.md`/`kotlin.md`/`csharp.md` are absent for a `["typescript"]` stack (current filter excludes them). + +- [ ] **Step 3: Remove the token filter in `assemble.ts`** + +Replace lines 49-62 (the `refDir` block) with: +```ts + const refDir = join(skillDir, "references"); + if (existsSync(refDir) && statSync(refDir).isDirectory()) { + // Deploy-all: every reference fragment is installed; the agent reads the + // one(s) matching its stack (SKILL.md points to them). This is robust to + // stack-detection misses — a narrow/empty stack never starves the agent. + const refs = readdirSync(refDir) + .filter((f) => f.endsWith(".md")) + .map((f) => f.replace(/\.md$/, "")) + .sort(); + for (const token of refs) { + out.push({ + path: `.claude/skills/${skill}/references/${token}.md`, + contents: readFileSync(join(refDir, `${token}.md`), "utf8"), + }); + } + } +``` + +- [ ] **Step 4: Run the test; verify it passes** + +Run: `bun test test/agent-context/assemble.test.ts` +Expected: PASS. + +- [ ] **Step 5: Run the full sdk suite to catch drift/size-gate/conformance fallout** + +Run: `bun test` +Expected: PASS. If `test/agent-context/drift.test.ts`, `size-gate.test.ts`, or `agent-context-conformance.test.ts` fail because they assumed stack-selective deploy or a size ceiling, update those assertions to the deploy-all reality (more reference files; larger assembled set). Show the diff for any test changed and keep the intent (drift = bundled copy matches source; size-gate = adjust the ceiling to fit all references, noting the new number). + +- [ ] **Step 6: Commit** + +```bash +git add server/typescript/packages/sdk/src/agent-context/assemble.ts \ + server/typescript/packages/sdk/test/agent-context/ +git commit -m "feat(agent-context): deploy all language references; agent picks (robust to stack-detection miss)" +``` + +--- + +## Task 2: Author the missing `metaobjects-codegen/references/python.md` + +**Files:** +- Create: `agent-context/skills/metaobjects-codegen/references/python.md` +- Reference (read for structure/voice): `agent-context/skills/metaobjects-codegen/references/csharp.md` and `.../java.md` + +**Interfaces:** +- Produces: a `python.md` codegen reference deployed by Task 1's assembler for every project. + +- [ ] **Step 1: Read the sibling references to match structure** + +Run: `cat agent-context/skills/metaobjects-codegen/references/csharp.md agent-context/skills/metaobjects-codegen/references/java.md` +Expected: see the section shape (CLI command, generators, layout, known gaps). Mirror it. + +- [ ] **Step 2: Write `python.md`** + +Create `agent-context/skills/metaobjects-codegen/references/python.md` with this content (adapt headings to match the siblings' exact style if they differ): +```markdown +# MetaObjects codegen — Python + +MetaObjects' Python codegen emits **Pydantic models** (one per `object.entity`) and +**FastAPI routers** (one `APIRouter` per entity). It does **not** generate an ORM +layer or a runnable server — you wire persistence (SQLAlchemy / asyncpg) and the +FastAPI app yourself. + +## Generate + +``` +metaobjects gen # emits Pydantic models + FastAPI routers +metaobjects verify --codegen # drift gate +``` +Schema migrations are TS-owned (ADR-0015): run `meta migrate` (the Node CLI), not a +Python migrate subcommand — the Python CLI has no migrate surface. + +## What is generated + +- **Pydantic models** — one model per entity. Requires **Pydantic 2.x** at runtime + (the codegen emits Pydantic-2 model syntax). Pin `pydantic>=2`. +- **FastAPI routers** — one `APIRouter` per entity. Requires **FastAPI** (tested + against 0.110+). The router declares a repository **`Protocol`**; you implement it + against your datastore and inject it. + +## Wiring (hand-written bootstrap) + +MetaObjects never emits the server entrypoint. Create `main.py`: +```python +from fastapi import FastAPI +from app.generated.author_router import router as author_router # generated +app = FastAPI() +app.include_router(author_router) +``` +Implement each generated repository `Protocol` (SQLAlchemy/asyncpg) and pass it in. + +## Known gaps (current) + +- Generated routers type request/response bodies as `dict[str, Any]`, not the typed + Pydantic model (per-target output dirs not wired yet) — you may want to tighten + these by hand. +- Integer primary keys are assumed; non-int / composite PKs need a hand-edit. +- No persistence layer is generated — consumer owns the datastore wiring. +``` +(If the actual generators differ from this when you run `metaobjects gen` on a sample +entity, correct the doc to match observed output before committing.) + +- [ ] **Step 3: Verify it deploys via the Task-1 assembler** + +Run: `cd server/typescript/packages/sdk && bun test test/agent-context/assemble.test.ts` +Then add (or extend the Task-1 test) an assertion: +```ts + expect(p).toContain(".claude/skills/metaobjects-codegen/references/python.md"); +``` +Expected: PASS (python.md now present in the assembled set). + +- [ ] **Step 4: Commit** + +```bash +git add agent-context/skills/metaobjects-codegen/references/python.md \ + server/typescript/packages/sdk/test/agent-context/assemble.test.ts +git commit -m "docs(agent-context): add Python codegen reference (closes the python.md gap)" +``` + +--- + +## Task 3: Re-bundle and re-scaffold the metaobjects repo's own agent-context + +**Files:** +- Modify (generated): `server/typescript/packages/sdk/agent-context/**` (via bundle script) +- Modify (generated): the repo's `.claude/skills/metaobjects-*/references/**` and `.metaobjects/.agent-context.json` (via `meta init`) + +**Interfaces:** +- Consumes: Task 1 assembler + Task 2 python.md. +- Produces: the metaobjects repo's own installed skills now carry **all** language references (this is also what the benchmark copies). + +- [ ] **Step 1: Re-bundle the SDK copy from source** + +Run: `cd server/typescript/packages/sdk && npm run bundle-agent-context && bun test test/agent-context/drift.test.ts` +Expected: bundle syncs root `agent-context/` → SDK copy (now including python.md + the deploy-all set); drift test PASS. + +- [ ] **Step 2: Build the CLI and re-scaffold the repo** + +Run from repo root: +```bash +(cd server/typescript/packages/sdk && npm run build) +(cd server/typescript/packages/cli && npm run build) +node server/typescript/packages/cli/dist/bin/meta.js init --refresh-docs +``` +Expected: `.claude/skills/metaobjects-codegen/references/` now contains `typescript.md, java.md, kotlin.md, csharp.md, python.md`; `.metaobjects/.agent-context.json` updated. If `init` needs a stack and detection is empty, deploy-all means references still land regardless. + +- [ ] **Step 3: Verify all references are present on disk** + +Run: `ls server/.../.. ; ls .claude/skills/metaobjects-codegen/references/` +Expected: all five language `.md` files present. + +- [ ] **Step 4: Commit** + +```bash +git add server/typescript/packages/sdk/agent-context .claude/skills .metaobjects/.agent-context.json +git commit -m "chore(agent-context): re-bundle + re-scaffold repo skills with all language references" +``` + +--- + +## Task 4: Note the design override in the spec + +**Files:** +- Modify: `docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md` (the references/stack-selective section) + +- [ ] **Step 1: Add an override note** + +Append to the stack-selective references section: +```markdown +> **Update (2026-06-23):** Superseded — references are now deployed for **all** +> languages, not stack-selectively. Stack detection misses were silently starving +> agents of language guidance; deploy-all is robust (the agent reads the reference +> matching its stack; `SKILL.md` points to them). AGENTS.md/CLAUDE.md remain +> stack-aware. See assemble.ts deploy-all change. +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md +git commit -m "docs(spec): record deploy-all references override" +``` + +--- + +## Self-Review + +**Spec coverage:** deploy-all (Task 1) ✓; python.md gap (Task 2) ✓; repo re-scaffold so references actually land + unblock the benchmark (Task 3) ✓; spec override recorded (Task 4) ✓. + +**Placeholder scan:** python.md content is provided in full (Task 2 Step 2) with a "correct to observed output" guard, not a TODO. Drift/size-gate fallout is handled as an explicit conditional step (Task 1 Step 5) with the intent stated, since the exact failing assertions can't be known until run. + +**Type consistency:** `assemble({contentRoot, stack})`, `makeStack`, `AssembledFile.path`, `SKILL_NAMES`, `SERVER_LANGS` used consistently with `types.ts`. The removed filter referenced `stack.tokens`; `Stack.tokens` stays in the type (still used by AGENTS.md stack-line logic via `stackLine`), only the reference filtering stops using it. diff --git a/docs/superpowers/plans/2026-06-23-agent-context-single-assembler.md b/docs/superpowers/plans/2026-06-23-agent-context-single-assembler.md new file mode 100644 index 000000000..1328b3db4 --- /dev/null +++ b/docs/superpowers/plans/2026-06-23-agent-context-single-assembler.md @@ -0,0 +1,208 @@ +# Single Agent-Context Assembler — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Collapse the agent-context to a single Node assembler — delete the Python, Java/Kotlin, and C# native assemblers + their byte-identity conformance tests + their embedded `agent-context/` copies, and replace each non-Node CLI's `agent-docs` with a non-executing pointer stub that redirects to the Node `meta agent-docs --server `. + +**Architecture:** The Node `meta` CLI (npm primary; optional binary) becomes the sole scaffolder — consistent with ADR-0015 (migrations already require Node/binary). Per-port "assembly" was reduced to "copy a static tree + interpolate two strings" by deploy-all, so the native re-implementations + N byte-identity gates are pure cost. Folded into branch `metaobjects-cli-axi-and-deploys` (PR #71) as deletions; the earlier cross-port assembler *fixes* are superseded. + +**Tech Stack:** Node/TS (`meta` CLI + SDK assembler, `bun test`), Python (`uv`/`pytest`, Hatch), Java/Kotlin (Maven), C# (.NET 8 / `dotnet test`). + +## Global Constraints +- **Source of truth stays** monorepo-root `agent-context/`; the **only** kept assembler is `server/typescript/packages/sdk/src/agent-context/assemble.ts` + its single conformance gate (`agent-context-conformance.test.ts`, `assemble.test.ts`) + `regen-agent-context-conformance.ts`. +- **Pointer stub contract (all non-Node CLIs):** invoking `agent-docs` prints, to stdout, exactly: `agent-context scaffolding moved to the meta CLI — run: npx meta agent-docs --server [--client ] [--out ]` and exits non-zero `1`. It MUST NOT exec any process, read any embedded content, or import any assembler symbol. +- **Keep the staleness-nudge machinery** in each port (it reads the `.metaobjects/.agent-context.json` manifest the Node CLI writes; used by `gen`/`verify`). Delete only the *assembly* path. +- Public repo: no private names / local abspaths in committed files. +- Each port's FULL test suite must pass after its deletions (no dangling refs). + +--- + +## Task 1: Add a `meta agent-docs` alias on the Node CLI (the redirect target) + +**Files:** +- Modify: `server/typescript/packages/cli/src/index.ts` (dispatch + HELP_TEXT) +- Modify (maybe): `server/typescript/packages/cli/src/commands/init.ts` (export the docs-only path) +- Test: `server/typescript/packages/cli/test/agent-docs-alias.test.ts` + +**Interfaces:** +- Produces: `meta agent-docs [--server ]... [--client ]... [--out ]` that scaffolds the agent-context (the `.metaobjects/` always-on files + `.claude/skills/**`) for the given stack — the same code path `init` already uses (`assemble()` + `planScaffold()`), without the `metaobjects/` project scaffold. + +- [ ] **Step 1: Read `init.ts`** to find the existing agent-context write path (`writeAgentContext()` ~lines 99-110, which calls `resolveStack` + `assemble` + `planScaffold`). Confirm whether `init --refresh-docs` already does exactly "docs only". If it does, `agent-docs` is a thin alias to that path; if not, extract the docs-only write into a reusable function. + +- [ ] **Step 2: Write the failing test** `test/agent-docs-alias.test.ts`: +```ts +import { test, expect } from "bun:test"; +import { run } from "../src/index.js"; +import { mkdtempSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +test("meta agent-docs --server csharp scaffolds the agent-context (no metaobjects/ project)", async () => { + const dir = mkdtempSync(join(tmpdir(), "meta-agentdocs-")); + const code = await run(["agent-docs", "--server", "csharp", "--cwd", dir]); + expect(code).toBe(0); + expect(existsSync(join(dir, ".claude/skills/metaobjects-codegen/references/csharp.md"))).toBe(true); + expect(existsSync(join(dir, ".claude/skills/metaobjects-codegen/references/python.md"))).toBe(true); // deploy-all + expect(existsSync(join(dir, ".metaobjects/AGENTS.md"))).toBe(true); + // codegenCommand for csharp comes from servers/csharp.meta.json + expect(require("node:fs").readFileSync(join(dir, ".metaobjects/AGENTS.md"), "utf8")).toContain("dotnet meta gen"); +}); +``` + +- [ ] **Step 3: Run it; verify it fails** — `cd server/typescript/packages/cli && bun test test/agent-docs-alias.test.ts` → FAIL (`agent-docs` unknown command / falls to default). + +- [ ] **Step 4: Implement** the `agent-docs` case in `index.ts`'s `run()` switch (mirror the `init` case), dispatching to the docs-only write path with the parsed `--server/--client/--out`. Add an `agent-docs` line to `HELP_TEXT` COMMANDS. Keep `init` as-is. + +- [ ] **Step 5: Run the test + full CLI suite** — `bun test test/agent-docs-alias.test.ts && bun test` → PASS, no regressions. + +- [ ] **Step 6: Commit** — `git add server/typescript/packages/cli/src/index.ts server/typescript/packages/cli/src/commands/init.ts server/typescript/packages/cli/test/agent-docs-alias.test.ts && git commit -m "feat(cli): add 'meta agent-docs --server ' alias (canonical scaffolder for all ports)"` + +--- + +## Task 2: Python — delete native assembler, stub `agent-docs` + +**Files:** +- Delete: `server/python/src/metaobjects/agent_context/assemble.py`, `.../agent_context/types.py`, `.../agent_context/content_root.py`, `server/python/tests/conformance/test_agent_context_conformance.py` +- Delete (verify first): `server/python/src/metaobjects/agent_context/scaffold.py` (only if its only consumer was `agent-docs`) +- Modify: `server/python/hatch_build.py` (remove the `_content` vendoring), `server/python/.gitignore` (drop the `_content/` line), `server/python/src/metaobjects/agent_context/__init__.py` (drop deleted exports), `server/python/src/metaobjects/cli.py` (stub `_cmd_agent_docs`, fix imports) +- Delete dir: `server/python/src/metaobjects/agent_context/_content/` + +**Interfaces:** Produces the pointer stub for `metaobjects agent-docs`. Keeps `agent_context_staleness`, `Manifest`, `AGENT_CONTEXT_MANIFEST_PATH`, `installed_metaobjects_version` (used by gen/verify nudge — do NOT delete). + +- [ ] **Step 1: Map references before deleting.** Run `cd server/python && grep -rn "assemble\|make_stack\|AssembledFile\|resolve_agent_context_root\|plan_scaffold\|from .scaffold\|agent_context._content\|content_root" src tests | grep -v "_content/"`. Confirm: (a) only `_cmd_agent_docs` (cli.py ~561-646) calls `assemble`/`make_stack`/`resolve_agent_context_root`; (b) whether `scaffold.py`/`plan_scaffold` is used by anything other than `agent-docs` — if not, delete it too; if yes, leave it and only delete `assemble.py`/`types.py`/`content_root.py`. Record findings. + +- [ ] **Step 2: Stub the command** in `cli.py` — replace the body of `_cmd_agent_docs` (~lines 561-646) with: +```python +def _cmd_agent_docs(args: argparse.Namespace) -> int: + print( + "agent-context scaffolding moved to the meta CLI — run: " + "npx meta agent-docs --server python [--client ] [--out ]", + file=sys.stderr, + ) + return 1 +``` +Keep its argparse registration (~lines 763-789) so the command still exists (it just redirects). Remove the now-unused argparse options that only fed the deleted impl if they cause unused-var lint; otherwise leave minimal. + +- [ ] **Step 3: Remove imports + exports.** In `cli.py` delete the imports of `assemble`, `make_stack`, `resolve_agent_context_root` (keep `Manifest`, `agent_context_staleness`, `AGENT_CONTEXT_MANIFEST_PATH`, `installed_metaobjects_version`, and `plan_scaffold` only if Step 1 kept `scaffold.py`). In `agent_context/__init__.py` drop the `assemble`/`make_stack`/`AssembledFile`/`Stack`/`types` re-exports. + +- [ ] **Step 4: Delete the files** identified (assemble.py, types.py, content_root.py, the conformance test, scaffold.py if unused, `_content/`), remove the `hatch_build.py` vendoring (lines ~30-39 → no-op or delete the hook) and the `.gitignore` `_content/` line. + +- [ ] **Step 5: Verify the Python suite + build.** Run `cd server/python && uv run pytest -q` → all pass (the staleness tests still pass; the agent-context conformance test is gone). Then confirm the package still builds without the hook: `uv build 2>&1 | tail -3` (or the project's build command) → success. If any dangling import remains, fix it. + +- [ ] **Step 6: Commit** — `git add -A server/python && git commit -m "refactor(python): drop native agent-context assembler + embedding; agent-docs redirects to meta CLI"` + +--- + +## Task 3: Java/Kotlin — delete native assembler, stub the Mojo + +**Files:** +- Delete: `server/java/metadata/src/main/java/com/metaobjects/agentcontext/AgentContextAssembler.java`, `.../AssembledFile.java`, `.../Stack.java`, `.../ContentRoot.java`, `server/java/metadata/src/test/java/com/metaobjects/agentcontext/AgentContextConformanceTest.java` +- Modify: `server/java/maven-plugin/pom.xml` (remove the `bundle-agent-context` `copy-resources` execution), `server/java/maven-plugin/src/main/java/com/metaobjects/mojo/AgentDocsMojo.java` (stub `execute()` + drop imports) + +**Interfaces:** Produces the pointer stub for `mvn metaobjects:agent-docs`. Keeps `AgentContextScaffold`/`Manifest`/staleness (verify they don't import the deleted `AssembledFile`/`Stack`; if they do, that code only served agent-docs → handle in Step 1). + +- [ ] **Step 1: Map references.** `cd server/java && grep -rn "AgentContextAssembler\|AssembledFile\|com.metaobjects.agentcontext.Stack\|ContentRoot" --include=*.java --include=*.kt src */src 2>/dev/null | grep -v "/target/"`. Confirm only `AgentDocsMojo` uses `AgentContextAssembler`/`AssembledFile`/`ContentRoot`/`Stack`. Confirm `AgentContextScaffoldTest`/`AgentContextStalenessTest` don't depend on the deleted types (the manifest says they're independent — verify). + +- [ ] **Step 2: Stub the Mojo** — in `AgentDocsMojo.java`, replace `execute()` (~lines 70-132) with: +```java +@Override +public void execute() throws MojoExecutionException { + getLog().warn("agent-context scaffolding moved to the meta CLI — run: " + + "npx meta agent-docs --server java [--client ] [--out ]"); + throw new MojoExecutionException("agent-docs is no longer implemented in the Maven plugin; use the meta CLI."); +} +``` +Delete the four `import com.metaobjects.agentcontext.*` lines (~7,9-11). Keep the `@Mojo(name = "agent-docs", ...)` annotation so the goal still exists (it just fails-with-redirect). + +- [ ] **Step 3: Remove the embedding** — in `server/java/maven-plugin/pom.xml`, delete the `bundle-agent-context...copy-resources...` block (~lines 57-76). + +- [ ] **Step 4: Delete the assembler + conformance files** listed above. + +- [ ] **Step 5: Verify the JVM build + the CI-run tests.** Run the same tests CI runs plus a build: +``` +cd server/java && mvn -pl metadata,maven-plugin -am install -DskipTests -q \ + && mvn -pl metadata test -Dtest='ConformanceTest,YamlConformanceTest,ObjectModelConformanceTest,RegistryManifestConformanceTest,AgentContextScaffoldTest,AgentContextStalenessTest' -q +``` +Expected: BUILD SUCCESS, tests pass. (`AgentContextConformanceTest` is gone and was never in the CI `-Dtest` list, so CI is unaffected — confirm by grepping `.github/workflows/conformance.yml` for the test name: absent.) + +- [ ] **Step 6: Commit** — `git add -A server/java && git commit -m "refactor(java): drop native agent-context assembler + classpath embedding; agent-docs Mojo redirects to meta CLI"` + +--- + +## Task 4: C# — delete native assembler, stub `agent-docs` + +**Files:** +- Delete: `server/csharp/MetaObjects/AgentContext/AgentContextAssembler.cs`, `.../AgentContext/AssembledFile.cs`, `.../AgentContext/Stack.cs` (verify path), `.../AgentContext/ContentRoot.cs`, `server/csharp/MetaObjects.Conformance.Tests/AgentContextConformanceTests.cs`, `server/csharp/MetaObjects.Cli/AgentDocsCommand.cs` +- Modify: `server/csharp/MetaObjects.Cli/MetaObjects.Cli.csproj` (remove the `agent-context` `` ItemGroup), `server/csharp/MetaObjects.Cli/Program.cs` (stub the `agent-docs` dispatch + help text), and `server/csharp/MetaObjects.Cli.Tests/AgentContextStalenessTests.cs` (it calls `AgentDocsCommand.Run` — update or delete) + +**Interfaces:** Produces the pointer stub for `dotnet meta agent-docs`. + +- [ ] **Step 1: Map references + resolve the two verify-items.** `cd server/csharp && grep -rn "AgentContextAssembler\|AssembledFile\|MetaObjects.AgentContext\|ContentRoot\|AgentDocsCommand" --include=*.cs . | grep -v "/bin/\|/obj/"`. Confirm (a) `Stack.cs` exists under `MetaObjects/AgentContext/`; (b) what `AgentContextStalenessTests.cs:~33` (`AgentDocsCommand.Run(...)`) actually tests — it tests staleness stamping, which depends on the manifest being written. Since `agent-docs` no longer writes the manifest (Node does), **update that test** to write a minimal `.metaobjects/.agent-context.json` fixture directly and assert the staleness decision, OR delete it if it's purely an agent-docs-writes-manifest test. Record the decision. + +- [ ] **Step 2: Stub the dispatch** in `Program.cs` (~line 43), replace `"agent-docs" => AgentDocsCommand.Run(args[1..]),` with: +```csharp +"agent-docs" => AgentDocsRedirect(), +``` +and add a local: +```csharp +static int AgentDocsRedirect() +{ + Console.Error.WriteLine("agent-context scaffolding moved to the meta CLI — run: " + + "npx meta agent-docs --server csharp [--client ] [--out ]"); + return 1; +} +``` +Remove the `agent-docs` lines from the help text (~lines 31-34) or replace with a one-line "agent-docs — see `npx meta agent-docs`". + +- [ ] **Step 3: Delete** `AgentDocsCommand.cs`, the assembler files, `ContentRoot.cs`, and `AgentContextConformanceTests.cs`. Remove the `` embedding `agent-context/**` from `MetaObjects.Cli.csproj` (~lines 39-45). + +- [ ] **Step 4: Apply the Step-1 decision** to `AgentContextStalenessTests.cs` (update to a manifest fixture, or delete). + +- [ ] **Step 5: Verify the C# build + the CI-run tests.** +``` +cd server/csharp \ + && dotnet test MetaObjects.Conformance.Tests/MetaObjects.Conformance.Tests.csproj --nologo --verbosity quiet \ + && dotnet test MetaObjects.Cli.Tests/MetaObjects.Cli.Tests.csproj --nologo --verbosity quiet +``` +Expected: build succeeds (no dangling refs to the deleted namespace), all tests pass. The Conformance project still runs its other real tests (not a false-green); `AgentContextConformanceTests` is gone. + +- [ ] **Step 6: Commit** — `git add -A server/csharp && git commit -m "refactor(csharp): drop native agent-context assembler + embedded resources; agent-docs redirects to meta CLI"` + +--- + +## Task 5: ADR + READMEs + CI sanity + +**Files:** +- Create: `spec/decisions/ADR-0033-single-agent-context-assembler.md` (confirm next ADR number first) +- Modify: each port's README mentioning `agent-docs` (Python/C#/Java), and `docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md` (supersede note) +- Verify: `.github/workflows/conformance.yml` + +- [ ] **Step 1: Confirm the next ADR number** — `ls spec/decisions/ | grep -oE 'ADR-[0-9]+' | sort -u | tail -3`. Use the next free number (the spec assumed 0033). + +- [ ] **Step 2: Write the ADR** — record the decision (single Node assembler; non-Node ports redirect via pointer stub; reconciles ADR-0015), superseding the "each port ships its own native scaffolder" non-goal in ADR-0027 / the 2026-06-02 spec. Add a one-line `> Superseded (2026-06-23): see ADR-0033` note to both. + +- [ ] **Step 3: Update the port READMEs** — replace any `dotnet meta agent-docs` / `metaobjects agent-docs` / `mvn metaobjects:agent-docs` usage doc with `npx meta agent-docs --server `. + +- [ ] **Step 4: CI sanity (no false-green).** Confirm the Python (`pytest tests/conformance -q`) and C# (`dotnet test MetaObjects.Conformance.Tests`) legs still execute multiple OTHER real conformance tests after the agent-context test is removed (grep the dir/project for remaining `*conformance*` tests). They do, so no workflow edit is required — but if a leg would be left empty, add an explicit test list. Record the check. + +- [ ] **Step 5: Commit** — `git add spec/decisions docs server/*/README* 2>/dev/null; git commit -m "docs(adr): ADR-0033 single agent-context assembler; redirect non-Node CLIs; supersede ADR-0027 non-goal"` + +--- + +## Task 6: Whole-branch verification + +- [ ] **Step 1: Run every port's suite once more** — TS (`cd server/typescript/packages/sdk && bun test test/agent-context` + `cd ../cli && bun test`), Python (`uv run pytest -q`), C# (the two `dotnet test` projects from Task 4), Java (the Task-3 mvn line). All green. + +- [ ] **Step 2: Confirm the single conformance gate is the only one left** — `grep -rln "byte-identical\|agent-context-conformance\|AgentContextConformance" server --include=*.ts --include=*.py --include=*.cs --include=*.java | grep -v "/bin/\|/dist/\|/target/\|/_content/"` returns only the TS file(s). + +- [ ] **Step 3: Push the branch** (continues PR #71) — `git push`. Then watch CI: `conformance (csharp)` / `conformance (python)` must pass by running their *remaining* tests (not zero). Do not merge; report PR ready. + +--- + +## Self-Review + +**Spec coverage:** single Node assembler kept (T1 confirms the canonical command; assembler untouched) ✔; delete Python/Java/C# assemblers + gates + embedding (T2/T3/T4) ✔; pointer stub per port (T2/T3/T4, contract in Global Constraints) ✔; keep staleness machinery (called out in every per-port task) ✔; ADR + supersede + READMEs (T5) ✔; CI-not-false-green (T5 S4, T6 S3) ✔; folded into branch as deletions (header) ✔. Out-of-scope (Plan 3/4/5 TOON; binary shipping) excluded. + +**Placeholder scan:** the three flagged verify-items from the manifest (Python `scaffold.py`/`plan_scaffold` usage; C# `Stack.cs` path; C# `AgentContextStalenessTests` fate) are explicit Step-1 "map references / decide" steps in their tasks, not silent TODOs — each task records the finding before deleting. Stub code is given in full per port. + +**Type/contract consistency:** the pointer-stub message + `exit 1` + "no exec/no import" contract is identical across T2/T3/T4 and the Global Constraints; the redirect target `meta agent-docs --server ` is the command T1 creates. The kept-symbols list (staleness/manifest) is consistent across the per-port tasks. diff --git a/docs/superpowers/plans/2026-06-23-node-meta-cli-axi-toon.md b/docs/superpowers/plans/2026-06-23-node-meta-cli-axi-toon.md new file mode 100644 index 000000000..9e5fbe28b --- /dev/null +++ b/docs/superpowers/plans/2026-06-23-node-meta-cli-axi-toon.md @@ -0,0 +1,451 @@ +# Node `meta` CLI → axi + TOON — Implementation Plan (Plan 2 of 5) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make the Node `meta` CLI agent-friendly per the axi standard — TOON output (TTY-aware default), a uniform `--format` flag, structured errors + exit codes, next-step suggestions, per-subcommand `--help`, and the `meta migrate` UX fixes — so an agent (and the four downstream ports that mirror it) gets token-efficient, parseable, self-describing output. + +**Architecture:** `meta`'s output already flows through pure formatter functions (`src/lib/output.ts`) and a stdout/stderr-split logger (`src/lib/log.ts`). We add a sibling TOON formatter per result type using `@toon-format/toon`, a `resolveFormat()` selector, and a global `--format` flag parsed in `run()` (like the existing `--cwd`). Default is TTY-aware: humans at a terminal get the current text; pipes/agents get TOON. axi's robustness/discoverability rules (exit codes, structured errors on stdout, `help[]` next-steps, per-subcommand `--help`) are layered onto the existing dispatcher. + +**Tech Stack:** TypeScript, `bun test`, `@toon-format/toon@^2.3.0`, the existing `meta` CLI (`server/typescript/packages/cli`). + +## Global Constraints + +- **Decisions (from the 2026-06-23 design review):** D1 TTY-aware TOON (text on TTY, TOON on non-TTY); D2 flag is `--format `; exit codes `0` success/no-op, `1` error, `2` usage error; structured errors on **stdout** (per axi), progress/debug on stderr; D5 build order Node first (this plan). +- **TOON package:** `@toon-format/toon` (the official org pkg; the bare `toon` is an unrelated squatter). Confirm the encode export name on install (Task 1 Step 2). +- **No interactive prompts** — every operation completable via flags (axi). The migrate `--remote --apply` confirmation pause must be skippable with `--yes` (already exists). +- **Public repo:** no private names / local abspaths in committed files. +- Tests: `bun test` from `server/typescript/packages/cli/`. +- Existing data shapes (do not rename): `GenResultShape { files: GenFileEntry[]; outDir; dialect; dryRun; warnings }`, `GenFileEntry { path; status: GenFileStatus; info }`, `GenFileStatus = "new"|"merged"|"conflict"|"unchanged"|"refused"`. The migrate shape is in `output.ts` (`formatMigrateResult` input) — read it in Task 4. + +--- + +## File Structure + +``` +server/typescript/packages/cli/ + src/lib/format.ts # NEW: OutputFormat type + resolveFormat() + toonEncode() wrapper + src/lib/output.ts # MODIFY: add formatGenResultToon / formatMigrateResult Toon; add help[] next-steps + src/lib/output-json.ts # NEW: plain JSON fallback (--format json) for gen/migrate result shapes + src/index.ts # MODIFY: parse global --format; per-subcommand --help; content-first no-args + src/commands/gen.ts # MODIFY: emit chosen format + src/commands/migrate.ts # MODIFY: emit chosen format; axi UX fixes (baseline, libsql, idempotency) + package.json # MODIFY: add @toon-format/toon + @libsql/kysely-libsql dep + test/lib/format.test.ts # NEW + test/lib/output-toon.test.ts # NEW +``` + +--- + +## Task 1: Format selector + TOON wrapper + +**Files:** +- Create: `src/lib/format.ts` +- Modify: `package.json` (add `@toon-format/toon`) +- Test: `test/lib/format.test.ts` + +**Interfaces:** +- Produces: `type OutputFormat = "toon" | "json" | "text"`; `resolveFormat(flag: string | undefined, isTTY: boolean): OutputFormat`; `toonEncode(value: unknown): string`. + +- [ ] **Step 1: Write the failing test** + +`test/lib/format.test.ts`: +```ts +import { test, expect, describe } from "bun:test"; +import { resolveFormat, toonEncode } from "../../src/lib/format.js"; + +describe("resolveFormat", () => { + test("explicit flag wins over TTY", () => { + expect(resolveFormat("toon", true)).toBe("toon"); + expect(resolveFormat("text", false)).toBe("text"); + expect(resolveFormat("json", true)).toBe("json"); + }); + test("default is TTY-aware: text on TTY, toon on non-TTY", () => { + expect(resolveFormat(undefined, true)).toBe("text"); + expect(resolveFormat(undefined, false)).toBe("toon"); + }); +}); + +describe("toonEncode", () => { + test("emits tabular TOON for a uniform array of objects", () => { + const out = toonEncode({ gen: [{ file: "a.ts", status: "new" }, { file: "b.ts", status: "new" }] }); + expect(out).toContain("gen[2]{file,status}:"); + expect(out).toContain("a.ts,new"); + }); +}); +``` + +- [ ] **Step 2: Add the dep and confirm the encode export** + +Run: `cd server/typescript/packages/cli && npm pkg set dependencies.@toon-format/toon=^2.3.0 && npm install @toon-format/toon@^2.3.0` +Then confirm the export: `node -e "console.log(Object.keys(require('@toon-format/toon')))"` +Expected: an `encode` (and `decode`) export. If the encoder is named differently (e.g. `stringify`), use that name in Step 3 and note it. + +- [ ] **Step 3: Implement `format.ts`** + +```ts +import { encode } from "@toon-format/toon"; // confirm name in Step 2 + +export type OutputFormat = "toon" | "json" | "text"; + +const VALID = new Set(["toon", "json", "text"]); + +export function resolveFormat(flag: string | undefined, isTTY: boolean): OutputFormat { + if (flag && VALID.has(flag as OutputFormat)) return flag as OutputFormat; + // TTY-aware default: humans at a terminal get text; pipes/agents get TOON. + return isTTY ? "text" : "toon"; +} + +export function toonEncode(value: unknown): string { + return encode(value); +} +``` + +- [ ] **Step 4: Run the test; verify pass** + +Run: `bun test test/lib/format.test.ts` +Expected: PASS (both describes). If `toonEncode`'s tabular output differs (header/row shape), align the assertion to the real TOON output (it must collapse uniform object arrays to `name[len]{keys}:` + CSV rows). + +- [ ] **Step 5: Commit** + +```bash +git add src/lib/format.ts test/lib/format.test.ts package.json package-lock.json +git commit -m "feat(cli): add output-format selector (TTY-aware) + TOON encode wrapper" +``` + +--- + +## Task 2: TOON + JSON formatters for the gen result + +**Files:** +- Modify: `src/lib/output.ts` (add `formatGenResultToon`, `genResultToData`) +- Create: `src/lib/output-json.ts` +- Test: `test/lib/output-toon.test.ts` + +**Interfaces:** +- Consumes: `GenResultShape`, `toonEncode` (Task 1). +- Produces: `genResultToData(result: GenResultShape): object` (the axi-shaped plain object: tabular files + aggregates + `help`), `formatGenResultToon(result: GenResultShape): string`, `formatGenResultJson(result: GenResultShape): string`. + +- [ ] **Step 1: Write the failing test** + +`test/lib/output-toon.test.ts`: +```ts +import { test, expect, describe } from "bun:test"; +import { formatGenResultToon, genResultToData } from "../../src/lib/output.js"; + +const result = { + files: [ + { path: "src/User.ts", status: "new" as const, info: "" }, + { path: "src/User.routes.ts", status: "unchanged" as const, info: "" }, + ], + outDir: "src", dialect: "sqlite" as const, dryRun: false, warnings: [], +}; + +describe("gen TOON output (axi)", () => { + test("data has tabular files, aggregate summary, and next-step help", () => { + const d = genResultToData(result) as any; + expect(d.gen).toHaveLength(2); + expect(d.gen[0]).toEqual({ file: "src/User.ts", status: "new" }); + expect(d.summary).toContain("1 written"); // aggregate inline + expect(d.summary).toContain("1 unchanged"); + expect(Array.isArray(d.help)).toBe(true); // next-step suggestions + expect(d.help.join(" ")).toContain("tsc"); // build hint + }); + test("TOON string collapses the file array to a tabular block", () => { + const s = formatGenResultToon(result); + expect(s).toContain("gen[2]{file,status}:"); + expect(s).toContain("src/User.ts,new"); + }); + test("empty gen states the zero explicitly (axi definitive empty state)", () => { + const d = genResultToData({ ...result, files: [] }) as any; + expect(d.summary.toLowerCase()).toContain("no entities"); + }); +}); +``` + +- [ ] **Step 2: Run it; verify it fails** + +Run: `bun test test/lib/output-toon.test.ts` +Expected: FAIL — `genResultToData`/`formatGenResultToon` not exported. + +- [ ] **Step 3: Implement in `output.ts`** + +Add (reuse the existing `GenFileStatus` counts logic): +```ts +import { toonEncode } from "./format.js"; + +export function genResultToData(result: GenResultShape): { + gen: { file: string; status: GenFileStatus }[]; summary: string; help: string[]; +} { + const counts = result.files.reduce>( + (a, f) => ((a[f.status] = (a[f.status] ?? 0) + 1), a), + { new: 0, merged: 0, conflict: 0, unchanged: 0, refused: 0 }, + ); + const parts: string[] = []; + if (counts.new) parts.push(`${counts.new} written`); + if (counts.merged) parts.push(`${counts.merged} merged`); + if (counts.conflict) parts.push(`${counts.conflict} conflict`); + if (counts.unchanged) parts.push(`${counts.unchanged} unchanged`); + if (counts.refused) parts.push(`${counts.refused} refused`); + const summary = result.files.length === 0 + ? `no entities to generate in ${result.outDir}` + : parts.join(", "); + const help = result.files.length === 0 + ? ["author entities under metaobjects/ then re-run `meta gen`"] + : ["typecheck the generated code with `npx tsc`", "run schema with `meta migrate --db --slug `"]; + return { gen: result.files.map((f) => ({ file: f.path, status: f.status })), summary, help }; +} + +export function formatGenResultToon(result: GenResultShape): string { + return toonEncode(genResultToData(result)); +} +``` +`src/lib/output-json.ts`: +```ts +import type { GenResultShape } from "./output.js"; +import { genResultToData } from "./output.js"; +export function formatGenResultJson(result: GenResultShape): string { + return JSON.stringify(genResultToData(result), null, 2); +} +``` + +- [ ] **Step 4: Run the test; verify pass** + +Run: `bun test test/lib/output-toon.test.ts` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/lib/output.ts src/lib/output-json.ts test/lib/output-toon.test.ts +git commit -m "feat(cli): TOON+JSON gen formatters with axi aggregates + next-step help" +``` + +--- + +## Task 3: TOON + JSON formatters for the migrate result + +**Files:** +- Modify: `src/lib/output.ts` (add `migrateResultToData`, `formatMigrateResultToon`), `src/lib/output-json.ts` +- Test: `test/lib/output-toon.test.ts` (extend) + +**Interfaces:** +- Consumes: the migrate result shape (the input type of the existing `formatMigrateResult` — read it first). +- Produces: `migrateResultToData(result): object`, `formatMigrateResultToon(result): string`, `formatMigrateResultJson(result): string`. + +- [ ] **Step 1: Read the migrate result shape** + +Run: `sed -n '/migrate/,/^export function formatMigrateResult/p' src/lib/output.ts` (find the input interface + the `formatMigrateResult` signature). Note the field names (dialect, db/url, changes, written files, blocked entries). + +- [ ] **Step 2: Write the failing test** (extend `output-toon.test.ts`) + +```ts +import { formatMigrateResultToon, migrateResultToData } from "../../src/lib/output.js"; +test("migrate TOON: tabular changes + status + applied count", () => { + // Build a minimal result object matching the real shape read in Step 1. + const r = /* minimal MigrateResultShape: dialect sqlite, 1 create-table change, 0 blocked */; + const d = migrateResultToData(r) as any; + expect(d.changes[0]).toHaveProperty("kind"); + expect(typeof d.summary).toBe("string"); + expect(formatMigrateResultToon(r)).toContain("changes["); +}); +``` +(Fill the `r` literal from the shape read in Step 1 — do not leave it as a comment in the committed test.) + +- [ ] **Step 3: Implement `migrateResultToData` + `formatMigrateResultToon`** mirroring Task 2's structure: a tabular `changes[]{kind,object}` array, a `written[]` list, an inline `summary` (e.g. `"1 create-table, 2 add-index; applied"`), and `help[]` (`["inspect with `meta migrate status`"]` or, when blocked, the `--allow`/`--slug` next step). Add the JSON variant to `output-json.ts`. + +- [ ] **Step 4: Run the test; verify pass** + +Run: `bun test test/lib/output-toon.test.ts` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/lib/output.ts src/lib/output-json.ts test/lib/output-toon.test.ts +git commit -m "feat(cli): TOON+JSON migrate formatters with axi aggregates + next-step help" +``` + +--- + +## Task 4: Parse `--format` globally and thread it to commands + +**Files:** +- Modify: `src/index.ts` (extract `--format` like `--cwd`; pass into command dispatch), `src/commands/gen.ts`, `src/commands/migrate.ts` +- Test: `test/index-format.test.ts` (new) + +**Interfaces:** +- Consumes: `resolveFormat` (Task 1), the format functions (Tasks 2–3). +- Produces: each command receives a resolved `OutputFormat` and emits the matching string via `log.info`. + +- [ ] **Step 1: Write the failing test** (dispatch-level, capturing stdout) + +`test/index-format.test.ts`: +```ts +import { test, expect } from "bun:test"; +import { run } from "../src/index.js"; +// Run `meta gen --format toon --dry-run` in a fixture dir; capture console.log. +// Assert stdout contains a TOON header ("gen[" or "no entities") not the glyph text. +``` +(Use an existing gen fixture/project dir; if none, point `--cwd` at a minimal metaobjects/ fixture. Assert the TOON shape appears under `--format toon` and the word-table appears under `--format text`.) + +- [ ] **Step 2: Extract `--format` in `run()`** alongside the `--cwd` loop in `src/index.ts`: +```ts +let format: string | undefined; +// inside the argv loop, before pushing to cleaned: +if (a === "--format") { format = argv[++i]; continue; } +if (a.startsWith("--format=")) { format = a.slice("--format=".length); continue; } +``` +Resolve once and pass into the command calls: +```ts +import { resolveFormat } from "./lib/format.js"; +const fmt = resolveFormat(format, process.stdout.isTTY ?? false); +// e.g. return genCommand(rest, cwd, fmt); +``` +Add `--format ` to `HELP_TEXT` GLOBAL OPTIONS. + +- [ ] **Step 3: Route gen + migrate output through the format** in `src/commands/gen.ts` / `migrate.ts`: +```ts +import { formatGenResult, formatGenResultToon } from "../lib/output.js"; +import { formatGenResultJson } from "../lib/output-json.js"; +const out = + fmt === "toon" ? formatGenResultToon(result) + : fmt === "json" ? formatGenResultJson(result) + : formatGenResult(result, { isTTY: process.stdout.isTTY ?? false }); +log.info(out); +``` +(Same pattern in migrate.) + +- [ ] **Step 4: Run the test; verify pass** + +Run: `bun test test/index-format.test.ts && bun test` +Expected: PASS; full CLI suite still green. + +- [ ] **Step 5: Commit** + +```bash +git add src/index.ts src/commands/gen.ts src/commands/migrate.ts test/index-format.test.ts +git commit -m "feat(cli): --format flag (TTY-aware) routes gen/migrate through TOON/JSON/text" +``` + +--- + +## Task 5: `meta migrate` axi UX fixes + +**Files:** +- Modify: `src/commands/migrate.ts`, `package.json` (declare `@libsql/kysely-libsql`) +- Test: `test/migrate-ux.test.ts` (new) + +**Interfaces:** +- Produces: idempotent/clear baseline flow, declared sqlite driver dep, package-manager-aware error, structured error output on stdout. + +- [ ] **Step 1: Declare the sqlite driver dependency** + +Run: `cd server/typescript/packages/cli && npm pkg set dependencies.@libsql/kysely-libsql=^0.4.0 && npm install` +(Removes the "install it: `bun add ...`" runtime surprise; sqlite migrate now works out of the box.) + +- [ ] **Step 2: Write the failing tests** + +`test/migrate-ux.test.ts`: +```ts +import { test, expect } from "bun:test"; +import { run } from "../src/index.js"; +test("migrate --help prints migrate-specific usage and exits 0", async () => { + // capture console.log; run(["migrate","--help"]); assert it mentions --db/--slug/baseline and returns 0 +}); +test("re-running migrate with no changes is a no-op exit 0 with an explicit empty state", async () => { + // against an already-migrated fixture: assert exit 0 + a 'no changes' message (not an error) +}); +``` + +- [ ] **Step 3: Implement** + - **Per-subcommand `--help`:** in `migrate.ts`, if `rest` includes `--help`/`-h`, print a migrate-usage block (the MIGRATE FLAGS section) and return 0. + - **Baseline discoverability:** when the engine reports "no schema snapshot", instead of surfacing the raw error, emit a structured next-step on stdout: `help[1]: first run `meta migrate baseline --dialect `` (or auto-run baseline when safe — choose the auto path only if the engine exposes it without a destructive risk; otherwise the guided message). + - **Idempotency:** when there are no changes, return exit 0 with an explicit `migrate: no changes` empty-state (already partially true — ensure exit 0, not error). + - **PM-aware missing-dep error:** if `@libsql/kysely-libsql` is somehow absent, detect the lockfile (package-lock.json/pnpm-lock.yaml/bun.lockb) and print the matching install command, not a hardcoded `bun add`. + - **Structured errors on stdout:** migrate failures print a `{error, hint}` object in the active `--format` on stdout (exit 1), per axi. + +- [ ] **Step 4: Run tests; verify pass** + +Run: `bun test test/migrate-ux.test.ts && bun test` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/commands/migrate.ts package.json package-lock.json test/migrate-ux.test.ts +git commit -m "fix(cli): axi-conformant meta migrate (--help, idempotent, declared libsql, PM-aware errors)" +``` + +--- + +## Task 6: Per-subcommand `--help` + content-first no-args + structured errors/exit codes + +**Files:** +- Modify: `src/index.ts` (per-subcommand `--help` routing; no-args view; ensure exit codes 0/1/2) +- Test: `test/help-and-exit.test.ts` (new) + +**Interfaces:** +- Produces: `gen --help`, `verify --help`, `export --help`, `docs --help`, `prompt-snapshot --help`, `init --help` each print a focused usage block (exit 0); bare `meta` prints a content-first status + `help[]` next-steps; usage errors return 2, runtime errors return 1. + +- [ ] **Step 1: Write the failing tests** + +`test/help-and-exit.test.ts`: +```ts +import { test, expect } from "bun:test"; +import { run } from "../src/index.js"; +test("each subcommand supports --help and exits 0", async () => { + for (const c of ["gen","migrate","verify","export","docs","init"]) { + expect(await run([c, "--help"])).toBe(0); + } +}); +test("unknown command exits 2 (usage error)", async () => { + expect(await run(["bogus"])).toBe(2); +}); +``` + +- [ ] **Step 2: Implement** a small per-command help map in `index.ts` (each command's usage slice from `HELP_TEXT`), routed when `rest` includes `--help`/`-h` before the command runs; make the `default:` switch case return 2 (usage error) for unknown commands; keep bare-`meta` as a concise status + next-step `help[]` (content-first) rather than dumping the full manual. + +- [ ] **Step 3: Run tests; verify pass** + +Run: `bun test test/help-and-exit.test.ts && bun test` +Expected: PASS. + +- [ ] **Step 4: Commit** + +```bash +git add src/index.ts test/help-and-exit.test.ts +git commit -m "feat(cli): per-subcommand --help, content-first no-args, 0/1/2 exit codes (axi)" +``` + +--- + +## Task 7: Conformance pass + docs + +**Files:** +- Modify: `agent-context/skills/metaobjects-verify/references/migration.md` (document the now-discoverable baseline flow + `--format`), `agent-context/skills/metaobjects-codegen/references/typescript.md` (note `--format toon` default for agents) +- Test: full `bun test` + +- [ ] **Step 1: Run the whole CLI suite** + +Run: `cd server/typescript/packages/cli && bun test` +Expected: all green. + +- [ ] **Step 2: Update the references** to mention `--format ` (TTY-aware default) and the baseline step now surfaced by `migrate`. Re-bundle: `cd ../sdk && npm run bundle-agent-context && bun test test/agent-context/drift.test.ts`. + +- [ ] **Step 3: Commit** + +```bash +git add agent-context/skills/metaobjects-verify/references/migration.md \ + agent-context/skills/metaobjects-codegen/references/typescript.md +git commit -m "docs(agent-context): document --format + discoverable migrate baseline" +``` + +--- + +## Self-Review + +**Spec coverage (axi 10 principles):** TOON output (T1–4) ✔; minimal schemas — gen/migrate data uses ~2-field rows ✔; aggregates/totals inline (T2/T3 `summary`) ✔; definitive empty states (T2 empty test, T5 no-op) ✔; idempotent mutations + exit 0/1/2 (T5/T6) ✔; structured errors on stdout (T5/T6) ✔; no prompts — `--yes` exists, no new prompts ✔; content-first no-args (T6) ✔; next-step `help[]` (T2/T3) ✔; per-subcommand `--help` (T6) ✔. Truncation w/ size hint: **not yet needed** (gen/migrate output has no large free-text fields) — defer to any command that emits large blobs. + +**Placeholder scan:** Task 3 Step 2 and Task 4/5/6 test bodies contain `/* ... */` describing a literal/capture to fill from the shape read in the preceding step — these are explicit "fill from observed shape" steps (the exact shape/capture API can't be known until the file is read at execution), not silent TODOs. All implementation code blocks are concrete. + +**Type consistency:** `OutputFormat`, `resolveFormat`, `toonEncode`, `genResultToData`, `formatGenResultToon`, `formatGenResultJson`, `migrateResultToData`, `formatMigrateResultToon` are used consistently across `format.ts`, `output.ts`, `output-json.ts`, and the command files. Existing `GenResultShape`/`GenFileStatus` names are reused unchanged. diff --git a/docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md b/docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md index 1dc022be9..e06c8a87a 100644 --- a/docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md +++ b/docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md @@ -38,6 +38,8 @@ All five ports deliver equivalent context; a conformance corpus gates it. add only thin overlays + a delivery command. - **Not** a universal `npx` scaffolder for non-TS shops (noted as a possible later convenience; each port ships its own native command instead). + > **Superseded (2026-06-23) by ADR-0033:** the Node `meta` CLI is now the single + > agent-context assembler for all ports; non-Node CLIs redirect via a pointer stub. - **Not** documenting the adopter's *own* entities — that is the neutral-metadata-docs work above. @@ -144,6 +146,16 @@ consumer-project/ A Python+TanStack project gets `python` + `tanstack` instead. Nothing carries the languages it doesn't use. +> **Update (2026-06-23) — SUPERSEDED (deploy-all):** `references/.md` fragments +> are now deployed for **all** languages, not stack-selectively. Stack-detection misses +> (e.g. a monorepo whose root `package.json` lacks the deps — as happened to this repo's +> own init, which recorded `servers: []`) were *silently* starving agents of every +> language reference. Deploy-all is robust: the agent reads the reference matching its +> stack (`SKILL.md` points to them), and a detection miss can never leave a skill +> reference-less. The always-on AGENTS.md/CLAUDE.md stay stack-aware, and point 2's +> body→reference progressive disclosure still holds. Implemented by removing the token +> filter in `assemble.ts`; see `docs/superpowers/plans/2026-06-23-agent-context-deploy-all-references.md`. + **Context is optimized at two levels:** 1. **Scaffold-time selection** — the init command detects the stack (`package.json` deps → react/tanstack/angular; `pom.xml`/Gradle → java/kotlin; `*.csproj` → C#; diff --git a/docs/superpowers/specs/2026-06-23-agent-context-single-assembler-design.md b/docs/superpowers/specs/2026-06-23-agent-context-single-assembler-design.md new file mode 100644 index 000000000..73fd86322 --- /dev/null +++ b/docs/superpowers/specs/2026-06-23-agent-context-single-assembler-design.md @@ -0,0 +1,79 @@ +# Agent-Context: Single Assembler (collapse the per-port re-implementations) — Design + +**Date:** 2026-06-23 +**Status:** Design — decisions locked, pending spec review +**Supersedes (in part):** ADR-0027 (polyglot-docs-composition) and `2026-06-02-downstream-agent-context-design.md` — specifically the "each port ships its own native scaffolder / not a universal scaffolder" non-goal. + +## Problem + +The agent-context (the `.claude/skills/metaobjects-*` tree + the two always-on `AGENTS.md`/`CLAUDE.md` files) is assembled by **four native re-implementations** — `assemble.ts` (TS), `assemble.py` (Python), `AgentContextAssembler.java` (JVM), `AgentContextAssembler.cs` (C#) — each guarded by its own **byte-identity conformance gate** against the shared `fixtures/agent-context-conformance` goldens. + +Two changes jointly removed the justification for that: +1. **deploy-all (2026-06-23):** references are no longer stack-selected — the `skills/` + `references/` tree is now **byte-identical for every project and every port**. The *only* per-project variation is two strings (`{{stackLine}}`, `{{codegenCommand}}`) in the two always-on files. So each assembler is now "copy a static tree + interpolate two strings." +2. **ADR-0015 (2026-05-30):** schema migrations are Node-only on every port (npm primary; an optional pre-compiled binary serves Node-free shops). So **every adopter already needs the `meta` CLI (or its binary)** — the "scaffold without Node" goal that justified the native assemblers was already traded away for migrations, two days before the agent-context design was written, and never reconciled. + +Net: 4 codebases + 4 byte-exact test suites maintaining what is effectively one static artifact. Every agent-context change must be fixed and re-verified in 4 places (we hit this exact wall in CI on PR #71). + +## Decision + +**Option A — one assembler.** The Node `meta` CLI is the sole agent-context assembler. The Python, JVM, and C# ports stop assembling and embedding the agent-context entirely. Scaffolding is distributed exactly like migrations (npm primary; optional binary for Node-free shops). This is the AWS CDK / Pulumi / `protoc` model: one canonical CLI for a polyglot toolkit. + +**`agent-docs` on the non-Node CLIs — A + pointer stub.** The native implementation is deleted, but each non-Node CLI keeps a tiny **non-executing** stub: invoking `agent-docs` prints a redirect — *"Agent-context scaffolding is provided by the `meta` CLI — run `npx meta agent-docs --server `."* It never execs (so it cannot version-skew, fail on PATH, or mishandle a missing runtime — the failure modes that make a thin shell wrapper a smell). The same line goes in each port's README and `init`/help output. This preserves discoverability with zero fragility. + +**Sequencing — fold into the current branch.** This is implemented on `metaobjects-cli-axi-and-deploys` (PR #71), as deletions. The cross-port assembler *fixes* added earlier on that branch (porting deploy-all into the C#/Python assemblers, commit `e414b7a2`/`1090b972`) are **superseded by deletion** — they got CI green at the time and remain in history; the net branch diff removes those assemblers. PR #71 thus becomes Plan 1 + Plan 2 + this consolidation. + +## Architecture + +### Kept (Node/TS only — the single source of truth) +- `server/typescript/packages/sdk/src/agent-context/assemble.ts` — the sole assembler. +- The SDK bundle step (`bundle-agent-context.mjs`) + the gitignored bundled copy. +- **One** byte-identity conformance gate: `agent-context-conformance.test.ts` + `assemble.test.ts` vs `fixtures/agent-context-conformance` goldens, and `regen-agent-context-conformance.ts`. +- The Node `meta agent-docs` / `meta init` command, which already accepts `--server `/`--client ` and reads `agent-context/servers/.meta.json` for each language's `codegenCommand` (e.g. `dotnet meta gen`, `mvn meta:gen`) — so it fully serves every stack's `{{stackLine}}`/`{{codegenCommand}}`. +- Root `agent-context/` — the source of truth (unchanged). + +### Deleted — Python +- `server/python/src/metaobjects/agent_context/assemble.py` (+ the module's `assemble`/`make_stack`/`AssembledFile` surface that only served scaffolding). +- `server/python/tests/conformance/test_agent_context_conformance.py` (the byte-identity gate). +- The embedded `_content/` tree + its Hatch build hook (`hatch_build.py`) + the `content_root.py` resolver (to the extent they exist only for agent-context). +- The Python CLI `agent-docs` implementation → replaced by the pointer stub. + +### Deleted — Java/Kotlin +- `server/java/metadata/src/main/java/com/metaobjects/agentcontext/AgentContextAssembler.java` + `AssembledFile.java` (+ `AgentContextScaffold` if it only wraps assembly). +- `AgentContextConformanceTest.java` (the byte-identity gate). +- The classpath-resource bundling of `agent-context/` + `ContentRoot.java` (to the extent agent-context-only). +- The Maven `agent-docs` goal/Mojo → pointer stub (a Mojo that logs the redirect). + +### Deleted — C# +- `server/csharp/MetaObjects/AgentContext/AgentContextAssembler.cs` (+ `AssembledFile`). +- `server/csharp/MetaObjects.Conformance.Tests/AgentContextConformanceTests.cs` (the byte-identity gate) and `AgentContextStalenessTests.cs` if it depends on the embedded copy. +- The embedded-resource bundling of `agent-context/` in the `.csproj`. +- `AgentDocsCommand.cs` → replaced by the pointer stub. + +### Pointer stub (each non-Node CLI) +A minimal command handler that prints, to stdout, the redirect line and exits 0 (or a small non-zero "not-here" code — decided in the plan), referencing the port's language: `run \`npx meta agent-docs --server \``. No process exec, no embedded content, no assembly. + +### Docs / ADR +A new ADR (e.g. ADR-0033) records: agent-context scaffolding is consolidated to the Node `meta` CLI (npm primary; binary optional), consistent with ADR-0015; the per-port native assemblers are removed; non-Node CLIs redirect via a pointer stub. It supersedes the relevant non-goal in ADR-0027 / the 2026-06-02 spec. Update each port's README + `init` output with the redirect line. + +## Components & boundaries + +- **Assembler (Node):** one unit; input = (`agent-context/` content root, resolved stack); output = the file set. Unchanged, now the only one. +- **Conformance gate (Node):** one unit; assembler output == committed goldens. Replaces the four. +- **Pointer stub (×3 ports):** trivial, isolated, no dependency on agent-context content; pure constant output. Independently testable (assert the redirect string + exit code). + +## Testing +- Keep + run the single Node conformance gate (`bun test test/agent-context`) + the SDK bundle/drift test. +- After each port's deletions: run that port's build + remaining test suite to confirm nothing else referenced the removed assembler/embedded copy (`uv run pytest -q`; `dotnet test` for the C# projects; `mvn -q test` / the JVM build; `bun test` for TS). The earlier audit found **no other consumers** of the native assemblers/embedded content beyond scaffolding, so deletions should be self-contained — verify per port. +- Add a tiny per-port test asserting the pointer stub prints the redirect and exits as specified. +- Full CI on PR #71 must go green (the previously-failing `conformance (csharp|python)` jobs now have *no* agent-context conformance to run — verify the conformance workflow still passes with those suites removed, not silently skipped into a false green). + +## Risks & mitigations +- **A relies on the `meta` CLI/binary being available.** It is — npm is the primary distribution and is already mandatory for migrations (ADR-0015). If the standalone binary is *not* actually shipped today, Node-free shops still use `npx`/npm (same as migrate); the binary is the same optional escape hatch, not a new requirement. (Flagged for confirmation: is the bun-compiled binary released? It doesn't block A, but the ADR should state the real status.) +- **Discoverability loss** — mitigated by the pointer stub + README/init nudge. +- **Deletion ripple** — a port may reference the assembler/embedded copy elsewhere; the per-port build+test run catches it. The audit says no other consumers exist. +- **CI false-green** — ensure removing the per-port conformance suites doesn't leave a job that "passes" by running nothing; the conformance workflow's csharp/python legs should still execute their remaining real tests. + +## Out of scope +- The full axi + TOON treatment of the Python (Plan 3), C# (Plan 4), and Maven (Plan 5) CLIs — separate, later. (Their `agent-docs` pointer stub is in scope here; their TOON output is not.) +- Changing the agent-context *content* (skills/references) — unchanged. +- Shipping/building the standalone binary — out of scope; only documenting its status. diff --git a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/csharp.md b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/csharp.md new file mode 100644 index 000000000..d1bd3ca56 --- /dev/null +++ b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/csharp.md @@ -0,0 +1,87 @@ +# C# codegen specifics + +The C# port targets .NET consumers (EF Core + ASP.NET). Codegen runs through the +**`dotnet meta` .NET tool** — there is no Maven plugin and the Node `meta` binary +is **schema-migrations only** on the C# side (ADR-0015): `meta migrate` / +`meta verify --db` are Node-`meta`-owned; everything below is `dotnet meta`. + +## Install + +Per the always-on descriptor: + +```bash +dotnet tool install --global MetaObjects.Cli # provides `dotnet meta` +dotnet add package MetaObjects.Codegen # the codegen generators +``` + +`dotnet meta` is a .NET tool invoked as `dotnet meta ` (the underlying +command is `dotnet-meta`). + +## Run + +```bash +dotnet meta gen \ + --metadata-dir metaobjects \ + --output-dir Generated \ + --namespace Acme.Generated +dotnet meta gen --list # list registered generators +dotnet meta gen --generators entity,db-context,routes # select a subset +dotnet meta verify --codegen # codegen-drift gate (regenerate + diff vs committed) +``` + +`dotnet meta verify` defaults to `--templates` (the FR-004 prompt/template drift +gate, see the prompts reference); `--codegen` is the codegen-output drift gate. +**Schema migration + live-DB drift are NOT `dotnet meta`** — they run through the +Node `meta` tool (see the migration reference). + +## `MetaObjects.Codegen` generators + +Wire generators by their stable name (`dotnet meta gen --generators `), +or run the default set. Output lands under `--namespace` in `--output-dir`. + +| Stable name | Output | +|---|---| +| `entity` | `.g.cs` — an EF Core entity class per `object.entity` / projection: PascalCase props mapped via `[Column]`, `[Table]`, `[Key]` (or class-level `[PrimaryKey]` for composites), `[MaxLength]`/validators, nullability from `@required`. Enum fields → a nested (or shared) C# `enum`; object fields → owned-type navigations; value objects → POCOs. A TPH `@discriminator` base is emitted `abstract` with `: Base` subtypes (single-table). | +| `db-context` | one `AppDbContext` — a `DbSet` per entity + `OnModelCreating`: `.HasConversion()` (enums), `.OwnsOne(...)`/`.ToJson(...)` (owned/jsonb object fields), `.HasPrecision(p,s)` (decimals), `.UsingEntity<>(...)` (M:N), `.HasDiscriminator(e => e.Type).HasValue(...)` (TPH). | +| `routes` | `Routes.cs` — ASP.NET **Minimal API** CRUD per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). A TPH base emits polymorphic `GET /(+/{id})` + a per-subtype CRUD set at `//` (create injects the discriminator, cross-subtype get/update/delete → 404). | +| `filter-allowlist` | per-entity `FilterAllowlist` (FR-009 — the server-side field+operator allowlist the routes validate against). | +| `callable` | `.callable.g.cs` — an FR-015 calling method for a `source.rdb @kind="storedProc"|"tableFunction"`, via EF `FromSqlInterpolated` (args from the `@parameterRef` value object in declaration order). | +| `output-parser` / `extractor` / `output-prompt` / `render-helper` | the `template.output` prompt-pillar artifacts (strict parser, tolerant `extract`, output-format prompt fragment, typed render helper) — see the **prompts** reference. | +| `template` | the generic Mustache `templateGenerator()` primitive. | + +Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the +same canonical JSON every port reads — fused-key form, `source.rdb` + `@table`, +`@column` for a renamed physical column. + +## Persistence + routes are the deployed artifact + +C# generates a *complete* server stack: the entity classes + `AppDbContext` ARE the +persistence layer (EF Core), and the minimal-API routes mount on your `WebApplication`. +There is no runtime "ObjectManager" layer to wire — the generated EF Core code is +what runs. (The other ports leave a repository seam; C# does not.) + +## Extending the generators (open-for-extension, ADR-0002) + +The generators are subclassable: the per-class emit methods are `protected virtual`, +plus finer hooks — `EmitClassHeader` / `EmitClassDeclarationLine` (class declaration: +`partial`, marker interfaces), `EmitPropertyAttributes` (per-property C# attributes), +`EmitFileUsings` (extra usings), `EmitClassBodyTrailer` (extra members). Subclass a +generator and override only the seam you need rather than forking. `@default` on a +scalar emits a literal initializer; an `object.value`'s default storage is jsonb. + +**Shared + externally-provided enums (FR-019).** A package-level abstract +`field.enum` (`abstract: true`, `@values`) extended by concrete entity fields is +materialized **once** (`Enums.g.cs`) and referenced — no per-entity nested enum. +Adding `@provided: true` to that declaration suppresses materialization entirely: +consuming fields reference a hand-written/third-party enum, and the C# namespace +**binds to the enum's declaring metadata package** via +`GenConfig.PackageNamespaces[""] = "Your.Namespace"` (one entry per namespace; +`ProvidedEnumNamespace` is the single fallback). The `@values` still drive the DB +`CHECK` + validation. This replaces the retired C#-only `@csEnumType` FQN attr +(ADR-0026) — no language FQN ever lives in metadata (ADR-0001). + +## Re-scaffold this context + +`dotnet meta agent-docs --server csharp [--out ]` (re)scaffolds the slim +always-on Markdown + these `metaobjects-*` skills into the project — the C# tool +bundles the agent-context tree, so a C# consumer needs no Node `meta`. diff --git a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/python.md b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/python.md new file mode 100644 index 000000000..406277c0c --- /dev/null +++ b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/python.md @@ -0,0 +1,76 @@ +# Python codegen specifics + +The Python port targets FastAPI consumers. Codegen runs through the **`metaobjects` +console-script** (`pip install metaobjects`). As on every port, schema migrations +are **Node-`meta`-owned** (ADR-0015): `meta migrate` / `meta verify --db` run +through the Node `meta` tool — the Python CLI has **no `migrate` subcommand** and +`metaobjects verify --db` is rejected. Everything below is `metaobjects`. + +## Install + +```bash +pip install metaobjects # provides the `metaobjects` console-script +# consumer runtime deps (you provide these — codegen does not pin them): +pip install "pydantic>=2" fastapi +``` + +## Run + +```bash +metaobjects gen ./metadata --out ./generated [--package ] +metaobjects gen ./metadata --out ./generated --generators entity,routes,filter-allowlist +metaobjects verify ./metadata --codegen # codegen-drift gate (regenerate into a + # temp dir + diff vs the committed --out tree) +``` + +`metaobjects verify` defaults to `--codegen` (the codegen-output drift gate; it shares +the exact `gen` code path so the two can't diverge). `--templates` is the prompt/template +drift gate (see the prompts reference). Schema migration + live-DB drift are **not** +`metaobjects` — they run through the Node `meta` tool (see the migration reference). + +## Generators + +Wire generators by their stable name (`--generators `), or run the default set. +Output lands under `--out` (with the `@generated` guard header). Metadata is the same +canonical JSON every port reads (fused-key form, `source.rdb` + `@table`, `@column` for +a renamed physical column). + +| Stable name | Output | +|---|---| +| `entity` | one **Pydantic model** per `object.entity` / projection (the `entity-model` generator): typed fields from the metadata, nullability from `@required`, `@maxLength`/validators, enum fields → a Python `Enum`. This is the typed data model. | +| `routes` | a **FastAPI `APIRouter`** per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). The router declares a repository **`Protocol`** you implement and inject. | +| `filter-allowlist` | per-entity filter allowlist (FR-009 — the server-side field+operator allowlist the routes validate against). | +| `payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper` | the `template.output` prompt-pillar artifacts — see the **prompts** reference. | +| `template` | the generic Mustache `template` primitive. | + +## No ORM — you own persistence (unlike the C# port) + +Python codegen emits the **Pydantic models + the FastAPI routers**, but **no ORM / +persistence layer and no runnable server**. Two things you hand-write: + +1. **The repository** — each generated router depends on a repository `Protocol`; + implement it against your datastore (SQLAlchemy / asyncpg) and inject it. +2. **The app entrypoint** — there is no generated `main.py`. Create one and mount the + routers: + ```python + from fastapi import FastAPI + from generated.author_router import router as author_router + app = FastAPI() + app.include_router(author_router) + ``` + +## Known gaps (current — may require a hand-edit) + +- **Single-field, `int` PKs only.** The generated router/repository assume a single + `int` primary key (`id: int`). Non-`int` single-field PKs and composite PKs need a + hand-edit until specified. +- **DTO = `dict[str, Any]`.** Request bodies for `POST`/`PATCH`/`PUT` are typed + `dto: dict[str, Any]` and responses return `Any`; the repository `Protocol` uses + `Any` for the row type. The typed Pydantic model from the `entity` generator exists — + you can tighten the router signatures to it by hand. + +## Re-scaffold this context + +`metaobjects agent-docs --server python [--out ]` (re)scaffolds the slim always-on +Markdown + these `metaobjects-*` skills into the project — the Python tool bundles the +agent-context tree, so a Python consumer needs no Node `meta`. diff --git a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/typescript.md b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/typescript.md new file mode 100644 index 000000000..c26003554 --- /dev/null +++ b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/references/typescript.md @@ -0,0 +1,169 @@ +# TypeScript codegen specifics + +The TS port is the reference implementation, published to npm as `@metaobjectsdev/*` +packages. Codegen runs through the Node `meta` CLI (`@metaobjectsdev/cli`, binary +`meta`). + +## Contents +- Install +- `metaobjects.config.ts` +- The generators +- Run +- Multiple output targets +- Field subtype → column mapping + +## Install + +```bash +npm install --save-dev @metaobjectsdev/cli @metaobjectsdev/codegen-ts +npm install @metaobjectsdev/metadata @metaobjectsdev/runtime-ts +``` + +For the React + TanStack codegen packages, also: + +```bash +npm install --save-dev @metaobjectsdev/codegen-ts-react @metaobjectsdev/codegen-ts-tanstack +``` + +## `metaobjects.config.ts` + +Codegen is wired in a type-checked TS config at the project root. `defineConfig` +comes from `@metaobjectsdev/cli`; the generators come from their packages. + +```ts +import { defineConfig } from "@metaobjectsdev/cli"; +import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +import { formFile } from "@metaobjectsdev/codegen-ts-react"; +import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack"; + +export default defineConfig({ + outDir: "src/generated", + dialect: "postgres", // "postgres" | "sqlite" | "d1" (D1 is TS-only) + apiPrefix: "/api", // flows to routes AND client fetch URLs + columnNamingStrategy: "snake_case", // "snake_case" (default) | "literal" | "kebab-case" + timestampMode: "string", // "string" (default, ISO-8601 wire contract) | "date" (Drizzle native Date) + pluralizeCollections: true, // default; table VARS auto-pluralize (AgentConfig → agentConfigs) + collectionNameOverrides: { // per-entity escape hatch for names the rule gets wrong + AuditLog: "auditLog", LlmTierConfig: "llmTierConfig", + }, + generators: [ + entityFile(), queriesFile(), routesFile(), barrel(), + formFile(), tanstackQuery(), tanstackGrid(), + ], +}); +``` + +Naming + timestamp knobs are **codegen config**, not metadata attributes — a +collection variable name and a Drizzle column mode are per-port rendering choices +with no meaning to the other language ports, so they carry no cross-port +conformance cost. `collectionNameOverrides` wins over `pluralizeCollections` and is +applied consistently to the table declaration, every FK reference, the `relations()` +block, and the inferred types. + +A second file, `.metaobjects/config.json`, holds static project state parseable by +non-TS tooling; `meta init` scaffolds both plus the `metaobjects/` source dir. + +## The generators + +From `@metaobjectsdev/codegen-ts/generators` (server-side, framework-neutral): + +| Generator | Emits per entity | +|---|---| +| `entityFile()` | `.ts` — Drizzle table + FK `.references()` + `relations()` + inferred types + Zod insert/update schemas + `FilterAllowlist` / `SortAllowlist` | +| `queriesFile()` | `.queries.ts` — typed CRUD (`findPostById`, `listPosts`, `createPost`, `updatePost`, `deletePostById`) | +| `routesFile()` | `.routes.ts` — Fastify CRUD routes on the cross-port REST contract. `routesFileHono()` is the Hono/Workers variant | +| `barrel()` | `index.ts` re-exporting each `.ts` (one-shot, not per-entity) | +| `promptRender()` | `render()` per `template.prompt` | +| `outputParser()` | `.output.ts` (`parse*` / `safeParse*`) per `template.output` | + +## Docs — `meta docs` (one door, two surfaces) + +Documentation is NOT a `meta gen` generator. The single door is the `meta docs` +command, which emits two cross-linked **surfaces** under one output dir (default +`./docs`): + +- **model surface** (`./docs/.md`, `./docs/