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 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: `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.
**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` alone; ADR-0034 Amendment 3 correction, 2026-09-26, narrowed by Amendments 5 and 7). When you find a generator defect, fix the reference; do not describe it as a broken core guarantee.

## Six pillars

Expand Down
111 changes: 97 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ generator catalog and the Atlas evaluation are 1.2 work.
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.
ejected except the `template` primitive, which has no emit logic of its own to own.
`shared-model`, `docs` and `api-docs` follow in their own entry below (ADR-0034
Amendment 7), and 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
Expand Down Expand Up @@ -521,6 +521,42 @@ generator catalog and the Atlas evaluation are 1.2 work.
docs` wording come from the packages, and each port's existing generators already type a
required field as not nullable), so an owned generator needs no resync for this. See
[docs/features/reporting.md](docs/features/reporting.md#rows-from-a-dimensions-entity).
- **`shared-model`, `docs` and `api-docs` can be ejected (TypeScript).** With these
three, every stock TypeScript generator can be ejected except `template`, the Mustache
primitive: you already supply its template and it has no emit logic of its own to own,
so it stays `package-only` (the C#, Java and Python ports rule the same way). The
standalone `mermaid-er` generator is not among them: it is removed (see **Removed**
below). Ruling and reasoning: ADR-0034 Amendment 7. Nothing changes for a project that
ejects none of them: packaged output is byte-identical, and gated to be.
- **`meta eject shared-model`** copies a thin generator. The copy owns which of your
source files may contribute and where the published version comes from. Everything a
consumer's `meta deps sync` verifies stays in the package, in two functions the copy
calls and `@metaobjectsdev/codegen-ts` now exports: `buildSharedModel` (load, select,
closure check, serialize, core-vocabulary re-load) and `renderSharedModelFiles` (the
file names, the hash, the manifest). A test publishes with an ejected copy and has a
consumer sync, lock and verify the result.
- **`meta eject docs` and `meta eject api-docs`** copy the generators behind
`meta docs`. List the copy in the docs block of `metaobjects.config.ts`, a new key:
`docs: { generators: [docsFile()] }`. It carries a `docsSurface` marker, and
`meta docs` and `meta verify --docs` run it in place of the packaged generator for that
surface. `meta eject` prints exactly that wiring, and its JSON row carries
`wire.into: "docs.generators"`. The copy owns which nodes get a page,
where a page lands, the overview page and which forms of the API reference are written.
A docs generator does not go in the top-level `generators`: `meta gen` skips one there
with a warning that names `docs.generators`. Two entries for one surface, or an entry
with no `docsSurface`, are refused by name. The `requirements` and `agent` pages stay
packaged.
- To change only the wording of a model page you still need no generator: a
`templates/docs/entity-page.md.mustache` in your project wins over the packaged one.
- Steps and what each copy owns:
[own-your-codegen](docs/features/own-your-codegen.md#the-docs-generators-and-shared-model-typescript).
- **`ctx.inputRoot` for a generator that reads the project's own files.** It is the real
project directory. Under `meta gen` it equals `ctx.projectRoot`. Under
`meta verify --codegen` the run writes into a temporary tree and `ctx.projectRoot` is
that tree, as before, because owned `entity` and `routes` copies compute an import path
against it. Under `meta docs` and `meta verify --docs` it is the directory that holds
`metaobjects.config.ts`, and `--templates` does not move it. Read files through
`ctx.inputRoot`; `runGen` takes it as `inputRoot`.

### Changed

Expand Down Expand Up @@ -651,6 +687,16 @@ until you regenerate.
- **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.
- **`meta docs` stops on a `metaobjects.config.ts` that fails to load.** It prints the load
error, exits 2 and writes nothing, as `meta gen` does. `meta verify --docs` calls
`meta docs`, so it fails on the same config. Before, `meta docs` warned, wrote the model
pages without the config and exited 0. With an owned docs generator listed in
`docs: { generators: [...] }` that would have overwritten your pages with the packaged
generator's output, so the fallback is removed. A project with no `metaobjects.config.ts`
is unaffected: `meta docs` still writes the model pages from metadata alone.
**To upgrade**, if a docs build has been passing with a config-load warning, fix the
config. A config that still imports the removed `mermaidErDiagram` is one such case (see
**Removed** below).
- **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
Expand Down Expand Up @@ -698,6 +744,27 @@ until you regenerate.
One rule is relaxed: an authored `validator.length @min: 0` on a `@required` string now
admits the empty string, as the generated schema already did.

### Removed

- **The standalone `mermaid-er` generator (TypeScript).** `mermaidErDiagram()`, its
`MermaidErOptions` type and the stable name `"mermaid-er"` are gone from
`@metaobjectsdev/codegen-ts/generators`, the generator catalog and the cross-port
generator manifest. It wrote the whole-model ER diagram to a file of its own
(`docs/model.md`) under `meta gen`, and was the older home of a diagram `meta docs` has
drawn since the docs engine took it over: the whole model on the overview page
(`README.md`), and each entity's neighbourhood on that entity's page. Those diagrams are
unchanged, byte for byte. No other port shipped the generator. Ruling: ADR-0034
Amendment 7; ADR-0022 Part 2 D1 is amended to match.
- **To upgrade**, if your `metaobjects.config.ts` wires it: delete the
`mermaidErDiagram` import and its entry in `generators` (or the `"mermaid-er"` name),
delete the file it wrote (`docs/model.md` under your output directory, unless you moved
it with `outFile`), and run `meta docs`. `meta verify --codegen` reports that file as
no longer generated until it is deleted.
- **A config that still wires it fails with a message that says this**, instead of the
runtime's own words for a missing export. The import is diagnosed in the config and in
a module the config imports, and the name is diagnosed as removed, not as unknown.
- A project that never wired it sees no change.

### Fixed

- **A doc generator skipped by `meta gen` is now reported in every output form.** `meta docs`
Expand All @@ -720,23 +787,33 @@ until you regenerate.
docs` `` with the reason under the row, a wired one reads ``WIRED but skipped by `meta
gen` ``, and `--probe` no longer prints `would emit 0` for them. In `--format json` the row
carries `note` and `door: "meta docs"`, and `project.wouldEmit` is `null` instead of `0`.
The `mermaid-er` row's note now states the back-compat fact: it is deprecated, a
`mermaidErDiagram()` already wired in `generators` still runs, and `meta docs` draws the
same diagram on its overview page. A neutral name wired as a string (`"docs"`,
`"mermaid-er"`), which `meta gen` refuses, reads ``WIRED by name, which `meta gen`
refuses`` (`project.refused` in `--format json`).
There is no `mermaid-er` row any more: the generator is removed in this release (see
**Removed** above). The neutral name `"docs"` wired as a string, which `meta gen`
refuses, reads ``WIRED by name, which `meta gen` refuses`` (`project.refused` in
`--format json`).
- **`meta verify`** said nothing about a config wired this way. It now reports each one as an
advisory row (rule `doc-generator-in-gen`, in `antiPatterns.rows[]`) that names
`meta verify --docs` as the gate that checks documentation. A neutral name wired as a
string is reported under rule `doc-generator-refused-by-gen`, because `meta gen` fails on
it rather than skipping it. Advisory: the exit code is unchanged.
it rather than skipping it. The removed name `"mermaid-er"` is reported under the same
rule, with the message `meta gen` fails with. Advisory: the exit code is unchanged.
- **An owned docs generator under `generators`** (a copy made with `meta eject docs` or
`meta eject api-docs`) is skipped by the same rule and reported in the same places. Its
fix is the opposite one, so each message says to move it to
`docs: { generators: [...] }`, where `meta docs` runs it, and not to remove it: the
runner's warning, the `help[]` next step and the `doc-generator-in-gen` advisory row. A
config that wires only such a copy is no longer told `no entities to generate`. Listed
in the docs block, its `meta gen --list` row reads ``WIRED in `docs.generators` ``
(`project.wiredInDocsDoor` in `--format json`).
- **The unknown-generator error** listed `api-docs` among the names `meta gen` accepts. It no
longer does, and it says documentation comes from `meta docs`.

`runGen()` returns a new `skippedDocGenerators: string[]` beside `warnings`, and
`@metaobjectsdev/codegen-ts` exports `isPackagedDocsGenerator` (skipped) and
`isNeutralGeneratorName` (refused), so a caller embedding the runner can ask rather than
match a sentence. TypeScript only: in the C#, Java, Kotlin and Python
`runGen()` returns a new `skippedDocGenerators: string[]` beside `warnings`, with
`skippedOwnedDocGenerators` naming the owned copies among them, and
`@metaobjectsdev/codegen-ts` exports `isPackagedDocsGenerator` (skipped),
`isNeutralGeneratorName` (refused) and `removedGeneratorMessage` (refused as removed), so
a caller embedding the runner can ask rather than match a sentence. TypeScript only: in
the C#, Java, Kotlin and Python
ports documentation is a separate command (`dotnet meta docs`, `mvn metaobjects:docs`,
`metaobjects docs`) and no doc generator is registered for code generation, so there is
nothing to wire and nothing to skip.
Expand Down Expand Up @@ -1183,6 +1260,12 @@ until you regenerate.
`enforce` is on the `MetaReferenceIdentity` that `referenceIdentities()` returns, and that a bare
entity name in `@of`, `@via` and `@spine` resolves in the declaring entity's package (the
report's, for `@spine`), so another package's entity needs its package name.
- **`meta verify --codegen` can check a project that wires `sharedModelFile()`.** The gate
regenerates into a temporary tree and passed the generator neither the project's own
source files nor a way to read the project, so it failed with "no source files to load"
for a packaged or an owned `shared-model`, and would have taken the version default from
the temporary tree. It now passes what `meta gen` passes. A publisher's drift gate on its
shared artifact, which the dependency docs describe, works as described.

## [1.0.13] — 2026-10-03

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ selection (every reference from a selected node must resolve to another selected
node, or the build fails naming the pair) and re-loads the emitted artifact with
core providers only, so a Phase 1a export needing non-core vocabulary fails at
publish time. It is registered and shows up in `meta gen --list` like any other
generator — but **it is deliberately not offered by `meta eject --list`** (unlike
the four ADR-0034 scaffold-and-own generators). The artifact is a contract whose
bytes a cross-port corpus pins and whose hash consumers verify; a user-owned,
editable copy would invite an artifact that silently stops matching what
consumers expect. Only TypeScript can run it in Phase 1a. **Phase 1a is TypeScript and Python
generator, and `meta eject shared-model` copies a thin generator you own: which
source files may contribute, and where the published version comes from. The
artifact is a contract whose bytes a cross-port corpus pins and whose hash
consumers verify, so everything a consumer checks stays in the package, in the two
functions the copy calls (`buildSharedModel`, `renderSharedModelFiles`). Do not
replace those calls: an unmodified copy publishes the same bytes as the packaged
generator. Only TypeScript can run it in Phase 1a. **Phase 1a is TypeScript and Python
only, full stop** — TypeScript and Python are the only two ports that can
*consume* a dependency at all today, and only the TypeScript toolchain can
*publish* one; Java, Kotlin, and C# do not read `dependencies` yet (Phase 2).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ it there, in the same change, and keep going:

| Port | Where the fix goes |
|---|---|
| **TypeScript** | Your ejected copy under `codegen/generators/` — and, for routes, the HTTP adapter it calls: `meta eject routes` / `routes-hono` / `entity` also copy that adapter's source (mount helpers, filter parser, error envelopes, pagination) into `codegen/runtime/`, and the ejected output imports it from there, so a bug in a route's behaviour is fixed in that copy. If you are running a generator you never ejected (imported from its package), `meta eject <name>` it first and swap the import for the local one eject prints. Every stock generator can be ejected except the `template` primitive, which has no emit logic of its own to own. `shared-model` and the docs tier (`docs`, `api-docs`, `mermaid-er`) are not yet ejectable. A generator `meta gen --list` marks `package-only` has no reference template: replace it in `generators` with your own `Generator`, starting from its source in the installed package. |
| **TypeScript** | Your ejected copy under `codegen/generators/` — and, for routes, the HTTP adapter it calls: `meta eject routes` / `routes-hono` / `entity` also copy that adapter's source (mount helpers, filter parser, error envelopes, pagination) into `codegen/runtime/`, and the ejected output imports it from there, so a bug in a route's behaviour is fixed in that copy. If you are running a generator you never ejected (imported from its package), `meta eject <name>` it first and swap the import for the local one eject prints. Every stock generator can be ejected except the `template` primitive, which has no emit logic of its own to own. An ejected `docs` or `api-docs` goes in `docs: { generators: [...] }`, not in `generators`: `meta docs` runs it. A generator `meta gen --list` marks `package-only` has no reference template: replace it in `generators` with your own `Generator`, starting from its source in the installed package. |
| **Java / Kotlin** | Your own generator class. Subclass the reference generator and override the `protected` method that emits the wrong piece; when that piece is `private`, copy the reference generator's source (Apache-2.0) into your own package and edit it. Put the class in a codegen module that the module running `metaobjects:generate` depends on — the plugin loads `<classname>` from that module's compile classpath, and `provided` scope keeps it out of your packaged app — then point `<classname>` at it. Full steps: the Java and Kotlin references. |
| **C# / Python** | Your owned copy (`dotnet meta eject <name>` / `metaobjects eject <name>`, then edit it), or a generator of your own that replaces the artifact — an `IGenerator` in `codegen/Program.cs` on C#, a `module:symbol` entry on Python — or a template spec (`--template-spec`). |

Expand Down
Loading
Loading