Skip to content

feat(cli): add eject --undo, gen --strict and init --with-docs - #505

Merged
dmealing merged 12 commits into
mainfrom
fm/mo-generator-authoring
Oct 28, 2026
Merged

dmealing merged 12 commits into
mainfrom
fm/mo-generator-authoring

Conversation

@dmealing

@dmealing dmealing commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • Owned generators: meta eject --undo [names] [--force] (TypeScript), metaobjects eject --undo (Python), mvn metaobjects:eject -Dundo=true (Java/Kotlin) and dotnet meta eject --undo (C#) remove owned copies that match the shipped reference, plus template modules only they used. Named copies that differ block the whole call unless --force. Undo never edits config and prints the import to restore. meta gen prints one line naming unchanged owned copies. Docs state that eject --list is the drift report.
  • gen --strict (TypeScript, Python, C#) loads metadata the way verify does, so unregistered or misspelled attributes stop the run before anything is written. meta init --with-docs (TypeScript, C#) and metaobjects init --with-docs (Python) wire the docs and api-docs generators; the default scaffold is unchanged.
  • api-docs: the "Model / metadata" link is emitted only when modelBaseUrl is set (Python, C#, Java, Kotlin). TypeScript api-docs describe only what the run writes. entityTypeNames(obj) is exported from @metaobjectsdev/codegen-ts and documented (Document the type names the TypeScript generators emit per entity #490). Python codegen reads apiPrefix from metaobjects.config.yaml, default /api (Python codegen: add an apiPrefix setting instead of hard-coding /api #488). The C# eject end-to-end test sets DOTNET_USE_POLLING_FILE_WATCHER=1 so it no longer exhausts the inotify limit.

Fixes #490
Fixes #488

Fixes a flaky test on main: C# eject end-to-end

EjectEndToEndTests generated a web app whose default host builder watches appsettings.json with an inotify instance. The per-user inotify limit is shared with every other process, so when it is spent the app dies in WebApplication.CreateBuilder with an IOException that has nothing to do with the code under test. The test now sets DOTNET_USE_POLLING_FILE_WATCHER=1 for the child process; the polling watcher needs no inotify instance. Measured before and after: 2 of 10 runs failed before the change, and 10 of 10 pass after it. The Python half of this fix already merged in #499. It is its own commit (fix(test): keep the C# eject e2e off inotify).

Gate runs

This took one no-mistakes gate run (it went through review twice for fixes and one test-command re-run; the single red was the mutation step being killed by SIGTERM, exit 143, and it passed on the base commit and on re-run).

Validation

Risk Assessment

✅ Low: The round-1 and round-2 fixes check out against the code: all three ports now treat a changed owned runtime adapter as non-stock, TS entity-module gating mirrors the includeQueries path, and the C# test now asserts on the active list; remaining edges are minor and consistent with the intent.

Testing

I drove the real TypeScript, Python and C# CLIs live in disposable projects: init --with-docs, gen, gen --strict, eject and eject --undo, plus Python apiPrefix. Those scenarios passed live. The Java and Kotlin undo and notice paths were not driven by hand against the Maven plugin; they are covered only by the java, java-integ and kotlin-integ lanes' own tests, so they are recorded as untested. The full regression run (scripts/ci-local.sh --strict-toolchains) ended with "LOCAL CI PASSED" across all 11 lanes in 11m41s, per the CI log, but I did not drive it as a live product scenario, so it is also recorded as untested. The baseline (ts-fast, ts-unit) had already passed. No UI surface, so no screenshots. The worktree is clean and the temp projects are removed.

  • Live validation: ✅ go - 10 of 12 scenarios driven live against the product
Scenario Result Live Evidence
TS: meta init --with-docs then meta gen writes docs and API pages that name no symbol nothing generates ✅ pass live ts-with-entity.txt; docs/generated/api/Subscriber.md had no symbols until entityFile() was wired, then listed Model and Validation symbols
TS: unchanged owned copy produces the gen notice, and meta eject --undo removes it with its templates and runtime ✅ pass live ts-undo.txt
TS adversarial: a changed codegen/runtime adapter file keeps the generator out of the notice and out of no-name undo ✅ pass live ts-runtime-changed.txt
TS: named undo refuses a customised copy (exit 2) unless --force; a never-ejected name exits 2 ✅ pass live ts-undo.txt
TS: gen --strict stops on a typo'd attr and writes nothing, with a gen-specific hint; lenient gen warns and writes ✅ pass live ts-strict.txt
TS: entityTypeNames is exported from @metaobjectsdev/codegen-ts ✅ pass live bun import in the package returned typeof function
Python: init --with-docs wires api-docs; plain init leaves the list empty; api-docs omit the Model/metadata link ✅ pass live py-init-docs.txt
Python: apiPrefix (default, /v2, root, special chars) drives routers and api-docs; an invalid prefix is rejected ✅ pass live py-apiprefix.txt
Python: eject, gen notice, changed runtime blocks undo, unchanged undo, never-ejected exit 2, gen --strict ✅ pass live py-eject.txt, py-undo-strict.txt
C#: init --with-docs writes an active ApiDocsGenerator entry; eject --undo nosuch exits 2; the Cli tests (including undo, strict, runtime-changed and init-with-docs) pass ✅ pass live cs-init.txt, cs-live.txt, cs-cli-tests.txt (334 passed)
Java/Kotlin: owned-copy undo and notice are covered by the java, java-integ and kotlin-integ lanes of the full local CI run ⏸️ untested no Not driven live against the Maven plugin. The prior payload's evidence was the CI lane results in ci-local-full.log, which are lane tests, not a live drive, so no live result is established.
Complete regression suite (scripts/ci-local.sh --strict-toolchains) ⏸️ untested no Not driven as a live product scenario. The CI log reports LOCAL CI PASSED, but that is a test-suite run, not a live drive, so no live result is established.
Evidence: TS: runtime adapter changed keeps generator
== gen after changing runtime adapter ==
== undo no names ==
undone: []
removed: []
kept[1]{name,path,reason}:
  entity,codegen/generators/entity.ts,customised — differs from the reference
keptCompanions: []
exit=0
entity.ts
Evidence: TS: eject --undo variants
== undo unchanged ==
undone[1]:
  - name: entity
    path: codegen/generators/entity.ts
    wire:
      import: "import { entityFile } from \"@metaobjectsdev/codegen-ts/generators\";"
      replaces: "import { entityFile } from \"./codegen/generators/entity.js\";"
removed[24]: codegen/generators/entity.ts,codegen/templates/date-time-regex.ts,codegen/templates/decimal-wire-helper.ts,codegen/templates/drizzle-schema.ts,codegen/templates/entity-constants.ts,codegen/templates/entity-file.ts,codegen/templates/entity-type-names.ts,codegen/templates/enum-int-codec.ts,codegen/templates/enums-file.ts,codegen/templates/field-meta.ts,codegen/templates/filter-allowlist.ts,codegen/templates/filter-shared.ts,codegen/templates/filter-type.ts,codegen/templates/inferred-types.ts,codegen/templates/jsdoc.ts,codegen/templates/net-regex.ts,codegen/templates/projection-decl.ts,codegen/templates/relations-block.ts,codegen/templates/tph-discriminator.ts,codegen/templates/value-object-file.ts,codegen/templates/view-decl.ts,codegen/templates/zod-enum.ts,codegen/templates/zod-validators.ts,codegen/runtime/drizzle-fastify/filter-allowlist.ts
kept: []
keptCompanions: []
exit=0
0
== undo never-ejected name ==
meta: not an owned copy (nothing at codegen/generators/): queries. Nothing was removed. Run `meta eject --list` to see what you own.
exit=2
== re-eject, edit generator, named undo w/o force ==
meta: codegen/generators/entity.ts differs from the @metaobjectsdev/codegen-ts reference (0 line(s) behind it, 1 of your own), so undoing it would discard your changes. See them: diff -u node_modules/@metaobjectsdev/codegen-ts/src/reference/entity.ts codegen/generators/entity.ts
meta: Nothing was removed. Pass --force to remove a customised copy anyway.
exit=2
entity.ts
== force ==
undone[1]:
  - name: entity
    path: codegen/generators/entity.ts
    wire:
      import: "import { entityFile } from \"@metaobjectsdev/codegen-ts/generators\";"
      replaces: "import { entityFile } from \"./codegen/generators/entity.js\";"
removed[24]: codegen/generators/entity.ts,codegen/templates/date-time-regex.ts,codegen/templates/decimal-wire-helper.ts,codegen/templates/drizzle-schema.ts,codegen/templates/entity-constants.ts,codegen/templates/entity-file.ts,codegen/templates/entity-type-names.ts,codegen/templates/enum-int-codec.ts,codegen/templates/enums-file.ts,codegen/templates/field-meta.ts,codegen/templates/filter-allowlist.ts,codegen/templates/filter-shared.ts,codegen/templates/filter-type.ts,codegen/templates/inferred-types.ts,codegen/templates/jsdoc.ts,codegen/templates/net-regex.ts,codegen/templates/projection-decl.ts,codegen/templates/relations-block.ts,codegen/templates/tph-discriminator.ts,codegen/templates/value-object-file.ts,codegen/templates/view-decl.ts,codegen/templates/zod-enum.ts,codegen/templates/zod-validators.ts,codegen/runtime/drizzle-fastify/filter-allowlist.ts
kept: []
keptCompanions: []
exit=0
runtime
Evidence: TS: gen --strict
== typo attr, gen (non-strict) ==
meta: warning: ERR_UNKNOWN_ATTR: Unknown attribute '@maxLenth' on field.string 'email' — not declared by any registered provider for field.string
  in meta.subscriber.yaml at $['metadata.root'].children[0]['object.entity'].children[2]['field.string']
meta: warning: meta gen loads leniently and generated anyway, but `meta verify` rejects this metadata (ADR-0023): fix or remove the attribute — a typo'd one (`isAbstrakt`, `@requird`) silently changes what is generated. `meta gen --strict` stops the run instead.
gen[9]{file,status}:
  docs/generated/README.md,unchanged
  docs/generated/Subscriber.md,overwrite
  docs/generated/SubscriberBlurbPayload.md,unchanged
  docs/generated/subscriberBlurb.md,unchanged
exit=0
== gen --strict ==
meta: failed to load metadata: ERR_UNKNOWN_ATTR: Unknown attribute '@maxLenth' on field.string 'email' — not declared by any registered provider for field.string
  in meta.subscriber.yaml at $['metadata.root'].children[0]['object.entity'].children[2]['field.string']
meta: meta gen --strict loads strictly, as `meta verify` does (ADR-0023): every authored @attr must be declared, and nothing was written. Fix: register the attr on a metadata provider, OR move arbitrary author-supplied properties into an `attr.properties` bag, OR drop `--strict`.
exit=1
ls: cannot access 'docs': No such file or directory
== verify ==
meta: meta verify — running --codegen --templates (default). Explicit subverbs: --templates (prompt drift), --db/--dialect d1 (schema drift), --codegen (drift of everything `generators` declares, documentation included), --deps (dependency drift), --replay/--replay-snapshot (the committed migration chain replays from empty).
meta: failed to load metadata: ERR_UNKNOWN_ATTR: Unknown attribute '@maxLenth' on field.string 'email' — not declared by any registered provider for field.string
  in meta.subscriber.yaml at $['metadata.root'].children[0]['object.entity'].children[2]['field.string']
meta: meta verify is strict (ADR-0023): every authored @attr must be declared. Fix: register the attr on a metadata provider, OR move arbitrary author-supplied properties into an `attr.properties` bag, OR re-run with `meta verify --lax` to keep the legacy open-attr load.
error: "failed to load metadata: Unknown attribute '@maxLenth' on field.string 'email' — not declared by any registered provider for field.string"
code: ERR_UNKNOWN_ATTR
Evidence: TS: api-docs with entity generator wired
  src/generated/Subscriber.ts,new
  src/generated/SubscriberBlurbPayload.ts,new
  docs/generated/README.md,new
  docs/generated/Subscriber.md,new
  docs/generated/SubscriberBlurbPayload.md,new
  docs/generated/subscriberBlurb.md,new
  docs/generated/api/README.md,new
  docs/generated/api/AGENT-API.md,new
  docs/generated/api/Subscriber.md,new
  docs/generated/api/SubscriberBlurbPayload.md,new
  docs/generated/api/subscriberBlurb.md,new
summary: 11 written
warnings[1]: "1 declared template.prompt (subscriberBlurb) generated no prompt code: no prompt generator is wired, so there is no render function to send one and nothing read a reply. Add promptRender() to `generators` in metaobjects.config.ts (import it from \"@metaobjectsdev/codegen-ts/generators\"). The payload value objects were emitted regardless — those are object.value nodes, which is why this looked like it had worked."
----
<!-- @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. -->

# Subscriber API

**Model / metadata:** [Subscriber](../Subscriber.md)

> Import paths are relative to your generated-output directory.

## Model

\### `interface Subscriber`

The typed shape of a Subscriber row, generated from its metadata.

`` `ts
import { Subscriber } from "./Subscriber"
`` `

Fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `email` | `string` | yes |  |
| `name` | `string` |  |  |
| `status` | `"active" \| "paused" \| "cancelled"` | yes |  |
| `createdAt` | `string` |  |  |

## Validation

\### `SubscriberInsertSchema: ZodType`

Zod schema validating the body of a create<Subscriber> / POST request (auto-generated PKs excluded).

`` `ts
import { SubscriberInsertSchema } from "./Subscriber"
`` `

Accepted fields:
Evidence: Python: init --with-docs and gen
wrote metaobjects.config.yaml
wrote metaobjects/meta.app.yaml
next: describe your entities in metaobjects/, then run `metaobjects gen`.
# What `metaobjects gen` generates: each target names its outDir and its generators.
# `metaobjects gen --list` shows the catalog.
metadata: metaobjects
targets:
  app:
    outDir: generated
    generators: []
  docs:
    outDir: docs
    generators: [api-docs]
== gen ==
target 'app' declares no generators; nothing written
metaobjects gen: wrote 5 file(s) across 2 target(s): the api-docs pages under docs/api/python/ (a README, AGENT-API.md and three per-object pages).
Evidence: Python: apiPrefix
--- apiPrefix=[]
docs/api/python/AGENT-API.md
metaobjects gen: wrote 10 file(s) across 2 target(s).
19:from fastapi import APIRouter, Depends, Query, Request, status
43:router = APIRouter(prefix="/api/subscribers", tags=["subscribers"])
--- apiPrefix=["/v2"]
docs/api/python/AGENT-API.md
metaobjects gen: wrote 10 file(s) across 2 target(s).
19:from fastapi import APIRouter, Depends, Query, Request, status
43:router = APIRouter(prefix="/v2/subscribers", tags=["subscribers"])
docs/api/python/AGENT-API.md:15:- `GET /v2/subscribers` — list with pagination / sort / filters
docs/api/python/AGENT-API.md:16:- `GET /v2/subscribers/{subscriber_id}` — fetch one by id
docs/api/python/AGENT-API.md:17:- `POST /v2/subscribers` — create
--- apiPrefix=[""]
docs/api/python/AGENT-API.md
metaobjects gen: wrote 10 file(s) across 2 target(s).
19:from fastapi import APIRouter, Depends, Query, Request, status
43:router = APIRouter(prefix="/subscribers", tags=["subscribers"])
--- apiPrefix=["/bad prefix!"]
error: metaobjects.config.yaml: 'apiPrefix' must be "" (mount at the root) or a path starting with '/' made of letters, digits, '.', '_', '-' and '/', e.g. "/api" or "/v1"; got '/bad prefix!'.
ugrep: warning: generated: No such file or directory
--- apiPrefix=["/a/b-c_d.e"]
docs/api/python/AGENT-API.md
metaobjects gen: wrote 10 file(s) across 2 target(s).
19:from fastapi import APIRouter, Depends, Query, Request, status
43:router = APIRouter(prefix="/a/b-c_d.e/subscribers", tags=["subscribers"])
Evidence: Python: eject and notice
usage: metaobjects eject [-h] [--force] [--undo] [--root ROOT] [NAME ...]

positional arguments:
  NAME         stable generator names

options:
  -h, --help   show this help message and exit
  --force      replace an existing owned copy with the reference (with --undo:
               remove a copy that differs from it)
  --undo       drop owned copies identical to the shipped generator (all of
               them, or the names given); a copy that differs is refused
               unless --force; prints the config edit, never makes it
  --root ROOT  project root (default: cwd)
== eject entity ==
  runtime codegen/runtime/shared_predicates.py: copied — your owned generator reads this copy
  runtime codegen/runtime/fr019_shared_enum.py: copied — your owned generator reads this copy
  runtime codegen/runtime/m2m_codegen.py: copied — your owned generator reads this copy

The copies are yours: edit them freely. Keep only one of the packaged name and your copy in `generators` — wiring both runs both, and they write the same files.
codegen/__init__.py
codegen/generators/__init__.py
codegen/generators/entity.py
codegen/runtime/shared_predicates.py
codegen/runtime/fr019_shared_enum.py
codegen/runtime/m2m_codegen.py
== gen notice ==
note: owned generator copy identical to the shipped one: entity. Run `metaobjects eject --undo` to drop the copies and track the shipped generators again.
metaobjects gen: wrote 10 file(s) across 2 target(s).
== undo never-ejected ==
error: not an owned copy (nothing at codegen/generators): routes
exit=2
Evidence: Python: undo and strict
== gen with changed runtime ==
(no note above expected)
== undo (no names) w/ changed runtime ==
nothing to undo: no owned generator copy is identical to the shipped one.
exit=0
entity.py
__init__.py
== undo unchanged ==
removed codegen/generators/entity.py
  removed codegen/runtime/shared_predicates.py
  removed codegen/runtime/fr019_shared_enum.py
  removed codegen/runtime/m2m_codegen.py

metaobjects.config.yaml is not edited. In `generators`:
  replace codegen.generators.entity:entity_model with "entity"
exit=0
codegen/__init__.py
codegen/generators/__init__.py
== typo + strict ==
error: failed to load metadata:
  ERR_UNKNOWN_ATTR: Unknown attribute '@maxLenth' on field.string 'email' — not declared by any registered provider for field.string
gen --strict is strict (ADR-0023): every authored @attr must be declared. Fix: register the attr on a metadata provider, OR move arbitrary author-supplied properties into an `attr.properties` bag, OR drop `--strict` to keep the lenient load.
exit=1
ls: cannot access 'generated': No such file or directory
ls: cannot access 'docs': No such file or directory
== non-strict ==
warning: ERR_UNKNOWN_ATTR: Unknown attribute '@maxLenth' on field.string 'email' — not declared by any registered provider for field.string
  in meta.subscriber.yaml at $['metadata.root'].children[0]['object.entity'].children[2]['field.string']
warning: metaobjects gen loads leniently and generated anyway, but `metaobjects verify` rejects this metadata (ADR-0023): fix or remove the attribute - a typo'd one (`isAbstrakt`, `requird`) silently changes what is generated. Pass `--strict` to make `gen` refuse it too.
generated/Subscriber.py
Evidence: C#: init --with-docs
  wrote codegen/Generators.cs
  wrote codegen/Program.cs
  wrote codegen/Codegen.csproj

codegen/Generators.cs is the generator list `dotnet meta gen` runs. It declares nothing yet, so gen writes nothing until you add entries: `dotnet meta gen --list` is the catalog.
codegen/Generators.cs lists the api-docs generator, which writes docs/csharp/api/csharp/.
`dotnet meta gen` writes docs/csharp/; `dotnet meta verify` drift-checks it once "csharp": { "outDir": "<dir>" } is set in .metaobjects/config.json (init does not write that file); keep docs/csharp/ generated files only, verify compares the whole tree.
====
./codegen/Generators.cs
./codegen/Program.cs
./codegen/Codegen.csproj
using MetaObjects.Codegen;

public static class Generators
{
    public static IReadOnlyList<IGenerator> All { get; } =
    [
            new MetaObjects.Codegen.Generators.ApiDocsGenerator { OutDir = "docs/csharp", ProjectName = "nmcs.jcX4" },
    ];
}
Evidence: C#: live eject --undo
error: unknown or non-ejectable generator(s): nosuch. Nothing was removed. Ejectable: api-docs, callable, db-context, entity, extractor, filter-allowlist, names, output-parser, output-prompt, render-helper, report-export, requirement-tests, routes.
exit=2
unknown flag --list for `dotnet meta eject`. Valid flags: --force, --root, --undo (also accepted everywhere: --help)
usage: dotnet meta gen [<metadataDir>] --out <dir> [--namespace <ns>] [--generators <a,b,c>] [--template-root <dir>] [--emit-abstract-shapes] [--baseline default|adopt] [--strict]
  --strict: an unknown @attr is an error (exit 1, nothing written), as in verify (ADR-0023);
Evidence: C#: Cli.Tests run
  Determining projects to restore...
  All projects are up-to-date for restore.
  MetaObjects -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/csharp/MetaObjects/bin/Debug/net8.0/MetaObjects.dll
  MetaObjects.Render -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/csharp/MetaObjects.Render/bin/Debug/net8.0/MetaObjects.Render.dll
  MetaObjects.Codegen -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/csharp/MetaObjects.Codegen/bin/Debug/net8.0/MetaObjects.Codegen.dll
  MetaObjects.Cli -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/csharp/MetaObjects.Cli/bin/Debug/net8.0/MetaObjects.Cli.dll
  MetaObjects.Cli.Tests -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/csharp/MetaObjects.Cli.Tests/bin/Debug/net8.0/MetaObjects.Cli.Tests.dll
Test run for ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/csharp/MetaObjects.Cli.Tests/bin/Debug/net8.0/MetaObjects.Cli.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:   334, Skipped:     0, Total:   334, Duration: 21 s - MetaObjects.Cli.Tests.dll (net8.0)

real	0m23.459s
user	0m1.541s
sys	0m0.097s
Evidence: Full ci-local run (LOCAL CI PASSED, not live-driven)
metaobjects local CI  (mode: full — all ports + reactor + docker, 8 lanes at once)

── prelude (serial): install, drift gates, workspace build ──

── ▶ dependencies installed ──────────────────────────────────────────────
bun install v1.3.14 (0d9b296a)

Checked 686 installs across 715 packages (no changes) [9.00ms]
── ✓ dependencies installed (1s)

── ▶ doc-template drift ──────────────────────────────────────────────
synced: ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/templates/docs/entity-page.md.mustache -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/codegen-ts/templates/docs/entity-page.md.mustache
synced: ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/templates/docs/template-page.md.mustache -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/codegen-ts/templates/docs/template-page.md.mustache
synced: ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/templates/api/agent-api.md.mustache -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/codegen-ts/templates/api/agent-api.md.mustache
synced: ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/templates/api/entity-api.md.mustache -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/codegen-ts/templates/api/entity-api.md.mustache
synced: ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/templates/api/index.md.mustache -> ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/codegen-ts/templates/api/index.md.mustache
generated ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/codegen-ts/src/render-engine/embedded-templates.generated.ts (5 template(s): api/agent-api.md, api/entity-api.md, api/index.md, docs/entity-page.md, docs/template-page.md)
doc templates synced from canonical root: ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/templates (docs api)
── ✓ doc-template drift (0s)

── ▶ embedded-library drift ──────────────────────────────────────────────
wrote 6 embedded library file(s) + 2 manifest(s) to server/typescript/packages/metadata/src/library/embedded-library.generated.ts, server/java/metadata/src/main/java/com/metaobjects/library/EmbeddedLibrary.java and server/csharp/MetaObjects/Library/EmbeddedLibrary.cs
── ✓ embedded-library drift (0s)

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

Checked 686 installs across 715 packages (no changes) [8.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 → ~/.no-mistakes/worktrees/5b8c6c97e760/01M61NJ8N1XTMY90GWGGP20G0S/server/typescript/packages/sdk/agent-context
@metaobjectsdev/conformance build: Exited with code 0
@metaobjectsdev/runtime-web 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/migrate-ts build: Exited with code 0
@metaobjectsdev/tanstack build: Exited with code 0
@metaobjectsdev/react 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 (5s)
  ▸ started Postgres sidecar 'metaobjects-ci-sidecar-1474091450'
    METAOBJECTS_TEST_PG_URL -> localhost:52856 (stop it: docker rm -f metaobjects-ci-sidecar-1474091450)

── lanes (up to 8 at once; each lane's log prints when it finishes) ──
  ▸ lane java started
  ▸ lane python started
  ▸ lane mutation started
  ▸ lane csharp started
  ▸ lane ts-integ started
  ▸ lane ts started
  ▸ lane cube started
  ▸ lane gates started

╔═════════════ lane: gates ═════════════

── ▶ leak-scan (security) ──────────────────────────────────────────────
leak-scan: clean
── ✓ leak-scan (security) (0s)

── ▶ pom-version parity ──────────────────────────────────────────────
check-pom-versions: ✓ 3 parent + 1 property pom(s) match reactor 8.1.4
── ✓ pom-version parity (1s)

── ▶ bun-version parity ──────────────────────────────────────────────
check-bun-version: bun 1.3.14 matches the workflow pin.
── ✓ bun-version parity (0s)

── ▶ uv.lock version parity ──────────────────────────────────────────────
check-uv-lock-version: uv.lock agrees with pyproject.toml (1.1.4)
── ✓ uv.lock version parity (0s)

── ▶ publish-intent parity ──────────────────────────────────────────────
publish-intent parity: OK (lockstep 1.1.4; 2 declared source-only)
── ✓ publish-intent parity (0s)

── ▶ publish-set parity ──────────────────────────────────────────────
   1. @metaobjectsdev/metadata  (server/typescript/packages/metadata)
   2. @metaobjectsdev/render  (server/typescript/packages/render)
   3. @metaobjectsdev/docs-site  (server/typescript/packages/docs-site)
   4. @metaobjectsdev/migrate-ts  (server/typescript/packages/migrate-ts)
   5. @metaobjectsdev/codegen-ts  (server/typescript/packages/codegen-ts)
   6. @metaobjectsdev/runtime-ts  (server/typescript/packages/runtime-ts)
   7. @metaobjectsdev/sdk  (server/typescript/packages/sdk)
   8. @metaobjectsdev/runtime-web  (client/web/packages/runtime-web)
   9. @metaobjectsdev/codegen-ts-react  (server/typescript/packages/codegen-ts-react)
  10. @metaobjectsdev/codegen-ts-tanstack  (server/typescript/packages/codegen-ts-tanstack)
  11. @metaobjectsdev/react  (client/web/packages/react)
  12. @metaobjectsdev/tanstack  (client/web/packages/tanstack)
  13. @metaobjectsdev/cli  (server/typescript/packages/cli)
  14. @metaobjectsdev/ai-runtime  (server/typescript/packages/ai-runtime)
publish set: OK (lockstep 1.1.4; 14 packages, tier-ordered, closed over sibling deps)
ok:   happy path: private + off-lockstep excluded, order [metadata, render, docs-site, cli]
ok:   untiered member: threw on "publish order undeclared"
ok:   unclosed set: threw on "not closed over its own runtime dependencies"
ok:   inverted tier order: threw on "publishes a dependency AFTER its dependent"
ok:   regression: docs-site (#2) publishes before cli (#12) in the real tree
ok:   regression: docs-site has a declared tier

publish-set tests: all passed
── ✓ publish-set parity (0s)

── ▶ no committed pre-release version ──────────────────────────────────────────────
check-no-pr

... [4798594 bytes truncated] ...

nx-coroutines-core-jvm-1.8.0.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-compiler-runner/2.2.21/kotlin-compiler-runner-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-daemon-client/2.2.21/kotlin-daemon-client-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-compiler-embeddable/2.2.21/kotlin-scripting-compiler-embeddable-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-common/2.2.21/kotlin-scripting-common-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-jvm/2.2.21/kotlin-scripting-jvm-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-compiler-impl-embeddable/2.2.21/kotlin-scripting-compiler-impl-embeddable-2.2.21.jar -Djava.awt.headless=true -Djava.rmi.server.hostname=127.0.0.1 -Xmx4g -XX:ReservedCodeCacheSize=512m -ea -XX:+UseCodeCacheFlushing -XX:+UseParallelGC -Dkotlin.daemon.initiator.marker.file=/dev/shm/maven-tmp/kotlin-compiler-client-3502692583815880624-is-running --add-exports java.base/sun.nio.ch=ALL-UNNAMED org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath ~/.local/share/kotlin/daemon --daemon-autoshutdownIdleSeconds=7200 --daemon-shutdownDelayMilliseconds=1800000 --daemon-compilerClasspath ~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-build-tools-impl/2.2.21/kotlin-build-tools-impl-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-build-tools-api/2.2.21/kotlin-build-tools-api-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-compiler-embeddable/2.2.21/kotlin-compiler-embeddable-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-script-runtime/2.2.21/kotlin-script-runtime-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-daemon-embeddable/2.2.21/kotlin-daemon-embeddable-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-compiler-runner/2.2.21/kotlin-compiler-runner-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-daemon-client/2.2.21/kotlin-daemon-client-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-compiler-embeddable/2.2.21/kotlin-scripting-compiler-embeddable-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-common/2.2.21/kotlin-scripting-common-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-jvm/2.2.21/kotlin-scripting-jvm-2.2.21.jar:~/.m2-ci/repository/org/jetbrains/kotlin/kotlin-scripting-compiler-impl-embeddable/2.2.21/kotlin-scripting-compiler-impl-embeddable-2.2.21.jar
[INFO] #1 retrying connecting to the daemon 
[INFO] 
[INFO] --- surefire:3.5.2:test (default-test) @ metaobjects-codegen-kotlin-exposed1x-check ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.metaobjects.generator.kotlin.exposed1x.Exposed1xControllerCompileTest
[INFO] Running com.metaobjects.generator.kotlin.exposed1x.Exposed1xRoundTripTest
[INFO] Running com.metaobjects.generator.kotlin.exposed1x.Exposed1xCodegenCompileTest
w: MetaPortableColumnTypes.kt:98:79 Unchecked cast of 'Array<*>' to 'Array<Any?>'.
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.801 s -- in com.metaobjects.generator.kotlin.exposed1x.Exposed1xRoundTripTest
w: MetaPortableColumnTypes.kt:98:79 Unchecked cast of 'Array<*>' to 'Array<Any?>'.
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.414 s -- in com.metaobjects.generator.kotlin.exposed1x.Exposed1xCodegenCompileTest
w: MetaPortableColumnTypes.kt:98:79 Unchecked cast of 'Array<*>' to 'Array<Any?>'.
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.93 s -- in com.metaobjects.generator.kotlin.exposed1x.Exposed1xControllerCompileTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- jacoco:0.8.13:report (report) @ metaobjects-codegen-kotlin-exposed1x-check ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  20.143 s
[INFO] Finished at: 2026-10-28T15:24:31-04:00
[INFO] ------------------------------------------------------------------------
OK — all selected runners passed.
── ✓ integration-tests (kotlin) (7m02s)
╚═════════════ lane: kotlin-integ — ✓ 7m02s ═════════════

══════════════════════ SUMMARY ══════════════════════
  ✓ dependencies installed  (1s)
  ✓ doc-template drift
  ✓ embedded-library drift
  ✓ ts build  (5s)
  ✓ java-reactor (install)  (4m30s)
  ✓ conformance: python  (2m43s)
  ✓ integration-tests (python)  (1m45s)
  ✓ completeness-gate (mutation)  (8m48s)
  ✓ conformance: csharp  (2m12s)
  ✓ integration-tests (csharp)  (8m27s)
  ✓ migrate-ts real-PG suite  (54s)
  ✓ runtime-ts real-PG dialect matrix  (2s)
  ✓ prisma real-PG round trip  (11s)
  ✓ integration-tests (ts)  (4m54s)
  ✓ ts typecheck  (27s)
  ✓ conformance: typescript  (5m26s)
  ✓ ts unit suites  (26s)
  ✓ integration-tests (kotlin)  (7m02s)
  ✓ integration-tests (java)  (3m14s)
  ✓ showcase current in all ports  (30s)
  ✓ cube-model live check (Cube + Postgres)  (3m17s)
  ✓ leak-scan (security)
  ✓ pom-version parity  (1s)
  ✓ bun-version parity
  ✓ uv.lock version parity
  ✓ publish-intent parity
  ✓ publish-set parity
  ✓ no committed pre-release version
  ✓ script-name hook collisions
  ✓ ci lane selection
  ✓ extract field kinds
  ✓ no-magic gate wired (5 ports)  (2s)
  ✓ test-file references resolve  (1s)
  ✓ metamodel-version bump
  ✓ positioning claims
  ✓ hosted assess prompt builds
  ✓ migration guides vs registry
  ✓ peer-range bounds
  ✓ owned template copies current
  ✓ reference templates lint  (1s)
  ✓ shipped doc examples load  (3s)
  ✓ no retired $apiPrefix
  ✓ site payload is true  (44s)
  ✓ site reference is fresh  (1s)
  ✓ release tag gate  (22s)
  ✓ scripts/ typecheck  (7s)
  ✓ requirements ledger verifies  (5s)
  ✓ requirements cover vocabulary  (1s)
  ✓ metamodel scaffolder  (1s)
  ✓ requirement harness  (1s)
  ✓ fixture-lint
── slowest steps ──
     8m48s  completeness-gate (mutation)
     8m27s  integration-tests (csharp)
     7m02s  integration-tests (kotlin)
     5m26s  conformance: typescript
     4m54s  integration-tests (ts)
     4m30s  java-reactor (install)
     3m17s  cube-model live check (Cube + Postgres)
     3m14s  integration-tests (java)
     2m43s  conformance: python
     2m12s  conformance: csharp
     1m45s  integration-tests (python)
       54s  migrate-ts real-PG suite
── lanes (wall time, ran 8 at once) ──
    10m39s  csharp
     8m48s  mutation
     7m02s  kotlin-integ
     6m19s  ts
     6m01s  ts-integ
     4m30s  java
     4m28s  python
     3m17s  cube
     3m14s  java-integ
     1m30s  gates
       30s  showcase
  total wall time: 11m41s
══════════════════════════════════════════════════════
LOCAL CI PASSED
  • Outcome: ⚠️ 2 infos across 2 runs (2h36m40s)

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (2) ✅
  • ⚠️ server/typescript/packages/cli/src/commands/eject.ts:602 - In TypeScript, C# and Java, the 'unchanged copy' check ignores the owned HTTP/helper runtime adapter. Python includes it. Failure path in TypeScript: the project owns entity (or routes/routes-hono), the generator file is identical to the reference, and the user changed a file under codegen/runtime/. generatorListGroups (eject.ts:445-488) folds template status into the row but never runtime status, so isStockRow returns true. meta gen then tells the user the copy is 'the shipped reference, unchanged', and meta eject --undo with no names removes the generator. The changed adapter file goes into keptCompanions and stays on disk. But the packaged generator imports @metaobjectsdev/runtime-ts, so the user's adapter change silently stops reaching the generated code. own-your-codegen.md:180-188 says to keep the owned generator in exactly this case ('Stop here for that generator'). It also contradicts the docs claim that 'A copy you changed is never touched unless you name it and pass the force flag'. Sibling sites with the same gap: server/csharp/MetaObjects.Cli/OwnedCopy.cs:133 (IdenticalCopies uses Status, which covers companions but not RuntimeStatus); server/java/maven-plugin/src/main/java/com/metaobjects/mojo/OwnedStock.java:70 (identical() explicitly excludes the helper runtime, which eject rewired via RUNTIME_PACKAGE). Related partial-removal path: eject.ts:733 and server/csharp/MetaObjects.Cli/EjectCommand.cs:359 delete the identical runtime files and keep the changed ones, which leaves a half-copied adapter tree (C# also prints 'The helper runtime copy is gone'). Fix: treat a generator that reaches runtime (ejectsRuntime / HelperRuntime.UsedBy / entry.runtime) as non-stock whenever its owned runtime copy differs, for both the notice and no-name undo, matching Python's identical_owned.
  • ⚠️ server/typescript/packages/codegen-ts/src/generators/api-model.ts:469 - The TypeScript api-docs now leave out queries and REST routes when no generator writes them. They still document the entity module (&lt;Entity&gt; model, the Insert/Update Zod schemas from validationSymbols, relations()) whatever the generator list holds. The new meta init --with-docs scaffold wires only docsFile() and apiDocsFile(), so its first meta gen writes API pages that name src/generated/&lt;Entity&gt;.ts exports nothing generates. That is the same class of defect this change fixes for queries. The CHANGELOG line 'TypeScript API docs describe only what the run writes' (CHANGELOG.md:222) overstates the fix. The intent scoped item 4 to queries and routes, so gating the entity-module symbols on an emitsEntity-style marker extends scope. The alternative is to narrow the CHANGELOG wording. Asking which one the author wants.
  • ℹ️ docs/ports/python.md:177 - The docs said metaobjects gen --strict prints 'verify's message and hint'. Per the recorded decision, the hint is gen-specific (drop --strict). Reworded to 'verify's message and a gen-specific hint (drop --strict)'.
  • ℹ️ docs/ports/csharp.md:235 - The docs said dotnet meta gen --strict prints 'the same ERR_UNKNOWN_ATTR lines and hint' as verify. It prints GenStrictHint, not verify's --lax hint. Reworded to 'the same ERR_UNKNOWN_ATTR lines with a gen-specific hint (drop --strict)'.

🔧 Fix applied.
2 issues (1 warning, 1 info) still open:

  • ⚠️ server/csharp/MetaObjects.Cli.Tests/InitWithDocsTests.cs:20 - With_docs_lists_the_api_docs_generator_and_says_how_it_is_drift_checked cannot fail when --with-docs does nothing. The scaffold template always contains the commented example // new MetaObjects.Codegen.Generators.ApiDocsGenerator { OutDir = &#34;docs/csharp&#34;, ProjectName = &#34;shop&#34; }, (InitCommand.cs:84). The first assertion, Assert.Contains(&#34;new MetaObjects.Codegen.Generators.ApiDocsGenerator { OutDir = \&#34;docs/csharp\&#34;&#34;, List), is a substring match, so it passes against that comment even if withDocs adds no entry. The second assertion strips that exact comment line and then asserts the // new ... OutDir prefix is absent. That is true by construction, and the ProjectName = &#34;shop&#34; text in the Replace never matches the real temp-dir name anyway. The assertions on the printed lines only check for the words "csharp" and "outDir", which the guidance text always contains. A plausible wrong behavior, InitCommand.Run(root, withDocs: true) passing [] to WriteMissing, would still pass. Fix: assert the entry appears in the active list, for example on the text after public static IReadOnlyList&lt;IGenerator&gt; All, or as a line that does not start with //. Assert it is absent from the same region when withDocs is false. The sibling test Without_the_flag_the_list_is_unchanged is fine. The TypeScript and Python --with-docs tests parse real output or run gen, so they are fine.
  • ℹ️ docs/llms/llms.txt:53 - The intent says the llms.txt copy no longer "leans on 'spine'". The lede and the guaranteed-versus-yours paragraph were rewritten. Two metaphorical uses remain: line 53, "capabilities of the same metadata spine", and line 108, "on one spine". The other hits are proper names: the "contract spine" assessment scope on line 21 and the essay title on line 145. Reword lines 53 and 108 in plain prose.

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

⚠️ **Test** - 2 infos
  • 🚨 The configured test command failed once with exit code 1. The failure was the mutation gate being killed under memory pressure (script conformance:mutation exited 143, SIGTERM); every test suite in that run reported 0 failures, the command passes on the base commit, and the re-run on the same head passed (LOCAL CI PASSED).
  • Live validation: ✅ go - 11 of 13 scenarios driven live against the product
Scenario Result Live Evidence
meta init --with-docs wires docsFile() and apiDocsFile() and a docs target ✅ pass live ts-init-with-docs.txt
meta gen names unchanged owned copies in one notice line ✅ pass live ts-eject-gen.txt: '2 owned generator copies are the shipped reference, unchanged (entity, queries)'
meta gen --strict stops on an unregistered attribute and writes nothing, with a gen-specific hint ✅ pass live ts-strict-undo.txt: exit 1, no docs dir written, hint says drop --strict
meta gen --strict succeeds on valid metadata ✅ pass live ts-strict-undo.txt: 5 written, exit 0
eject --undo of a never-ejected name exits 2 and removes nothing ✅ pass live ts-strict-undo.txt: exit=2
eject --undo of a customised copy is refused without --force and removes nothing ✅ pass live ts-strict-undo.txt: exit=2, both generators still on disk
eject --undo with no names removes only unchanged copies and their templates, keeps customised ones, prints the restore import ✅ pass live ts-strict-undo.txt
eject --undo <name> --force removes a customised copy and its templates ✅ pass live ts-strict-undo.txt: 24 files removed, exit 0
TS api-docs with docs-only generators describe no queries, routes or entity-module symbols ✅ pass live ts-apidocs-typenames.txt: zero matches in the generated API pages
entityTypeNames exported from codegen-ts returns the documented type names ✅ pass live ts-apidocs-typenames.txt: row=Item, insert=ItemInsert, update=ItemUpdate, create=ItemCreate
Python metaobjects init --with-docs scaffolds a docs target with api-docs ✅ pass live py-init.txt
Python apiPrefix honored by routers and api-docs ⏸️ untested no Prior payload established only unit-test coverage (32 passed in py-apiprefix-tests.txt); no live Python server was booted, so no live result is supported.
C#, Java and Kotlin eject --undo, gen --strict and notice behaviors ⏸️ untested no Not driven live here; dotnet and Maven toolchains were not exercised, and the ci-local lanes for them were outside the configured test command. Run `scripts/ci-local.sh --only csharp --only java-fast…
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains (exit 0: build+typecheck, TS conformance, TS unit suites, mutation gate)
  • meta init --with-docs in a scratch project
  • meta eject entity queries then meta gen (unchanged-copy notice)
  • meta gen --strict with an unregistered attribute, then with valid metadata
  • meta eject --undo nosuch, --undo entity on a customised copy, --undo with no names, --undo entity --force
  • API pages from a docs-only generator list
  • entityTypeNames({name:&#34;Item&#34;}) from codegen-ts
  • Python metaobjects init --with-docs in a scratch dir
  • python3 -m pytest on the Python apiPrefix test files (32 passed)

🔧 No changes applied.
2 infos still open:

  • ℹ️ docs/features/cli.md:251 - Text says 'No port has a doctor command: eject --list (or the matching goal) is the report.' C# dotnet meta eject --list is rejected as an unknown flag. The C# report is dotnet meta gen --list, which docs/ports/csharp.md states correctly. Minor imprecision in the shared doc.
  • ℹ️ After TS meta eject --undo removes the copies, empty directories remain (codegen/runtime/drizzle-fastify). Cosmetic only.
  • Live validation: ✅ go - 10 of 12 scenarios driven live against the product
Scenario Result Live Evidence
TS: meta init --with-docs then meta gen writes docs and API pages that name no symbol nothing generates ✅ pass live ts-with-entity.txt; docs/generated/api/Subscriber.md had no symbols until entityFile() was wired, then listed Model and Validation symbols
TS: unchanged owned copy produces the gen notice, and meta eject --undo removes it with its templates and runtime ✅ pass live ts-undo.txt
TS adversarial: a changed codegen/runtime adapter file keeps the generator out of the notice and out of no-name undo ✅ pass live ts-runtime-changed.txt
TS: named undo refuses a customised copy (exit 2) unless --force; a never-ejected name exits 2 ✅ pass live ts-undo.txt
TS: gen --strict stops on a typo'd attr and writes nothing, with a gen-specific hint; lenient gen warns and writes ✅ pass live ts-strict.txt
TS: entityTypeNames is exported from @metaobjectsdev/codegen-ts ✅ pass live bun import in the package returned typeof function
Python: init --with-docs wires api-docs; plain init leaves the list empty; api-docs omit the Model/metadata link ✅ pass live py-init-docs.txt
Python: apiPrefix (default, /v2, root, special chars) drives routers and api-docs; an invalid prefix is rejected ✅ pass live py-apiprefix.txt
Python: eject, gen notice, changed runtime blocks undo, unchanged undo, never-ejected exit 2, gen --strict ✅ pass live py-eject.txt, py-undo-strict.txt
C#: init --with-docs writes an active ApiDocsGenerator entry; eject --undo nosuch exits 2; the Cli tests (including undo, strict, runtime-changed and init-with-docs) pass ✅ pass live cs-init.txt, cs-live.txt, cs-cli-tests.txt (334 passed)
Java/Kotlin: owned-copy undo and notice are covered by the java, java-integ and kotlin-integ lanes of the full local CI run ⏸️ untested no Not driven live against the Maven plugin. The prior payload's evidence was the CI lane results in ci-local-full.log, which are lane tests, not a live drive, so no live result is established.
Complete regression suite (scripts/ci-local.sh --strict-toolchains) ⏸️ untested no Not driven as a live product scenario. The CI log reports LOCAL CI PASSED, but that is a test-suite run, not a live drive, so no live result is established.
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • meta init --with-docs then meta gen in a fresh TS project: docs and API pages written, no phantom queries/routes/entity symbols
  • meta eject entity, meta gen: one-line unchanged-copy notice; with entityFile() wired, API docs list the Model and Validation symbols
  • TS: edited codegen/runtime/drizzle-fastify/filter-allowlist.ts, notice disappears and meta eject --undo keeps the generator as customised
  • TS: meta eject --undo (unchanged, removes generator, templates and runtime), --undo queries (exit 2), --undo entity on edited copy (refused, exit 2), --force (removes)
  • TS: meta gen vs meta gen --strict with typo'd @maxLenth (lenient warns and writes; strict exits 1, writes nothing; verify rejects)
  • TS: entityTypeNames is exported from @metaobjectsdev/codegen-ts (function)
  • Python: metaobjects init (empty list) vs init --with-docs then gen; no 'Model / metadata' link in api-docs
  • Python: apiPrefix default /api, "/v2", "", "/a/b-c_d.e" in routers and AGENT-API.md; "/bad prefix!" rejected with a clear error
  • Python: eject entity, gen notice, changed codegen/runtime file suppresses notice and no-name --undo keeps it, unchanged --undo removes copy and runtime, --undo routes exits 2, gen --strict exits 1 and writes nothing
  • C#: dotnet meta init --with-docs writes an active ApiDocsGenerator entry; eject --undo nosuch exits 2; gen --strict flag present
  • dotnet test MetaObjects.Cli.Tests with DOTNET_USE_POLLING_FILE_WATCHER=1 (334 passed)
  • scripts/ci-local.sh --strict-toolchains (all lanes: gates, showcase, cube, python, java, ts-integ, ts, java-integ, mutation, csharp, kotlin-integ) — LOCAL CI PASSED per CI log, not live-driven
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…e, handEdits advice, one install line, llms.txt copy, CHANGELOG
eject --undo: Python de-duplicates names, TS refuses a never-ejected name like the other ports.
gen: the TS stock-copy notice cannot abort a run, the strict hint prints only for ERR_UNKNOWN_ATTR,
C# gen --strict has its own hint and loads once, the C# notice catches any failure.
init --with-docs says so when metaobjects.config.ts already exists.
Python apiPrefix is limited to path characters so it cannot break the generated router.
The generated web app's default host builder watches appsettings.json with an inotify
instance, and the per-user limit is shared with every other process the user runs. When it
is spent the test dies in WebApplication.CreateBuilder with an IOException that has nothing
to do with the code under test. The polling file watcher needs no inotify instance.
Share one findOwned and the codegen directory name in the Maven plugin, compute the identical
set once in Python undo, reuse the config-name and api-prefix constants, and collapse a
duplicated list in the TypeScript undo.
@dmealing
dmealing merged commit a50b9f2 into main Oct 28, 2026
1 check passed
@dmealing
dmealing deleted the fm/mo-generator-authoring branch October 28, 2026 20:08
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.

Document the type names the TypeScript generators emit per entity Python codegen: add an apiPrefix setting instead of hard-coding /api

1 participant