Skip to content

feat: assistant-neutral agent context and per-language docs truth pass - #569

Merged
dmealing merged 8 commits into
mainfrom
fm/mo-docs-truth
Nov 13, 2026
Merged

dmealing merged 8 commits into
mainfrom
fm/mo-docs-truth

Conversation

@dmealing

@dmealing dmealing commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • Skills install for any coding assistant. meta init --skills-dir <dir> (and meta agent-docs --skills-dir <dir>) installs the metaobjects-* skills where another assistant reads them (default stays .claude/skills, e.g. .agents/skills); the choice is recorded in .metaobjects/.agent-context.json so refreshes keep it, moving the directory prunes untouched copies at the old one, and absolute or .. paths are refused. The duplicate .metaobjects/CLAUDE.md is no longer written — a refresh removes an untouched copy and names a hand-edited one — and first init on a repo with only a root CLAUDE.md also creates root AGENTS.md (a refresh only prints an advisory). The five agent-context conformance stacks and their sdk/cli tests are regenerated to match.
  • Assistant-neutral always-on file with the REST conventions. .metaobjects/AGENTS.md gains a "Calling the generated REST API" block — the filter form ?filter[<field>][<op>]=<value> (never ?status=active) with the nine operators, sort/limit/offset including each port's default when limit is omitted, and the {"error": ...} bodies — while the Java/Kotlin generator class lists, the TypeScript catalog pipeline and the prompt/report rules move behind the skills they pointed to; root wiring adds a plain pointer sentence beside the @ import, and the C#/Java/Python staleness notices no longer name .claude/skills.
  • Docs truth pass. New docs/features/language-differences.md lists what the same model does differently per port — schema migrations and database checks need the Node meta tool in every language, list default and cap, validation and filter error bodies, TypeScript-only filter extras, field names in code, generated data access, hand-edit handling — and the README, port pages, api-contract, cli and llms docs now say up front that Java/Kotlin/C#/Python teams still run Node for schema work and link to the page.

Always-on agent file size, before and after

The always-on file is .metaobjects/AGENTS.md, the text every agent session loads.

1.2.0 (published) main before this PR This PR
Files written AGENTS.md + duplicate CLAUDE.md AGENTS.md + duplicate CLAUDE.md AGENTS.md only
Size per file 17,561 bytes (TypeScript scaffold) 4,978-6,731 bytes across the five stacks 5,580-6,705 bytes across the five stacks
  • C# 4,978 -> 5,580; Java+React 5,767 -> 6,045; Java+Kotlin+React+TanStack 6,731 -> 6,705; Python 4,983 -> 5,585; TS+React+TanStack 6,207 -> 6,572 (bytes, from the conformance corpus).
  • Against 1.2.0 the per-session file is about a third of its size, and written once instead of twice. Against main, which had already shed most of the Java, Flyway and prompt detail, it grows up to 12% (Java+Kotlin shrinks slightly), because the new "Calling the generated REST API" block (filter syntax and operators, sort, paging, error shapes) is larger than the stack-specific catalogs that moved behind skills. Bytes on disk are down 44-50% because the duplicate is gone.

Also

Validation

Risk Assessment

✅ Low: The round-1 fixes (REST-block wording, Kotlin in the Schema heading, regenerated fixtures) check out against source, and the new assistant-neutral skills-install, duplicate-file removal and docs changes are well bounded and covered by behavioural tests.

Testing

The configured baseline (ts-fast + ts-unit, including the new init-agent-context tests and agent-context conformance fixtures) passed before this step; this step added the ts-slow integration lane (LOCAL CI PASSED) and drove eleven scenarios live. Init wiring: fresh init creates root AGENTS.md and CLAUDE.md with the pointer+@import block and only .metaobjects/AGENTS.md (5,960 bytes vs 17,924 in 1.2.0, Flyway moved behind the codegen skill); first init on a CLAUDE.md-only repo creates root AGENTS.md and preserves hand-written content; refresh on such a repo creates no root file and prints exactly one advisory; an AGENTS.md-only repo stays unchanged; --skills-dir installs, is remembered on refresh, moves with old files pruned, and refuses absolute/.. paths with exit 2. A real 1.2.0 project (npx @metaobjectsdev/cli@1.2.0) upgraded cleanly: untouched legacy .metaobjects/CLAUDE.md removed with root CLAUDE.md byte-identical, hand-edited legacy kept and named. REST-conventions claims hold on the real Fastify mount: no limit returns 60 rows (not a hidden page of 50), limit honored, bare ?field= gives 400 filter.bare_field naming the bracketed form, all nine operators plus sort/offset/withCount driven. "Migrations/DB checks need Node" is true at the product level: Python's metaobjects CLI has no migrate and refuses verify --db pointing at Node, dotnet meta lists no migrate and documents --db as unsupported. Hand-edit overwrite verified via the ports' own sweep tests (C# 14/14, Java 18+26). Evidence logs live in the evidence dir (index: this-round-index.md); the worktree was left clean, the throwaway test deleted, and build outputs removed. One cosmetic note recorded (empty dir husks after a skills move).

  • Live validation: ✅ go - 11 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Fresh meta init --server typescript in an empty disposable project is assistant-neutral: root AGENTS.md AND CLAUDE.md created each carrying the pointer line + @.metaobjects/AGENTS.md import, only .m… ✅ pass live s1-fresh-init.stdout.txt, s1-fresh-verify.txt, s1-rest-block.txt
First init on a repo that has only a root CLAUDE.md creates root AGENTS.md with the import block and preserves the hand-written CLAUDE.md content (the round-2 fix) ✅ pass live s2-claude-only-init.stdout.txt, s2-verify.txt
Refresh (--docs-only --refresh-docs) on a CLAUDE.md-only repo creates NO root file and prints exactly one advisory naming AGENTS.md, while still wiring the import into CLAUDE.md (the round-2 fix) ✅ pass live s3-refresh-claude-only.stdout.txt, s3-verify.txt
A repo with only root AGENTS.md is left unchanged: no CLAUDE.md invented, block appended to AGENTS.md, no advisory (boundary of the fix) ✅ pass live s4-agents-only.stdout.txt, s4-verify.txt
--skills-dir end-to-end: installs to .agents/skills (no .claude), always-on names the dir, manifest records it, refresh without the flag keeps it, refresh with a new --skills-dir writes the new locati… ✅ pass live s5-install.txt, s5-refresh-move-refuse.txt
A real 1.2.0 project (npx @metaobjectsdev/cli@1.2.0 init, 17,924-byte duplicate always-on files) upgraded by this branch: untouched legacy .metaobjects/CLAUDE.md REMOVED, .metaobjects/AGENTS.md refres… ✅ pass live s7-120-init.txt, s7-before-hashes.txt, s7-caseA-verify.txt
Same upgrade with the legacy .metaobjects/CLAUDE.md hand-edited: file KEPT with the 'no longer part of this stack but appears hand-edited' advisory and the edit intact ✅ pass live s7-caseB-verify.txt
REST-conventions claims are true of the live TypeScript HTTP layer: no ?limit returns every row (60 rows, not a hidden 50), ?limit=50 honored, bare ?<field>= gives 400 filter.bare_field naming filter[… ✅ pass live s8-nolimit-60rows.log (2 pass), s8-rest-claims-tests.log (82 pass / 0 fail)
Migrations and DB checks need Node for non-Node ports: metaobjects --help (py3.11 venv) lists gen/verify/fmt/eject/init/deps with no migrate, and metaobjects verify --db sqlite:///demo.db refuses… ✅ pass live s9-python-help.txt, s9-python-verify-db2.txt, s9-csharp-cli-help-staleness.log
Hand-edit overwrite and updated staleness notices in the ports: C# OrphanSweepTests 14/14 (incl. overwrite rebaseline), Java OrphanSweepTest 18/18 + GeneratedFileWriterTest 26/26, C# staleness 14/14,… ✅ pass live s10-csharp-orphansweep.log, s10-java-orphansweep-mvn.log, s9-java-staleness-mvn.log, s9-python-staleness-pytest.log
Complete regression for the touched surface: configured baseline ts-fast + ts-unit passed, and this step's ts-slow lane (integration-tests TS over Testcontainers + real-Postgres migrate-ts suites) rep… ✅ pass live ts-slow-this-round.log (rc=0, 9m11s), baseline ran as configured command before this step
Evidence: Evidence index for this round (scenario → result → artifact map)
# Evidence index — test round at target commit 662300036 (fm/mo-docs-truth)

All scenarios driven live against the built CLI (`server/typescript/packages/cli/dist/bin/meta.js`, v1.3.0)
and the real port toolchains, in disposable projects under `scratch/`.

| # | Scenario | Result | Evidence |
|---|----------|--------|----------|
| S1 | Fresh `meta init`: root AGENTS.md + CLAUDE.md both created with pointer+@import; `.metaobjects/AGENTS.md` only (no duplicate); 5,960 bytes (1.2.0 wrote 17,924); REST-conventions block present with corrected wording; skills at `.claude/skills` | PASS | `s1-fresh-init.stdout.txt`, `s1-fresh-verify.txt`, `s1-rest-block.txt` |
| S2 | First init on a repo with only CLAUDE.md creates root AGENTS.md with the import block; hand-written CLAUDE.md content preserved + block appended (the fix) | PASS | `s2-claude-only-init.stdout.txt`, `s2-verify.txt` |
| S3 | Refresh (`--docs-only --refresh-docs`) on CLAUDE.md-only repo: creates NO AGENTS.md, prints exactly one advisory naming AGENTS.md, still wires the import into CLAUDE.md (the fix) | PASS | `s3-refresh-claude-only.stdout.txt`, `s3-verify.txt` |
| S4 | AGENTS.md-only repo: no CLAUDE.md invented, block appended, no advisory | PASS | `s4-agents-only.stdout.txt`, `s4-verify.txt` |
| S5 | `--skills-dir .agents/skills` installs there (no `.claude`), always-on names the dir, manifest records it; refresh without flag keeps it; refresh with `--skills-dir .cursor/skills` writes new + prunes old files; `/abs` and `../` refused exit 2, nothing written outside the project | PASS | `s5-install.txt`, `s5-refresh-move-refuse.txt` |
| S6 | Real 1.2.0 project (`npx @metaobjectsdev/cli@1.2.0`) upgraded by this branch, legacy `.metaobjects/CLAUDE.md` untouched → REMOVED; `.metaobjects/AGENTS.md` 17,924→5,960 bytes; root CLAUDE.md byte-identical (md5 match) incl. hand-written section | PASS | `s7-120-init.txt`, `s7-before-hashes.txt`, `s7-caseA-verify.txt` |
| S7 | Same upgrade, legacy file hand-edited → KEPT with the "no longer part of this stack but appears hand-edited" advisory; edit intact | PASS | `s7-caseB-verify.txt` |
| S8 | REST-conventions claims on the live TS HTTP layer (real Fastify mount, in-memory libsql): no `limit` → 60 rows returned (> 50, not a hidden default page), `limit=50` honored; bare `?<field>=` → 400 `filter.bare_field` naming the bracketed form; all nine operators; sort/offset/withCount; read-only 405s | PASS | `s8-nolimit-60rows.log` (2 pass), `s8-rest-claims-tests.log` (82 pass / 0 fail across 3 files) |
| S9 | "Migrations and DB checks need Node": Python `metaobjects --help` (venv, py3.11) lists gen/verify/fmt/eject/init/deps — no migrate; `metaobjects verify --db sqlite:///demo.db` refuses: "not supported in the Python port; … the Node `meta` CLI, ADR-0015". `dotnet meta --help`: init/gen/verify/eject/fmt only, `--db` documented "NOT supported in C# (migrate engine)" | PASS | `s9-python-help.txt`, `s9-python-verify-db2.txt`, `s9-csharp-cli-help-staleness.log` |
| S10 | Hand-edit overwrite per port: C# `OrphanSweepTests` 14/14 (incl. `Overwrite_replaces_an_edited_file_and_rebaselines_it`); Java `OrphanSweepTest` 18/18 + `GeneratedFileWriterTest` 26/26; staleness notices: C# `AgentContextStalenessTests` 14/14, Python staleness+cli-nudge pytest 26 passed, Java `AgentContextStalenessTest` 19/19 | PASS | `s10-csharp-orphansweep.log`, `s10-java-orphansweep-mvn.log`, `s9-python-staleness-pytest.log`, `s9-java-staleness-mvn.log` |
| S11 | Regression: configured baseline `scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains` (ran before this step, passed); this step added `--only ts-slow`: LOCAL CI PASSED, 9m11s (integration-tests TS + real-Postgres migrate-ts suites) | PASS | `ts-slow-this-round.log` |

Notes:
- always-on Flyway mentions: 0 (moved behind `.claude/skills/metaobjects-codegen/references/`).
- README getting-started Node callout present (README.md:59, :348); no stray `pip install` claims (jinja/pydantic extras exist in pyproject; the >=3.11 requirement is documented with the venv workaround).
- The skills move prunes the old directory's FILES; empty directory husks remain on disk (files-only manifest). Cosmetic.
- Scratch projects, the 1.2.0 upgrade cases and the disposable py venv live under `scratch/` here; the worktree was left clean (`git status --porcelain` = 0 lines) and the throwaway 60-row test file was deleted after capturing its log.
Evidence: S1/S3/S5: init + skills-dir drive transcripts (fresh init verify, refresh advisory, move+prune+refusals)
=== refresh WITHOUT flag remembers .agents/skills
Scaffolded the MetaObjects agent context (52 files): .metaobjects/AGENTS.md + the metaobjects-* skills.
Re-run --docs-only --refresh-docs to update; --no-wire-root to skip the root AGENTS.md/CLAUDE.md @import.
rc=0
.claude exists? no; .agents/skills skills: 6
-- manifest skillsDir still:
9:  "skillsDir": ".agents/skills",
=== refresh WITH --skills-dir .cursor/skills moves and prunes
Scaffolded the MetaObjects agent context (52 files): .metaobjects/AGENTS.md + the metaobjects-* skills.
Re-run --docs-only --refresh-docs to update; --no-wire-root to skip the root AGENTS.md/CLAUDE.md @import.
rc=0
old .agents/skills skills left: 6; new .cursor/skills skills: 6
-- manifest skillsDir now:
9:  "skillsDir": ".cursor/skills",
=== adversarial refusals
meta: --skills-dir must be relative to the project root, got '<absolute-path>'
abs rc=2
meta: --skills-dir must stay inside the project (no '..'), got '../../escaped'
dotdot rc=2
outside artifacts? ls: cannot access '<absolute-path>': No such file or directory
-- .cursor/skills intact after refusals: 6
Evidence: S6/S7: 1.2.0→this-branch upgrade, case A (legacy removed, root md5 identical) and case B (hand-edited legacy kept + named)
-- case A stdout:
Scaffolded the MetaObjects agent context (52 files): .metaobjects/AGENTS.md + the metaobjects-* skills.
  no root AGENTS.md — assistants other than Claude Code will not load .metaobjects/AGENTS.md; add a root AGENTS.md containing @.metaobjects/AGENTS.md
Re-run --docs-only --refresh-docs to update; --no-wire-root to skip the root AGENTS.md/CLAUDE.md @import.
-- .metaobjects/CLAUDE.md exists? REMOVED
-- .metaobjects/AGENTS.md size now: 5960 (was 17924)
-- root CLAUDE.md hash: 53a3c8a143c54a3213c7a3091b311ec0 (before: 53a3c8a143c54a3213c7a3091b311ec0)
1
-- other scaffold untouched? 0 git-tracked changes (no git repo here; listing dirs instead):
CLAUDE.md
codegen
metaobjects
metaobjects.config.ts
tsconfig.codegen.json
Evidence: S8: 60-row no-limit live HTTP check (every row > 50 default; limit=50 honored)

2 pass / 0 fail — GET /subscribers without ?limit returned all 60 seeded rows; ?limit=50 returned 50

bun test v1.3.14 (0d9b296a)

 2 pass
 0 fail
 4 expect() calls
Ran 2 tests across 1 file. [1075.00ms]
Evidence: S8: full REST-conventions test run on the real Fastify mount (filter ops, bare-field 400, sort, 405s)

82 pass / 0 fail / 204 expect() across 3 files

bun test v1.3.14 (0d9b296a)

 82 pass
 0 fail
 204 expect() calls
Ran 82 tests across 3 files. [1441.00ms]
Evidence: S9: Python port CLI help (no migrate; Node owns schema) and live verify --db refusal

error: verify --db is not supported in the Python port; schema verify is the migrate engine (the Node meta CLI, ADR-0015).

error: verify --db is not supported in the Python port; schema verify is the migrate engine (the Node `meta` CLI, ADR-0015).
Evidence: S9/S10: dotnet meta --help (no migrate; --db NOT supported) + C# AgentContextStalenessTests 14/14
=== dotnet meta --help:
usage: dotnet meta <command> [options]
  commands:
    init [--root <dir>] [--with-docs]                write codegen/Generators.cs (the generator list),
                                                     codegen/Program.cs and codegen/Codegen.csproj;
                                                     --with-docs lists the api-docs generator in it
    gen [<metadataDir>] --out <dir> [--namespace <ns>] [--emit-abstract-shapes]
        [--generators <a,b,c>] [--template-root <dir>] [--strict]
                                                     run the generator list in codegen/Generators.cs;
                                                     --generators narrows it to the entries named;
                                                     --strict rejects an unknown @attr before writing
                                                     (as verify does, ADR-0023; default is a warning)
    gen --list                                       list available generators (stable names) and exit
    verify <metadataDir> [--templates [--prompts <dir>]] [--codegen --out <dir> [--namespace <ns>]] [--deps] [--db] [--lax]
                                                     drift gates (ADR-0021 D2 subverbs):
                                                       --templates  template/prompt drift (default); the
                                                                    directory is --prompts <dir> or the inline
                                                                    root and must exist; with none it reads
                                                                    prompts/ if present, else templates/
                                                       --codegen    regen-to-temp vs committed --out
                                                                    (--out defaults to csharp.outDir in
                                                                    .metaobjects/config.json, as for gen)
                                                       --namespace  codegen regen namespace (default: csharp.namespace
                                                                    in .metaobjects/config.json, else the metadata package)
                                                       --deps       dependency drift: each declared dependency's
                                                                    artifact vs .metaobjects/deps.lock.json
                                                       --db         NOT supported in C# (migrate engine)
                                                       --lax        load lax (legacy); strict-by-default
                                                                    rejects an unregistered @attr (ADR-0023)
                                                       --require-implementers
                                                                    the requirement gate runs on every verify;
                                                                    this raises a functional requirement nothing
                                                                    implements to an error (or
                                                                    META_REQUIRE_IMPLEMENTERS=1)
    eject <name>... [--force] [--root <dir>]        copy a reference generator into
                                                     codegen/generators/ to own (ADR-0034);
                                                     see `dotnet meta gen --list`
    eject --undo [<name>...] [--force] [--root <dir>]
                                                     drop owned copies identical to the shipped
                                                     generators (all of them, or the names given;
                                                     a copy that differs needs --force). It prints
                                                     the Generators.cs edit and never makes it.
                                                     There is no `doctor` command: gen's notice
                                                     and `gen --list` show identical copies
    fmt [<metadataDir>] [--check]                    rewrite metadata into canonical form (#304);
                                                     --check lists drift, exits non-zero, changes nothing
    agent-docs                                           see `npx meta agent-docs`
    --version, -v, -V                                print the version
    <command> --help                                 that command's usage
help rc=0
=== AgentContextStalenessTests:
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:    14, Skipped:     0, Total:    14, Duration: 40 ms - MetaObjects.Cli.Tests.dll (net8.0)
rc=0
Evidence: S10: C# OrphanSweepTests (hand-edit overwrite) 14/14
  Determining projects to restore...
  Restored {run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj (in 303 ms).
  5 of 6 projects are up-to-date for restore.
  MetaObjects.Render -> {run-worktree}/server/csharp/MetaObjects.Render/bin/Debug/net8.0/MetaObjects.Render.dll
  MetaObjects -> {run-worktree}/server/csharp/MetaObjects/bin/Debug/net8.0/MetaObjects.dll
  MetaObjects.Codegen -> {run-worktree}/server/csharp/MetaObjects.Codegen/bin/Debug/net8.0/MetaObjects.Codegen.dll
  MetaObjects.Cli -> {run-worktree}/server/csharp/MetaObjects.Cli/bin/Debug/net8.0/MetaObjects.Cli.dll
  MetaObjects.TestVocabulary -> {run-worktree}/server/csharp/MetaObjects.TestVocabulary/bin/Debug/net8.0/MetaObjects.TestVocabulary.dll
{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/CodegenCliComposeTests.cs(24,21): warning CS0618: 'CodegenCli.ComposeGenerators(IReadOnlyList<IGenerator>, IReadOnlyList<string>?, string)' is obsolete: 'A --generators selection only narrows the declared list since 1.1.0 (ADR-0034 Amendment 9). Use SelectDeclared; this method is kept for 1.0 callers and is no longer used by Run.' [{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj]
{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/CodegenCliComposeTests.cs(37,21): warning CS0618: 'CodegenCli.ComposeGenerators(IReadOnlyList<IGenerator>, IReadOnlyList<string>?, string)' is obsolete: 'A --generators selection only narrows the declared list since 1.1.0 (ADR-0034 Amendment 9). Use SelectDeclared; this method is kept for 1.0 callers and is no longer used by Run.' [{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj]
{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/CodegenCliComposeTests.cs(50,21): warning CS0618: 'CodegenCli.ComposeGenerators(IReadOnlyList<IGenerator>, IReadOnlyList<string>?, string)' is obsolete: 'A --generators selection only narrows the declared list since 1.1.0 (ADR-0034 Amendment 9). Use SelectDeclared; this method is kept for 1.0 callers and is no longer used by Run.' [{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj]
{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/CodegenCliComposeTests.cs(59,19): warning CS0618: 'CodegenCli.ComposeGenerators(IReadOnlyList<IGenerator>, IReadOnlyList<string>?, string)' is obsolete: 'A --generators selection only narrows the declared list since 1.1.0 (ADR-0034 Amendment 9). Use SelectDeclared; this method is kept for 1.0 callers and is no longer used by Run.' [{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj]
{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/EjectableGeneratorsTests.cs(163,9): warning xUnit2013: Do not use Assert.Equal() to check for collection size. Use Assert.Single instead. (https://xunit.net/xunit.analyzers/rules/xUnit2013) [{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj]
{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/Issue203AutoSetStampingTests.cs(192,19): warning xUnit2031: Do not use a Where clause to filter before calling Assert.Single. Use the overload of Assert.Single that accepts a filtering function. (https://xunit.net/xunit.analyzers/rules/xUnit2031) [{run-worktree}/server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj]
  MetaObjects.Codegen.Tests -> {run-worktree}/server/csharp/MetaObjects.Codegen.Tests/bin/Debug/net8.0/MetaObjects.Codegen.Tests.dll
Test run for {run-worktree}/server/csharp/MetaObjects.Codegen.Tests/bin/Debug/net8.0/MetaObjects.Codegen.Tests.dll (.NETCoreApp,Version=v8.0)
Microsoft (R) Test Execution Command Line Tool Version 17.8.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:    14, Skipped:     0, Total:    14, Duration: 584 ms - MetaObjects.Codegen.Tests.dll (net8.0)
rc=0
  • Evidence: S10: Java OrphanSweepTest 18/18 + GeneratedFileWriterTest 26/26 (surefire counts in log)
Evidence: S11: ts-slow integration lane — LOCAL CI PASSED, rc=0
metaobjects local CI  (mode: only:ts-slow)

── ▶ ts build (for integration) ──────────────────────────────────────────────
bun install v1.3.14 (0d9b296a)

Checked 564 installs across 593 packages (no changes) [112.00ms]
@metaobjectsdev/forge build: Exited with code 0
@metaobjectsdev/render build: Exited with code 0
@metaobjectsdev/metadata build: Exited with code 0
@metaobjectsdev/sdk build: bundled agent-context → {run-worktree}/server/typescript/packages/sdk/agent-context
@metaobjectsdev/runtime-web build: Exited with code 0
@metaobjectsdev/conformance build: Exited with code 0
@metaobjectsdev/docs-site build: Exited with code 0
@metaobjectsdev/sdk build: Exited with code 0
@metaobjectsdev/angular build: Exited with code 0
@metaobjectsdev/tanstack build: Exited with code 0
@metaobjectsdev/react build: Exited with code 0
@metaobjectsdev/migrate-ts build: Exited with code 0
@metaobjectsdev/runtime-ts build: Exited with code 0
@metaobjectsdev/ai-runtime build: Exited with code 0
@metaobjectsdev/codegen-ts build: Exited with code 0
@metaobjectsdev/codegen-ts-angular build: Exited with code 0
@metaobjectsdev/codegen-ts-react build: Exited with code 0
@metaobjectsdev/codegen-ts-tanstack build: Exited with code 0
@metaobjectsdev/test-generators build: Exited with code 0
@metaobjectsdev/cli build: Exited with code 0
── ✓ ts build (for integration) (14s)
  ▸ started Postgres sidecar 'metaobjects-ci-sidecar-fLF9FL'
    METAOBJECTS_TEST_PG_URL -> 127.0.0.1:41900

── ▶ migrate-ts real-PG suite ──────────────────────────────────────────────
bun install v1.3.14 (0d9b296a)

Checked 564 installs across 593 packages (no changes) [26.00ms]
bun test v1.3.14 (0d9b296a)

 1317 pass
 0 fail
 3185 expect() calls
Ran 1317 tests across 184 files. [85.15s]
── ✓ migrate-ts real-PG suite (1m26s)

── ▶ runtime-ts real-PG dialect matrix ──────────────────────────────────────────────
bun install v1.3.14 (0d9b296a)

Checked 564 installs across 593 packages (no changes) [15.00ms]
bun test v1.3.14 (0d9b296a)

 71 pass
 0 fail
 182 expect() calls
Ran 71 tests across 3 files. [3.02s]
── ✓ runtime-ts real-PG dialect matrix (3s)

── ▶ prisma real-PG round trip ──────────────────────────────────────────────
bun install v1.3.14 (0d9b296a)

Checked 564 installs across 593 packages (no changes) [11.00ms]
bun test v1.3.14 (0d9b296a)

 5 pass
 0 fail
 39 expect() calls
Ran 5 tests across 1 file. [13.87s]
── ✓ prisma real-PG round trip (14s)

── ▶ brownfield recipe real-PG check ──────────────────────────────────────────────
bun install v1.3.14 (0d9b296a)

Checked 564 installs across 593 packages (no changes) [19.00ms]
bun test v1.3.14 (0d9b296a)

 1 pass
 10 filtered out
 0 fail
 6 expect() calls
Ran 1 test across 1 file. [8.71s]
── ✓ brownfield recipe real-PG check (8s)

── ▶ integration-tests (ts) ──────────────────────────────────────────────
==> TypeScript persistence conformance
@metaobjectsdev/forge build: Exited with code 0
@metaobjectsdev/render build: Exited with code 0
@metaobjectsdev/metadata build: Exited with code 0
@metaobjectsdev/sdk build: bundled agent-context → {run-worktree}/server/typescript/packages/sdk/agent-context
@metaobjectsdev/conformance build: Exited with code 0
@metaobjectsdev/runtime-web build: Exited with code 0
@metaobjectsdev/sdk build: Exited with code 0
@metaobjectsdev/docs-site build: Exited with code 0
@metaobjectsdev/tanstack build: Exited with code 0
@metaobjectsdev/react build: Exited with code 0
@metaobjectsdev/migrate-ts build: Exited with code 0
@metaobjectsdev/angular build: Exited with code 0
@metaobjectsdev/runtime-ts build: Exited with code 0
@metaobjectsdev/ai-runtime build: Exited with code 0
@metaobjectsdev/codegen-ts build: Exited with code 0
@metaobjectsdev/codegen-ts-angular build: Exited with code 0
@metaobjectsdev/codegen-ts-react build: Exited with code 0
@metaobjectsdev/codegen-ts-tanstack build: Exited with code 0
@metaobjectsdev/test-generators build: Exited with code 0
@metaobjectsdev/cli build: Exited with code 0
bun test v1.3.14 (0d9b296a) 6x PARALLEL

 1008 pass
 0 fail
 2786 expect() calls
Ran 1008 tests across 64 files. [390.55s]
OK — all selected runners passed.
── ✓ integration-tests (ts) (6m54s)

══════════════════════ SUMMARY ══════════════════════
  ✓ ts build (for integration)  (14s)
  ✓ migrate-ts real-PG suite  (1m26s)
  ✓ runtime-ts real-PG dialect matrix  (3s)
  ✓ prisma real-PG round trip  (14s)
  ✓ brownfield recipe real-PG check  (8s)
  ✓ integration-tests (ts)  (6m54s)
── slowest steps ──
     6m54s  integration-tests (ts)
     1m26s  migrate-ts real-PG suite
       14s  prisma real-PG round trip
       14s  ts build (for integration)
        8s  brownfield recipe real-PG check
        3s  runtime-ts real-PG dialect matrix
  total wall time: 9m11s
══════════════════════════════════════════════════════
LOCAL CI PASSED
ts-slow rc=0 (task exit 0; "LOCAL CI PASSED" in log)
Evidence: S1: the always-on REST-conventions block as generated (corrected per-port wording)
## Calling the generated REST API

When the project serves the generated routes (any port):

- Filter as `?filter[<field>][<op>]=<value>`: `?filter[status][eq]=active`, never
  `?status=active` (TypeScript refuses that with 400; the other ports ignore it). Operators: `eq`, `ne`, `isNull` (`true`/`false`), `in` (comma list, at most 100), `like`
  (case-sensitive, `%` and `_`), `gt`, `gte`, `lt`, `lte`. Filters AND; only `@filterable`
  fields filter.
- `?sort=<field>:asc|desc`, `?limit=N&offset=N`; `withCount=1` returns `{"rows", "total"}`.
  Always send `limit`: without it TypeScript and C# return every row, the other ports 50.
- `PUT` is the same partial update as `PATCH`.
- Errors are `{"error": "<code>"}`: 400 for validation (only TypeScript adds `issues`) and for
  a bad filter or sort (`invalid_filter_field`, `_op`, `_value`, `invalid_sort`, each naming
  `field`), 404 `not_found`, 409 for a unique or foreign-key conflict.
  • Outcome: ⚠️ 1 info across 2 runs (2h7m37s)

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ agent-context/templates/always-on.md.mustache:46 - The new REST-conventions block states a bare ?status=active is refused by TypeScript and 'the other ports ignore it and return every row'. Verified against source: only C# returns every row (RoutesGenerator.cs:163 leaves limit null); Java, Kotlin and Python return their default page of 50 (SpringControllerGenerator.java:838 limit != null ? limit : 50; KotlinSpringControllerGenerator.kt:74; python router_generator.py:19 limit=50). The sentence contradicts the block's own limit bullet two lines later ('without it TypeScript and C# return every row, the other ports 50') and the new language-differences table, where only the C# cell says 'every row comes back'. An agent on a Java/Kotlin/Python project is told to expect the full result set. Fix: drop 'and return every row' (the per-port default is already stated by the limit bullet). Sibling sites carrying the same sentence: fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/AGENTS.md:76, java-react/.../AGENTS.md:66, csharp/.../AGENTS.md:64, python/.../AGENTS.md:65, java-kotlin-react-tanstack/.../AGENTS.md:76. Secondary wording nit in the same template+fixtures: the Schema heading (line 14) says 'Java, C# and Python projects need Node too', omitting Kotlin, the fifth port reading the same file.
  • ⚠️ docs/features/language-differences.md:48 - The 'Hand edits to a generated file' row gave the overwrite escape only to Java/Kotlin, but C# (WithHandEdits(HandEdits.Overwrite), server/csharp/MetaObjects.Codegen/CodegenRunner.cs:105) and Python (handEdits: overwrite, server/python/src/metaobjects/codegen/config.py:71) ship the same escape (docs/features/own-your-codegen.md:698-701). Applied inline: the C# cell now reads 'Refused by name, or overwritten with WithHandEdits(HandEdits.Overwrite)' and the Python cell 'Refused by name, or overwritten with handEdits: overwrite'.

🔧 Fix applied.
📝 1 wording or comment-placement note left for the author (no fix round spent):

  • ℹ️ docs/ports/java.md:25 - Inline fix: two port-page callouts began a sentence in lowercase after the bold lead-in (...Node meta tool.** the Maven plugin generates...). Capitalised 'The' in both. Wording only, no behaviour change.
⚠️ **Test** - 1 info
  • ⚠️ The complete TypeScript server test suite did not finish: cd server/typescript &amp;&amp; bun test ran past 24 minutes and was stopped at its 25-minute timeout with no exit status. Its partial log shows no failing tests, but completion is not established. The sdk (448) and cli (2026) suites pass, and the baseline ts-fast and ts-unit lanes passed. The outer executor should rerun the full server suite, or split it by directory, and record its exit status.
  • ℹ️ server/typescript/packages/cli/src/commands/init.ts:454 - In a repository that has only CLAUDE.md, init and refresh append the @.metaobjects/AGENTS.md import to CLAUDE.md alone and do not create AGENTS.md. Assistants that read only AGENTS.md (the goal of the assistant-neutral change) will not load the context in such repositories. Fresh projects are fine because both files are created. Whether to create AGENTS.md when only CLAUDE.md exists is a product-policy choice for the maintainer.
  • ℹ️ server/typescript/packages/cli/src/commands/init.ts - meta init --docs-only --refresh-docs prints 'Scaffolded the MetaObjects agent context (N files)' rather than 'Refreshed'. The docsOnly branch is checked before the refreshedDocsOnly branch, and that ordering predates this change, so the refresh message is misleading.
  • ⚠️ live validation verdict: inconclusive (11 of 14 scenarios were driven live against the product); untested: Language table: C# hand-edit overwrite via WithHandEdits(HandEdits.Overwrite), Adversarial: a 1.2.0-generated project upgraded to 1.3.0 keeps or removes its legacy .metaobjects/CLAUDE.md correctly, Complete regression: the full TypeScript server test suite (cd server/typescript && bun test)
  • Live validation: ⚠️ inconclusive - 11 of 14 scenarios driven live against the product
Scenario Result Live Evidence
S1 Fresh TypeScript init writes AGENTS.md and CLAUDE.md at the root, each importing .metaobjects/AGENTS.md, with no .metaobjects/CLAUDE.md duplicate ✅ pass live scenario-evidence.txt (S1); meta init --server typescript from the built worktree CLI in a temp project
S2 --skills-dir .agents/skills installs skills only under .agents/skills, records the choice in the manifest, and the always-on text points there ✅ pass live scenario-evidence.txt (S2/S3); /tmp project s2-java run with meta init --server java --skills-dir .agents/skills
S3 A refresh with no flag keeps the skills directory the project chose earlier ✅ pass live scenario-evidence.txt (S3); meta init --docs-only --refresh-docs run without --skills-dir
S4 Refresh over a hand-edited generated file writes .new and keeps the local edit ✅ pass live scenario-evidence.txt (S4); refresh output names .metaobjects/AGENTS.md.new and .agents/skills/metaobjects-audit/SKILL.md.new
S5 Adversarial --skills-dir: absolute path, ../ traversal and bare '.' are refused, and nothing is written outside the project ✅ pass live scenario-evidence.txt (S5); exit codes 2 with messages, temp tree shows only the project directory
S6 Root wiring is idempotent and appends to an existing CLAUDE.md without losing its content ✅ pass live S6 run in a temp project with a pre-existing CLAUDE.md; import count 1 after two runs
S7 Moving --skills-dir back to the default on refresh prunes unedited orphans and clears the manifest entry ✅ pass live scenario-evidence.txt (S7); temp project s7
S8 A bare ?status=active is refused in TypeScript and the bracketed filter syntax filters rows ✅ pass live s8-filter-syntax-bun-test.log: bun test over runtime-ts filter-mount-list.test.ts and mount-read-only.test.ts, 80 pass, 0 fail
S9 A .NET, Java or Python team can run the Node migrate and verify --db flow from a config-only project ✅ pass live scenario-evidence.txt (S9): meta init --config-only then meta migrate --from-db ... --apply, then apply-pending and verify --db, all exit 0 in a Maven-rooted temp project with a sqlite file database
S10 agent-docs for C# and Python writes the same context with --skills-dir ✅ pass live scenario-evidence.txt (S10); meta agent-docs --server csharp --server python --skills-dir .agents/skills
Language table: a Python hand-edit is refused by default and replaced under handEdits: overwrite ✅ pass live s11-python-hand-edits-pytest.log: pytest tests/codegen/test_hand_edits.py, 7 passed, parametrized keep and overwrite cases through metaobjects.cli.main(['gen'])
Language table: C# hand-edit overwrite via WithHandEdits(HandEdits.Overwrite) ⏸️ untested no Not driven. Running it needs the MetaObjects dotnet tool built from server/csharp, which requires a NuGet restore I did not attempt in this run. Doing it: dotnet build the tool offline or from a local…
Adversarial: a 1.2.0-generated project upgraded to 1.3.0 keeps or removes its legacy .metaobjects/CLAUDE.md correctly ⏸️ untested no Not driven. It needs a project generated by the base commit's CLI. I did not build the base commit, so this upgrade path is not covered.
Complete regression: the full TypeScript server test suite (cd server/typescript && bun test) ⏸️ untested no Started, but it ran 24 minutes with no completion and was killed at the 25-minute timeout. Its log has zero (fail) lines but no exit status, so completion cannot be established. The sdk (448 tests) an…
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • Baseline (given): scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains (passed)
  • bun run --filter '*' build from the worktree (all TypeScript packages built, exit 0)
  • node server/typescript/packages/cli/dist/bin/meta.js init --server typescript in a fresh temp project (S1)
  • meta init --server java --skills-dir .agents/skills, then meta init --docs-only --refresh-docs with no flag (S2/S3)
  • Local edits to .metaobjects/AGENTS.md and a skill, then --docs-only --refresh-docs: .new files written, edits preserved (S4)
  • Adversarial --skills-dir ../escaped, /absolute/path, a/../../escaped2, '.' with exit codes and a check that nothing was written outside the project (S5)
  • Re-run init to check root import idempotence; pre-existing CLAUDE.md content preserved with the import appended once (S6)
  • Moving --skills-dir from .agents/skills back to default on refresh: unedited orphans pruned, manifest skillsDir cleared (S7)
  • bun test in server/typescript/packages/runtime-ts test/drizzle-fastify/filter-mount-list.test.ts and mount-read-only.test.ts: 80 pass (S8, real Fastify routes over in-memory libsql, filter.bare_field and filter[field][eq] forms)
  • Config-only Java-rooted project: meta init --config-only, meta migrate --from-db --dialect sqlite --slug init --apply, meta migrate apply-pending --db file:other.sqlite, meta verify --db file:other.sqlite (S9, all exit 0)
  • meta agent-docs --server csharp --server python --skills-dir .agents/skills (S10)
  • python3 -m pytest server/python/tests/codegen/test_hand_edits.py: 7 pass; keep and overwrite parametrized cases drive the real metaobjects CLI main(['gen']) (S11)
  • cd server/typescript/packages/sdk && bun test: 448 tests pass
  • cd server/typescript/packages/cli && bun test: 2026 tests pass
  • Full TypeScript server suite (cd server/typescript && bun test) started; no result after 24 minutes, killed at the 25-minute limit. Partial log shows zero (fail) lines but no completion. Not established.

🔧 Fix applied.
1 info still open:

  • ℹ️ After meta init --refresh-docs --skills-dir &lt;new&gt; moves the skills install, the old directory's FILES are all pruned (documented behavior, manifest tracks files) but the empty directory husks (e.g. .agents/skills/metaobjects-audit/references/) remain on disk, which can read as 'the old copy is still there'. Cosmetic only; nothing is committed by git and no content survives.
  • Live validation: ✅ go - 11 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Fresh meta init --server typescript in an empty disposable project is assistant-neutral: root AGENTS.md AND CLAUDE.md created each carrying the pointer line + @.metaobjects/AGENTS.md import, only .m… ✅ pass live s1-fresh-init.stdout.txt, s1-fresh-verify.txt, s1-rest-block.txt
First init on a repo that has only a root CLAUDE.md creates root AGENTS.md with the import block and preserves the hand-written CLAUDE.md content (the round-2 fix) ✅ pass live s2-claude-only-init.stdout.txt, s2-verify.txt
Refresh (--docs-only --refresh-docs) on a CLAUDE.md-only repo creates NO root file and prints exactly one advisory naming AGENTS.md, while still wiring the import into CLAUDE.md (the round-2 fix) ✅ pass live s3-refresh-claude-only.stdout.txt, s3-verify.txt
A repo with only root AGENTS.md is left unchanged: no CLAUDE.md invented, block appended to AGENTS.md, no advisory (boundary of the fix) ✅ pass live s4-agents-only.stdout.txt, s4-verify.txt
--skills-dir end-to-end: installs to .agents/skills (no .claude), always-on names the dir, manifest records it, refresh without the flag keeps it, refresh with a new --skills-dir writes the new locati… ✅ pass live s5-install.txt, s5-refresh-move-refuse.txt
A real 1.2.0 project (npx @metaobjectsdev/cli@1.2.0 init, 17,924-byte duplicate always-on files) upgraded by this branch: untouched legacy .metaobjects/CLAUDE.md REMOVED, .metaobjects/AGENTS.md refres… ✅ pass live s7-120-init.txt, s7-before-hashes.txt, s7-caseA-verify.txt
Same upgrade with the legacy .metaobjects/CLAUDE.md hand-edited: file KEPT with the 'no longer part of this stack but appears hand-edited' advisory and the edit intact ✅ pass live s7-caseB-verify.txt
REST-conventions claims are true of the live TypeScript HTTP layer: no ?limit returns every row (60 rows, not a hidden 50), ?limit=50 honored, bare ?<field>= gives 400 filter.bare_field naming filter[… ✅ pass live s8-nolimit-60rows.log (2 pass), s8-rest-claims-tests.log (82 pass / 0 fail)
Migrations and DB checks need Node for non-Node ports: metaobjects --help (py3.11 venv) lists gen/verify/fmt/eject/init/deps with no migrate, and metaobjects verify --db sqlite:///demo.db refuses… ✅ pass live s9-python-help.txt, s9-python-verify-db2.txt, s9-csharp-cli-help-staleness.log
Hand-edit overwrite and updated staleness notices in the ports: C# OrphanSweepTests 14/14 (incl. overwrite rebaseline), Java OrphanSweepTest 18/18 + GeneratedFileWriterTest 26/26, C# staleness 14/14,… ✅ pass live s10-csharp-orphansweep.log, s10-java-orphansweep-mvn.log, s9-java-staleness-mvn.log, s9-python-staleness-pytest.log
Complete regression for the touched surface: configured baseline ts-fast + ts-unit passed, and this step's ts-slow lane (integration-tests TS over Testcontainers + real-Postgres migrate-ts suites) rep… ✅ pass live ts-slow-this-round.log (rc=0, 9m11s), baseline ran as configured command before this step
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains (configured baseline, ran before this step, passed)
  • scripts/ci-local.sh --only ts-slow --strict-toolchains (this step: LOCAL CI PASSED, 9m11s — integration-tests TS + real-Postgres migrate-ts)
  • node server/typescript/packages/cli/dist/bin/meta.js init [--docs-only] [--refresh-docs] [--skills-dir ...] driven in disposable projects under the evidence dir (S1–S5)
  • npx -y @metaobjectsdev/cli@1.2.0 init then refresh with this branch's CLI — 1.2.0→1.3.0 upgrade cases A and B (S6–S7)
  • bun test runtime-ts: test/drizzle-fastify/filter-mount-list.test.ts + mount-read-only.test.ts (82 pass) plus a throwaway 60-row no-limit test (2 pass, deleted after capture)
  • dotnet test MetaObjects.Codegen.Tests --filter FullyQualifiedName~OrphanSweepTests (14/14); dotnet test MetaObjects.Cli.Tests --filter AgentContextStaleness (14/14); dotnet run --project MetaObjects.Cli -- --help
  • mvn -pl codegen-base -am test -Dtest='OrphanSweepTest,GeneratedFileWriterTest' (18+26, 0 fail); mvn -pl metadata -am test -Dtest=AgentContextStalenessTest (19/19)
  • pytest tests/unit/test_agent_context_staleness.py tests/codegen/test_cli_staleness_nudge.py in a disposable py3.11 venv (26 passed); metaobjects --help; metaobjects verify --db sqlite:///demo.db (refusal captured)
🔧 **Document** - 1 issue found → auto-fixed ✅
  • ℹ️ agent-context/skills/metaobjects-codegen/references/principles.md:15 - One leftover Claude-only mention survives in the assistant-neutral agent context this change built: the shared metaobjects-codegen reference still says a project that deliberately owns its schema by hand records that rule "when the root CLAUDE.md says so, follow it" — naming only CLAUDE.md where the always-on text everywhere else now says AGENTS.md or CLAUDE.md. The same sentence is byte-copied into all five fixtures/agent-context-conformance/*/expected/.claude/skills/metaobjects-codegen/references/principles.md copies, so fixing it means editing the skill source (product output scaffolded into consumer projects) and regenerating five fixture files — both outside this documentation phase's lane (no product content or test changes). Not factually false, just incomplete relative to the neutrality intent the change's own CHANGELOG entry claims ("The text no longer tells the reader to use a root CLAUDE.md only").

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…ts, assistant-neutral agent file

Docs truth pass (1.3.0 plan PR 8):
- Every port page and the README getting-started path now say that schema
  migrations and database checks run in the Node `meta` tool.
- New docs/features/language-differences.md: one table of what differs by
  port (list default and cap, validation body, filter errors, field naming,
  data access, migrations, hand edits), linked from the port pages.
- Docs that still described .metaobjects/CLAUDE.md are corrected.

Assistant-neutral agent context (1.3.0 plan PR 36):
- `meta init --skills-dir <dir>` / `meta agent-docs --skills-dir` installs
  skills for any assistant (e.g. .agents/skills); the choice is recorded in
  the manifest and kept on refresh, and old copies are pruned on a move.
- The duplicate .metaobjects/CLAUDE.md is no longer written (pruned on
  refresh); root wiring creates AGENTS.md and CLAUDE.md importing
  .metaobjects/AGENTS.md.
- The always-on text gains a "Calling the generated REST API" block
  (filter[f][op]=v, operators, sort, paging, error shapes) and drops
  stack-specific catalogs in favour of skill pointers.
- Staleness nudges in C#, Java and Python use the neutral wording.
Resolve CHANGELOG.md (keep both [Unreleased] entries; always-on sizes updated to the final
measured values) and cli README (keep the assistant-neutral init description and main's
outputLayout sentence).
@dmealing
dmealing merged commit b0987a5 into main Nov 13, 2026
9 checks passed
@dmealing
dmealing deleted the fm/mo-docs-truth branch November 13, 2026 20:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant