Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/rules/codegen-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ generator new <name> [--scope entity|package|model]` writes a working one into
are exported from the package root (`objectRefTarget`, `enumValues`, `servedPath`, the case
helpers); the guide and every port's shape are in `docs/recipes/write-your-own-generator.md`.

**Built-in factories**: `entityFile`, `queriesFile`, `routesFile`, `formFile`, `barrel`. Per ADR-0034 (scaffold-and-own) and its Amendment 2 (opt-in codegen), `meta init` scaffolds `codegen/generators/` EMPTY with `generators: []`; `meta gen --list --probe` is the catalog, and `meta eject <name>...` copies each chosen reference template into the consumer repo at `codegen/generators/*.ts` and prints the import and entry to wire. The owned copy is the ONLY import path for `entityFile`/`queriesFile`/`routesFile`/`barrel`: the deprecated `@metaobjectsdev/codegen-ts/generators` re-export of them was **removed at the 1.0 cut**.
**Built-in factories**: `entityFile`, `queriesFile`, `routesFile`, `formFile`, `barrel`. Per ADR-0034 (scaffold-and-own) and its Amendment 2 (opt-in codegen), `meta init` scaffolds `codegen/generators/` EMPTY with `generators: []`; `meta gen --list --probe` is the catalog, and `meta eject <name>...` copies each chosen reference template into the consumer repo at `codegen/generators/*.ts` and prints the import and entry to wire. `entityFile`/`queriesFile`/`routesFile`/`barrel` are also importable from `@metaobjectsdev/codegen-ts/generators` to run them as shipped (that export was removed at the 1.0 cut and **restored in 1.1**, ADR-0034 Amendment 5); the owned copy is how you change what one emits.

**User wiring** (`metaobjects.config.ts`):

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ MetaObjects is a **cross-language metadata standard** for declaring typed entity

The metamodel is the **durable spine**; generated code is the **disposable artifact**. Substrate is local-first: typed metadata lives in your repo, generated code is idiomatic per-language output with **no proprietary runtime**: the generated entity and model code imports nothing from MetaObjects at runtime, and the REST, prompt, client and runtime tiers import ordinary Apache-2.0 packages you can vendor, fork or replace. If `@metaobjectsdev/*` disappears tomorrow, you keep working code.

**Core vs helpers (ADR-0034 Amendment 3, 2026-09-22).** Only the core is a product promise: the metamodel, loader, canonical format and registry; runtime metadata access (the `ObjectManager` and its drivers; the HTTP adapters that mount it on a web framework are helpers, 2026-09-24 ruling); schema migrations (`meta migrate`); the drift gates (`meta verify`); prompt render and the reply parser. Every generator that writes application code into an adopter's repo — routes, controllers, ORM wiring, DTOs, forms, grids, hooks, filter allowlists — is a **reference helper**: it must compile and pass its reference fixtures, and the adopter copies it with `meta eject` and owns the copy. Mechanical test: what the tool guarantees is core; what it writes into your repo is a helper. Every port can eject: `meta eject` (TypeScript), `metaobjects eject` (Python), `mvn metaobjects:eject` (Java/Kotlin), `dotnet meta eject` (C#). Not every generator ships a reference copy: `meta gen --list` marks the ones that do not `package-only` (in TypeScript: `callable`, `trace-helper`, `template`, the docs tier and `shared-model`; ADR-0034 Amendment 3 correction, 2026-09-26). When you find a generator defect, fix the reference; do not describe it as a broken core guarantee.
**Core vs helpers (ADR-0034 Amendment 3, 2026-09-22).** Only the core is a product promise: the metamodel, loader, canonical format and registry; runtime metadata access (the `ObjectManager` and its drivers; the HTTP adapters that mount it on a web framework are helpers, 2026-09-24 ruling); schema migrations (`meta migrate`); the drift gates (`meta verify`); prompt render and the reply parser. Every generator that writes application code into an adopter's repo — routes, controllers, ORM wiring, DTOs, forms, grids, hooks, filter allowlists — is a **reference helper**: it must compile and pass its reference fixtures, and the adopter copies it with `meta eject` and owns the copy. Mechanical test: what the tool guarantees is core; what it writes into your repo is a helper. Every port can eject: `meta eject` (TypeScript), `metaobjects eject` (Python), `mvn metaobjects:eject` (Java/Kotlin), `dotnet meta eject` (C#). Not every generator ships a reference copy: `meta gen --list` marks the ones that do not `package-only` (in TypeScript: `template`, the docs tier and `shared-model`; ADR-0034 Amendment 3 correction, 2026-09-26, narrowed by Amendment 5). When you find a generator defect, fix the reference; do not describe it as a broken core guarantee.

## Six pillars

Expand Down
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,51 @@ generator catalog and the Atlas evaluation are 1.2 work.
`GeneratorRegistry.GeneratorInfo.ejectWith()`, the classes eject copies with a
generator.

- **The stock `entity`, `queries`, `routes` and `barrel` generators are importable again
(TypeScript).** `entityFile`, `queriesFile`, `routesFile` and `barrel` are exported from
`@metaobjectsdev/codegen-ts/generators` once more, beside `namesFile`, `routesFileHono`
and the prompt tier. 1.0 had removed those four so that an owned copy was the only way to
run them. An assessment of an adopter found its owned copies were almost pure duplication
of the reference and had already fallen behind it, so the rule is now: **import a stock
generator to run it as shipped, and `meta eject` it when you need to change what it
emits.** Both paths are supported and tested, and the factory keeps its name in an
ejected copy, so moving between them is one import line. Ruling and reasoning:
ADR-0034 Amendment 5.
- **Nothing changes for a project that owns its copies.** They run as before, and
`meta eject --list` and `meta verify --codegen` behave as before.
- **To drop a copy you never changed**, replace its local import with the package import,
delete the file and run `meta gen`. Only a copy `meta eject --list --format text` marks
`identical to the reference` is proven unchanged: `0 line(s) of your own` rules out
added or edited lines, not removed or reordered ones, so check the file's history
before dropping a copy with any other mark. `queries` and `barrel` regenerate nothing if the
copy matched the reference. `entity`, `routes` and `routes-hono` also move their
HTTP-adapter import from `codegen/runtime/` back to `@metaobjectsdev/runtime-ts`, which
then has to be installed again. For those three, a copy is unchanged only when
`meta eject --list --format text` also marks every `codegen/runtime/` file it reaches
identical: if you edited the adapter, keep the owned generator. Those rows are printed
in the text form only (`meta gen --list --format json` carries them as
`source.runtimeCopy[]`), so a listing without them is not evidence that the adapter is
unchanged. Steps and the diff to expect:
[own-your-codegen](docs/features/own-your-codegen.md#replacing-an-unmodified-owned-copy-with-the-import).
- **An imported generator is still a reference helper.** Its output is not covered by the
compatibility policy and can change in a MINOR; `meta verify --codegen` reports that
until you regenerate. Own the copy if output has to hold still across upgrades.
- **`meta gen --list` prints how to import each generator.** Every row outside the `docs`
layer shows the import line that runs the generator as shipped, and `--format json`
carries it as `wire.import`. The catalog prints the import only: each generator's
documented options say how to call it. A gate imports each package's factory module and
requires the name the catalog prints, and requires an ejectable generator's reference
template to export the same symbol.
- **`callable` and `trace-helper` can be ejected (TypeScript).** Both gained a reference
template, so `meta eject callable` and `meta eject trace-helper` copy a generator you
own: which objects get a module and where it lands. The module body stays in the package
(`renderCallableFile`, `renderTraceHelper`, now exported from `@metaobjectsdev/codegen-ts`),
the same split the prompt tier uses. The rule is that every stock generator can be
ejected except the `template` primitive, which has no emit logic of its own to own. Not
yet ejectable in this change: `shared-model` and the docs tier (`docs`, `api-docs`,
`mermaid-er`) in TypeScript. The Java `entity` and `extractor` generators became
ejectable in ADR-0034 Amendment 6.

- **Agent skills: a cost test for owned generators.** The `metaobjects-codegen` skill now says when not to write a generator: only when generator code plus
its generated output and added metadata is fewer lines than the hand-written code it
replaces, more instances of the shape are expected, or drift protection is the
Expand Down Expand Up @@ -507,6 +552,18 @@ until you regenerate.
With neither or both declared, the goal used to resolve these two names to Kotlin, the
only port that had them; it now refuses with `ejectable on more than one port` and asks
for `-Dport=java|kotlin`, as it already did for `names` and `requirement-tests`.
- **`meta init`'s scaffolded `metaobjects.config.ts` shows the import first.** The selection
is still empty (`generators: []`), and an existing project is not touched. The comment
now shows the package import as the way to wire a generator and `meta eject` as the way to
change one. `meta init`'s next steps and `meta gen`'s "no generators are wired" help say
the same.
- **The config loader no longer rejects an import of the four generators.** 1.0.x answered
`import { entityFile } from "@metaobjectsdev/codegen-ts/generators"` with a message naming
`meta eject`. That import now loads.
- **The agent-context skills teach "import by default, eject to customize".** The codegen
skill and the always-on context no longer send an agent to eject a generator it is not
going to change. The audit skill reports an owned copy it can prove unmodified as a
finding, and reads an import of the four as a blocker only on a 1.0.x install.
- **TypeScript: `RequirementTestArgs` and `RequirementView` gained required fields — a
compile break for code that builds them by hand.** `RequirementTestArgs` has six new
required fields (`package`, `unit`, `id`, `witnessKey`, `skip`, `digest`) and
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ MetaObjects has two layers, and only the first is a promise
| | Core — guaranteed | Helpers — yours |
|---|---|---|
| **What** | The metamodel, loader, canonical format and registry; runtime metadata access (the `ObjectManager`, not the HTTP adapters that mount it); schema migrations (`meta migrate`); the drift gates (`meta verify`); prompt render and the reply parser | Every generator that writes code into your repo — the ones you write for the outputs you need, and the reference routes, controllers, ORM wiring, DTOs, forms, grids and hooks you copy |
| **Promise** | Conformance-gated, the same behaviour in every port that ships it, covered by the [compatibility policy](docs/compatibility-policy.md) | Reference starting points that compile and pass their reference fixtures. Copy one with `meta eject` and change it freely |
| **Promise** | Conformance-gated, the same behaviour in every port that ships it, covered by the [compatibility policy](docs/compatibility-policy.md) | Reference starting points that compile and pass their reference fixtures. Run one as shipped, or copy it with `meta eject` and change it freely |
| **A defect is** | A MetaObjects bug, fixed in a release | A bug in the reference, fixed there; your copy is yours |

The test is mechanical: what the tool guarantees is core; what it writes into your repo
Expand All @@ -212,7 +212,8 @@ sixth ships two libraries at their own stability labels:
20-line shape plus JSON Schema and OpenAPI examples to copy. The reference generators
(Drizzle/Zod + Fastify for TS, Spring REST + DTO + repository for Java, `data class` +
Exposed for Kotlin, EF Core record + ASP.NET routes for C#, Pydantic + FastAPI for
Python) are starting points: eject the one that is close, change it, and regenerate.
Python) are starting points: run one as shipped when it already emits what you want, or
eject the one that is close, change it, and regenerate.
The engine that runs generators is core; their output is yours.
2. **Runtime metadata** — load metadata at runtime, drive behavior dynamically
(CRUD, validation, relationships, dynamic admin UIs; typed tool payloads are
Expand Down
44 changes: 31 additions & 13 deletions agent-context/skills/metaobjects-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,27 @@ Actual cutovers run through the existing skills mapped per finding tier (§ Brid
- [ ] MetaObjects present? (`metaobjects/` dir, metadata sources, `@metaobjectsdev/*` /
`com.metaobjects:*` / `metaobjects` / `MetaObjects.*` deps).
- [ ] Count metadata source lines + all `@generated` / `DO NOT EDIT` files repo-wide.
- [ ] **Owned-generators check:** does the project own generators at `codegen/generators/*`
(scaffold-and-own via `meta eject`, or `meta init` on a CLI before 1.0.4), or still import `entityFile` / `queriesFile` /
`routesFile` / `barrel` from `@metaobjectsdev/codegen-ts/generators`? Those four were
REMOVED at 1.0, so an import of them is an upgrade blocker, not just a style finding.
**Match the four NAMES, never the subpath alone** — that subpath is the supported home of
the non-ownable generators (`promptRender`, `outputParser`, `routesFileHono`, …), so
flagging a hit on the path convicts a correct project.
- [ ] **Owned-generators check:** which generators does the project own at
`codegen/generators/*` (via `meta eject`, or `meta init` on a CLI before 1.0.4), and
which does it import from `@metaobjectsdev/codegen-ts/generators`? Both are supported
from 1.1. Two findings to look for. **An unmodified owned copy** is duplication that
falls behind the reference: `meta eject --list --format text` marks each copy, and one
marked `identical to the reference` should be replaced by the package import. That is
the only mark that proves a copy unmodified. `same content as the reference, your
formatting` and `DIFFERS` with `0 line(s) of your own` rule out added or edited lines,
not removed or reordered ones: confirm those from the file's history
(`git log -p -- codegen/generators/<name>.ts` shows no edit after the eject) first, and
report a copy you cannot confirm as "possibly customized: check its history", not as a
copy to drop. For `entity`,
`routes` and `routes-hono` that holds only when every `codegen/runtime/` file the copy
reaches is also marked identical: an edited adapter is a customization, and such a copy
is not a finding. Those runtime rows appear only with `--format text` (or as the
`runtimeCopy` array inside the row's `source` object in `meta gen --list --format
json`); a listing with no runtime rows is not evidence that the adapter is unmodified. **An import of `entityFile` /
`queriesFile` / `routesFile` / `barrel` on a 1.0.x install** fails to load, because 1.0
removed those four and 1.1 restored them: there it is an upgrade blocker, fixed by
upgrading to 1.1 or by ejecting. Importing anything from that subpath is otherwise
correct and is never a finding by itself.
- [ ] **Cross-language version consistency (silent-drift check).** If the project uses MetaObjects in more than one language (e.g. a TS web client + a Java/Python/C# backend), enumerate EVERY MetaObjects package across ALL ecosystems (npm `@metaobjectsdev/*`, Maven `com.metaobjects:*`, PyPI `metaobjects`, NuGet `MetaObjects.*`) and record each version. **The version-number LINES differ by ecosystem (npm/PyPI/NuGet `0.x`/`1.x` vs Maven `7.x`/`8.x`), so you CANNOT eyeball drift** — a `0.12` next to a `7.7` looks fine but can be badly out of sync. Compare the **`metamodelVersion`** each port reports (the shared spec version on the registry manifest): a mismatch is real cross-language drift and a **finding** — the ports disagree on vocabulary/wire behavior. Also flag any port not on the latest release for its ecosystem. (This is a known real-world failure mode: newest backend, stale client, invisible because the numbers differ.)
- [ ] Classify: **Greenfield** (none/minimal) · **Partial** · **Deep** → choose path below.

Expand Down Expand Up @@ -380,16 +394,20 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend

## Owned-codegen & scaffold-and-own assessment

- If config imports `entityFile` / `queriesFile` / `routesFile` / `barrel` from
`@metaobjectsdev/codegen-ts/generators` instead of owned `codegen/generators/*`,
recommend the scaffold-and-own migration (`meta init`, or `meta eject <name>`) — on 1.0+
those four no longer resolve. Importing the non-ownable generators from that subpath is
correct and is not a finding.
- A config may import `entityFile` / `queriesFile` / `routesFile` / `barrel` from
`@metaobjectsdev/codegen-ts/generators` or from owned `codegen/generators/*`. Both are
supported from 1.1, and the import is the right choice for a generator the project does
not change. The import is a blocker only on a 1.0.x install, where those four do not
resolve: recommend upgrading to 1.1, or `meta eject <name>` if the project has to stay
on 1.0.x. On 1.1 the finding is the reverse case, an owned copy proven unmodified by
the Phase 0 owned-generators check: recommend the package import. Importing any other
generator from that subpath is correct and is not a finding.
- Audit owned generators: (a) regenerate clean? (b) drifted from reference templates —
intentional (good) vs stale/accidental (missed upstream fix)? (c) hand-rolling a walk
that a declarative `scope` + `outputPattern` could replace? (d) bespoke shape better as
a `templateGenerator` than a forked generator?
- **Authoring ladder:** built-in fits → use it · close → **own + customize** (the default)
- **Authoring ladder:** built-in fits → import it as shipped · close → **eject it and
customize the copy**
· new shape → **author a declarative template-spec / custom generator from the metadata**
· genuinely un-modelable → hand-write (still import the generated types).
- **Generator-gap check:** missing generators that block the biggest wins? Recommend per gap:
Expand Down
Loading
Loading