feat(cli): agent-friendly meta CLI with --format/TOON output and deploy-all agent-context references#71
Merged
Merged
Conversation
…ust to stack-detection miss) The reference filter (assemble.ts) only installed references whose token was in the detected stack; when detection came up empty (as in this monorepo's own init), agents got zero language guidance. Deploy all references instead and let the agent read the one matching its stack (SKILL.md already points to them). Update the assemble tests + regenerate the conformance goldens (each stack now assembles the full 22-file set); add a regen script for the corpus. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…md gap) metaobjects-codegen shipped references for ts/java/kotlin/csharp but not python, so Python adopters got no codegen guidance. Add python.md (accurate to the actual Python port: entity->Pydantic, routes->FastAPI, no ORM, int-PK/dict[str,Any] gaps, Node-owned migrate). Regenerate conformance goldens (now 23 files/stack). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…elective) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…f 5) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…JSON/text Extract --format globally in run() alongside --cwd; resolve once with resolveFormat(flag, isTTY) and thread the OutputFormat into genCommand and migrateCommand. Each command emits formatGenResultToon/Json or the existing text formatter at the single log.info call site. Adds --format <toon|json|text> to HELP_TEXT GLOBAL OPTIONS. Tests: dispatch-level unit tests for resolveFormat selection + five integration tests against the trainer-website-meta fixture asserting TOON tabular shape, JSON parse, and text header; full suite 296 pass.
… 1 fallout) Replace stack-selective assertions (typescript.md absent) with deploy-all assertions (all language references present) in init-agent-context and init-docs-only unit tests. Test names updated to reflect the new behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…ibsql, PM-aware errors)
- Declare @libsql/kysely-libsql ^0.4.0 as a direct dep (was optional peer)
- Intercept --help/-h before strict parseMigrateArgs; print per-subcommand usage block
- Emit structured {error, hint} JSON/TOON on stdout for arg-parse failures
- Baseline discoverability: no-snapshot path emits JSON next-step hint on stdout
- PM-aware missing-dep error: detect lockfile (npm/pnpm/yarn/bun) and print matching install command
- 11 new tests in test/migrate-ux.test.ts; suite: 309 pass, 0 fail
…-detect, drop stale devDep) Thread fmt into runOfflineGenerate so no-snapshot hint uses emitStructuredError(fmt) instead of hardcoded log.info(JSON.stringify(...)). Add top-level try/catch in migrateCommand with AlreadyEmittedError sentinel so any unhandled sub-function throw emits structured error on stdout in the active format (exit 1) without double-emitting. Add yarn pm-detect test case. Remove stale @libsql/kysely-libsql from devDependencies (already in dependencies). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…xistent migrate subcommands
- Append "; run \`meta migrate baseline --dialect <dialect>\` first" to the
log.error() in the no-snapshot path so text/TTY users see the next step
(emitStructuredError is a no-op for text format; the structured hint was
already correct for json/toon).
- Replace the two fabricated help[] hints ("meta migrate status",
"meta migrate down --slug <name>") with the real rollback surface:
"roll back with \`meta migrate --rollback <target>\`".
- Extend captureCommand() in migrate-ux.test.ts to also capture stderr;
add a text-mode no-snapshot test asserting stderr contains "baseline".
- Update output-toon.test.ts help[] assertion from "status" to "--rollback".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…vs-applied-vs-dry-run state
…even with no fresh diff
…ware in CLI kysely builder
…d help, and bundled libsql driver
…rs (cross-port parity) The TS assembler was updated to deploy-all (emit every references/*.md regardless of stack); the C# and Python ports still had the per-stack token filter, so the byte-identity conformance gate failed for those ports. Changes: - server/python: drop `p.stem in stack.tokens` guard in assemble(); emit every .md in references/ sorted, matching TS deploy-all semantics. - server/csharp: drop `.Where(stack.Tokens.Contains)` from the LINQ chain in AgentContextAssembler.Assemble(); same effect. - fixtures/: regenerate conformance goldens via regen-agent-context-conformance.ts to pick up the 03bd9b5b doc additions (typescript.md +4 lines, migration.md +37 lines) that were missed when the goldens were last committed. All conformance gates pass: Python 305/305, C# 670/670, CLI 35/35.
…ementations) Option A: Node meta is the sole assembler; Python/JVM/C# stop assembling + embedding agent-context. Non-Node CLIs keep a non-executing pointer stub redirecting to 'npx meta agent-docs --server <lang>'. Reconciles with ADR-0015 (Node/binary already required for migrate). Folded into this branch as deletions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…e per-port assemblers) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…ffolder for all ports) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent-docs redirects to meta CLI - Delete assemble.py, types.py, content_root.py (the Python-port assembler) - Delete tests/conformance/test_agent_context_conformance.py (byte-identity test) - Delete src/metaobjects/agent_context/_content/ (build-time vendored content tree) - Stub _cmd_agent_docs in cli.py: prints redirect message to stderr, returns 1 - Keep scaffold.py (stripped to staleness-nudge symbols only: agent_context_staleness, Manifest, AGENT_CONTEXT_MANIFEST_PATH, installed_metaobjects_version) — used by gen/verify - Fix agent_context/__init__.py: drop assembler re-exports, keep staleness symbols - Fix cli.py imports: drop assemble/make_stack/plan_scaffold/resolve_agent_context_root - Remove dead _detect_python_server/_wire_root_doc helpers and _ROOT_DOC_* constants - Neutralise hatch_build.py: no-op hook (no more _content vendoring) - Drop _content/ from .gitignore - Remove test_agent_docs_stamps_generated_by from staleness tests (stamp is Node CLI's job now) 1288 passed (was 1294: 5 conformance fixtures + 1 stamp test removed).
…ding; agent-docs Mojo redirects to meta CLI AssembledFile and Stack are retained — AgentContextScaffold.plan() takes both as parameters and AgentContextScaffoldTest constructs them directly. Only AgentContextAssembler and ContentRoot were assembly-only; they are deleted. AgentContextConformanceTest (the byte-identity gate) is also deleted; it was never in the CI -Dtest list (confirmed absent from conformance.yml).
…ledFile/Stack) + correct staleness nudge to npx meta Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…urces; agent-docs redirects to meta CLI
… CLIs; supersede ADR-0027 non-goal
…udge → npx meta agent-docs --server python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mjBXyYKk6tbQymzhgMmdB
…DR-0033) + CLI changes
dmealing
force-pushed
the
metaobjects-cli-axi-and-deploys
branch
from
June 24, 2026 18:38
d144ea5 to
c67e80a
Compare
dmealing
added a commit
that referenced
this pull request
Jun 25, 2026
Lockstep npm release of the @metaobjectsdev/* packages (13 publish candidates). Ships the agent-friendly meta CLI (--format/TOON output, deploy-all agent-context references, #71) plus the agent-context scaffold fixes (#1, #73): the injected AGENTS.md/CLAUDE.md now name the database schema + migrations as metadata-derived and no longer guess the migration binding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LuZWKnWzYGVnESijL7uuky
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Two-part metaobjects product fix on this branch (Plan 1 + Plan 2 of a planned 5; Node CLI first, Python/C#/Maven CLIs are intentionally NOT touched yet). PLAN 1 — agent-context deploy-all: change the skill-reference assembler (sdk/src/agent-context/assemble.ts) to deploy ALL per-language references/.md fragments instead of only the detected stack's. This is a deliberate override of the prior stack-selective design: stack detection silently found nothing for monorepos (the metaobjects repo itself had servers:[]), starving agents of all language guidance; deploy-all is robust (the agent reads the reference matching its stack; SKILL.md points to them). Also authored the missing metaobjects-codegen/references/python.md (the only language without a codegen reference), added a regen script for the conformance goldens, and updated SDK + CLI init-scaffold tests to assert all-languages-present (not weakened). PLAN 2 — make the Node 'meta' CLI agent-friendly per the axi standard (axi.md) with TOON output: add --format <toon|json|text> with a deliberately TTY-aware default (human-readable text on a TTY, token-efficient TOON on a pipe/agent — intentionally NOT always-TOON, to avoid breaking humans); TOON+JSON formatters for gen and migrate sharing one data-shape function each, with axi aggregates/totals + next-step help[] lines; axi-conformant 'meta migrate' (promote @libsql/kysely-libsql from optional peer to a real dependency since sqlite migrate hard-requires it, per-subcommand --help, idempotent no-op-on-no-changes, a discoverable 'baseline' prerequisite hint, package-manager-aware missing-dep errors, and structured errors emitted on STDOUT in the active format — intentionally diverging from POSIX stderr convention because axi requires agent-parseable errors on stdout); per-subcommand --help for every command; a content-first no-args view; and 0/1/2 exit codes. Key deliberate decisions a diff-only reviewer would miss: TOON uses @toon-format/toon (the official package; the bare 'toon' is an unrelated squatter); drizzle-orm is pinned ^0.45.1 and zod ^4.4.3 to match what codegen-ts 0.11.5 actually emits (the wider peer ranges are misleading); a few migrate sub-function error paths (runBaseline/runD1Migrate) are intentionally left stderr-only with _fmt stubs as a documented follow-up; and several Minor review findings were triaged as acceptable follow-ups rather than fixed in this branch.
What Changed
metaCLI surface: a TTY-aware--format <toon|json|text>flag (human text on a TTY, token-efficient TOON on a pipe) routinggenandmigratethrough shared data-shape formatters with axi aggregates/totals and next-stephelp[]lines, plus per-subcommand--help, a content-first no-args view, and 0/1/2 exit codes. Newlib/format.ts,lib/output.ts,lib/output-json.ts, andlib/pm-detect.tsback this.meta migratefor agents: idempotent no-op when nothing changed, a discoverablebaselineprerequisite hint, package-manager-aware missing-dependency errors, structured errors emitted on stdout in the active format, and a structured summary that reflects written-vs-applied-vs-dry-run state (including applied ledger files with no fresh diff). Promoted@libsql/kysely-libsqlfrom optional peer to a real dependency and pinneddrizzle-orm/zodto the ranges codegen-ts emits.sdk/src/agent-context/assemble.ts) to deploy all per-languagereferences/<lang>.mdfragments instead of only the detected stack's, authored the previously-missingmetaobjects-codegen/references/python.md, added a conformance-goldens regen script, and updated SDK + CLI init-scaffold tests to assert all languages are present.Risk Assessment
✅ Low: The change is well-bounded and its substantive concerns were already addressed across four prior review rounds and are covered by tests; only one minor cosmetic output glitch and a doc-staleness note remain, neither affecting correctness or parseability materially.
Testing
Ran the CLI test suite (331 pass) plus the eight new/updated axi + deploy-all test files (79 pass) and the SDK assembler test (6 pass), all green. Beyond unit tests, I drove the actual
metabinary to produce reviewer-visible CLI transcripts: Plan 1 shows a freshmeta initin an empty directory deploying all five per-language reference fragments (incl. the new codegen python.md) — the monorepo case that previously starved agents; Plan 2 shows the TTY-aware--format toon|json|textoutput with axi summary/help[] lines, invalid-format rejection, the discoverable migrate baseline hint, idempotent no-op, structured errors on STDOUT, and a live-DB--applythat persisted real sqlite tables (verified by querying sqlite_master), exercising the @libsql/kysely-libsql dependency promotion. Exit codes 0/1/2 were all observed. This change is CLI/agent-facing (no rendered UI surface), so terminal transcripts and persisted DB state are the appropriate end-user evidence rather than screenshots. One scratch run hit an EEXIST on a duplicate same-second migration timestamp — a test-sequencing artifact, not a product defect — worked around with a single clean apply.Evidence: Plan 2 — axi CLI transcript (no-args, gen text/toon/json, invalid format, migrate baseline/idempotent/structured-error)
Evidence: Plan 2 — live-DB migrate: structured JSON aggregates + persisted sqlite apply (widgets + ledger)
Evidence: Plan 1 — deploy-all references: empty-dir init installs all 5 language refs incl. new python.md
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
server/typescript/packages/cli/src/commands/migrate.ts:102-AlreadyEmittedError(migrate.ts:102) is defined and handled in the top-level catch (line 440), but it is never thrown anywhere. The doc comments (lines 99-101, 438-440) describe sub-functions throwing it after calling emitStructuredError(), but runOfflineGenerate/runBaseline/runD1Migrate allreturn <code>instead. The catch branch is therefore unreachable dead code today. Consistent with the author's documented follow-up to wire structured errors into the migrate sub-functions; harmless as forward-looking scaffolding, but currently misleading.server/typescript/packages/cli/src/commands/migrate.ts:186- In json/toon format mode several migrate error paths emit only to stderr via log.error and return non-zero WITHOUT calling emitStructuredError, so an agent parsing stdout gets empty output plus a bare exit code and cannot see the failure reason: the d1 top-level checks (lines ~186/190/194), the--db <url> requiredpath (~213), and the d1/baseline sub-functions (the_fmtstubs). The author explicitly scoped runBaseline/runD1Migrate as follow-ups, but the migrateCommand-level d1/db-required branches fall in the same gap and partially undercut the axi 'parseable errors on stdout' goal. The parse-args and offline-generate paths ARE covered, so this is a coverage gap, not a regression.server/typescript/packages/cli/src/lib/format.ts:8- resolveFormat silently ignores an invalid--formatvalue (e.g.--format jsonn) and falls back to the TTY-aware default rather than erroring. On a pipe, an agent that mistypes the format would silently receive TOON instead of the JSON it requested, with no diagnostic. Consider rejecting unrecognized format values with a non-zero exit.🔧 Fix: emit migrate branch errors on stdout; reject invalid --format
3 issues (1 warning, 2 infos) still open:
server/typescript/packages/cli/src/lib/output.ts:212- migrateResultToData() derives summary purely from isBlocked, so it emits"…; applied"(and help"roll back withmeta migrate --rollback <target>") for runs that applied nothing to the DB: (1)--from-dbwithout--applyonly writes migration files; (2)--dry-runwrites nothing yet still hits this formatter (migrate.ts:388 falls through to migrate.ts:434-446); (3) even with--apply, the output prints BEFORE applyPending runs (migrate.ts:448 precedes 449), so an apply failure that flips exitCode to 1 contradicts the already-printedapplied. The text formatter intentionally avoids the word (it showsWritten:+ paths). Because the TOON/JSON output is the agent-facing contract,summary: appliedcan cause an agent to believe the DB is migrated and skip applying. The shape already carriesdryRun, and apply-state is known at the call site — thread both in so the structured summary reflects written-vs-applied.server/typescript/packages/cli/src/commands/gen.ts:30- In json/toon mode genCommand still emits all error paths (arg-parse, config-load, metadata-load, runGen failure at lines 30/49/68/70/83) only to stderr via log.error with a bare non-zero exit — no structured payload on stdout. This is the same axi 'parseable errors on stdout' gap the migrate command-level branches were just fixed for; gen's success path is structured but its failures are not. Consistent with the author scoping structured errors to migrate, so flagging only as a parallel coverage gap, not a regression.server/typescript/packages/cli/src/index.ts:238- The content-first no-args view always emits human text and ignores the resolvedfmt. An agent invoking baremetaon a pipe (where the TTY-aware default is toon) gets prose rather than structured output, a minor divergence from the axi pipe-gets-structured intent applied to gen/migrate.🔧 Fix: make migrate structured summary reflect written-vs-applied-vs-dry-run state
1 warning still open:
server/typescript/packages/cli/src/lib/output.ts:220- migrateResultToData checks!hasChangesfirst and ignoresapplied, but applyPending runs wheneverconfig.apply && exitCode===0 && !dryRun(migrate.ts:413) — NOT gated on hasChanges. As the comment at migrate.ts:409-411 documents,--applyalso applies previously-written-but-unapplied files via the ledger. Someta migrate --db <url> --applywith the schema already in sync but a committed-unapplied migration file in outDir applies that file (appliedNames non-empty) while the structured summary reportsno schema changes/metadata and schema are in sync — nothing to do. This is the inverse of the round-2 bug: the agent-facing TOON/JSON summary under-reports a real apply (only the text-mode log at migrate.ts:456-457 surfaces it). Surface the applied state ahead of the!hasChangesbranch (e.g. reportapplied N migration(s)when applied.length>0 even with no diff).🔧 Fix: surface applied ledger files in migrate summary even with no fresh diff
2 issues (1 warning, 1 info) still open:
server/typescript/packages/cli/src/lib/kysely.ts:114- The sqlite missing-dep path was upgraded to a package-manager-aware install hint via installCommand() (line 84), but the postgres path still hardcodes 'bun add pg'. This inverts the intended UX:@libsql/kysely-libsqlis now a harddependency(always present in a normal install, so its catch-branch hint is effectively dead), whilepgremains the genuinely-optional peer (peerDependenciesMeta.pg.optional=true) — i.e. the actually-missing-dep case keeps the non-PM-aware 'bun add pg' message, undercutting the stated 'package-manager-aware missing-dep errors' goal exactly where it matters. Mirror the sqlite path:const cmd = await installCommand('pg', process.cwd());and interpolate it.server/typescript/packages/cli/src/commands/migrate.ts:425- On --apply failure the structured stdout summary correctly reportsapply failed(output.ts:236-238), but the actual DB error message is emitted only to stderr via log.error and never as a structured {error,hint} on stdout in json/toon mode. An agent parsing stdout learns the apply failed but not why. This is the same axi 'parseable errors on stdout' gap the command-level branches were fixed for, now in the mid-pipeline apply path; consistent with the documented sub-function follow-up scope, flagging as a parallel coverage gap, not a regression.🔧 Fix: make postgres missing-dep hint package-manager-aware in CLI kysely builder
2 infos still open:
server/typescript/packages/cli/src/lib/output.ts:226- In theisBlockedbranch the summary is built as${changeSummary}; not applieddirectly, NOT via the guardedprefix(line 215) that every other branch uses. The ambiguous-abort path in migrateCommand (migrate.ts:335-345) constructs the result withchangeCounts: {}and a populatedambiguouslist, sochangeSummaryis empty and the agent-facing TOON/JSON summary becomes a dangling"; not applied". This path is reachable by a realmeta migrate --db <url>run with an ambiguous rename under--on-ambiguous abort. The blocked-changes path always carries non-empty changeCounts (set before emit at migrate.ts:357), which is why the existing blocked test (output-toon.test.ts:91) didn't surface it. Use the same empty-guard asprefix:${changeSummary ?${changeSummary};: ""}not applied.server/typescript/packages/cli/src/index.ts:61- The top-level HELP_TEXT migrate section still says--db <url> DB connection URL (required, or set DATABASE_URL or config), but migrate's default path is now offline snapshot generation (migrate.ts:223) and--dbis only required for live-introspect / --apply / --rollback. The per-subcommand MIGRATE_HELP_TEXT (migrate.ts:61) already states this accurately; the top-level slice is now inconsistent and overstates the requirement.✅ **Test** - passed
✅ No issues found.
cd server/typescript && bun test packages/cli— 331 pass / 2 skip / 0 fail (baseline CLI suite)bun test packages/cli/test/migrate-ux.test.ts packages/cli/test/index-format.test.ts packages/cli/test/help-and-exit.test.ts packages/cli/test/lib/output-toon.test.ts packages/cli/test/lib/format.test.ts packages/cli/test/unit/init-agent-context.test.ts packages/cli/test/unit/init-docs-only.test.ts packages/cli/test/unit/kysely-builder.test.ts— 79 pass / 0 fail (new axi + Plan-1 files)bun test packages/sdk/test/agent-context/assemble.test.ts— 6 pass / 0 fail (deploy-all assembler)Manual CLI:meta initin an empty dir, thenfind .claude/skills -path '*references*' -name '*.md'— all 5 language refs incl. new codegen python.md present (Plan 1)Manual CLI:meta(no-args),meta gen --format text|toon|json,meta gen --format yaml(exit 2),meta gen|migrate --help(Plan 2)Manual CLI:meta migrate --dialect sqlite(baseline hint, exit 2) →meta migrate --dialect sqlite --format json(structured error on stdout) →meta migrate baseline→ re-run (idempotent no-op, exit 0)Manual CLI:meta migrate --db file:app.db --from-db --apply --slug init --format json(structured TOON/JSON aggregates+help[]) then probed sqlite_master via bun:sqlite — widgets table + migration ledger persisted (exit 0)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.