From e0a2b9b84f6d51d41fa89899db6fd0e414bf8767 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 24 Oct 2026 15:18:21 -0400 Subject: [PATCH] docs: feature commits update the requirement ledger; recommend the strict requirements check; correct handler-code figure --- CHANGELOG.md | 17 ++++++++++++++++- .../skills/metaobjects-authoring/SKILL.md | 2 ++ .../references/requirements.md | 9 +++++++++ .../references/requirements.md | 12 ++++++++++++ agent-context/templates/always-on.md.mustache | 1 + docs/features/requirements.md | 6 ++++++ docs/ports/csharp.md | 1 + docs/ports/java.md | 2 +- docs/ports/kotlin.md | 2 +- docs/ports/python.md | 3 ++- docs/ports/typescript.md | 3 ++- .../skills/metaobjects-authoring/SKILL.md | 2 ++ .../expected/.metaobjects/AGENTS.md | 1 + .../expected/.metaobjects/CLAUDE.md | 1 + .../skills/metaobjects-authoring/SKILL.md | 2 ++ .../java-react/expected/.metaobjects/AGENTS.md | 1 + .../java-react/expected/.metaobjects/CLAUDE.md | 1 + .../skills/metaobjects-authoring/SKILL.md | 2 ++ .../python/expected/.metaobjects/AGENTS.md | 1 + .../python/expected/.metaobjects/CLAUDE.md | 1 + .../skills/metaobjects-authoring/SKILL.md | 2 ++ .../expected/.metaobjects/AGENTS.md | 1 + .../expected/.metaobjects/CLAUDE.md | 1 + .../skills/metaobjects-authoring/SKILL.md | 2 ++ .../references/requirements.md | 9 +++++++++ .../references/requirements.md | 12 ++++++++++++ .../expected/.metaobjects/AGENTS.md | 1 + .../expected/.metaobjects/CLAUDE.md | 1 + 28 files changed, 94 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e30c32e..975acd084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,21 @@ here.** ## [Unreleased] +### Changed + +- **Requirements guidance: a feature commit updates its requirement.** The agent context + (the authoring and verify skills and the always-on file `meta init` writes) now tells an + agent that a commit adding, changing or removing behaviour updates the ledger in the same + commit, or says why not. `verify` checks that the ledger's links resolve, not that it is + current, so nothing else enforces it. +- **The adoption docs recommend the strict requirements check** (`--require-implementers`, or + its per-port form) in the project's verify script or pre-push hook once the ledger is clean. + +### Fixed + +- The 1.1.0 entry's handler-code figure of 439 lines retired is corrected to about 460, as + measured in one adopter's pull request. + ## [1.1.2] — unreleased _Fixes #454 and #455. A bug-fix release: no vocabulary or `metamodelVersion` change._ @@ -205,7 +220,7 @@ every port's `verify`), the derived-key parity fix across the ports, and the lis nested-average fixes found by the first adopter to map real report pages onto the feature. **How much smaller the code gets.** The size reduction comes in two steps, and each is its own -number. The first landed on 1.0.x: 439 lines of handler code retired. The second is reporting, +number. The first landed on 1.0.x: about 460 lines of handler code retired, measured in one adopter's pull request. The second is reporting, and it is a SQL reduction only: in an adopter, hand-written SQL went from 75 lines to 15 (-80%). Total hand-written lines did not fall, so do not read it as a total line reduction. diff --git a/agent-context/skills/metaobjects-authoring/SKILL.md b/agent-context/skills/metaobjects-authoring/SKILL.md index 14c60029b..85ac7bc5c 100644 --- a/agent-context/skills/metaobjects-authoring/SKILL.md +++ b/agent-context/skills/metaobjects-authoring/SKILL.md @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of: Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current. + If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is: ```yaml diff --git a/agent-context/skills/metaobjects-authoring/references/requirements.md b/agent-context/skills/metaobjects-authoring/references/requirements.md index e35490d5a..abd10504a 100644 --- a/agent-context/skills/metaobjects-authoring/references/requirements.md +++ b/agent-context/skills/metaobjects-authoring/references/requirements.md @@ -8,6 +8,7 @@ more than the rest. - A requirement is PRESCRIPTIVE — it states what should be true, never what happened - Do not delete a retired capability's entry - When you add an entity, claim it +- A feature commit updates the ledger in the same commit - Every requirement states its counterexample - Four prose slots, and `statement` is the one that means "description" - `title` is a LABEL and is chartered here; `summary` is not @@ -56,6 +57,14 @@ the requirement went stale. **2. When you add an entity, claim it.** Every `object.entity` should appear in some requirement's `implementedBy`, or `verify` says so. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit.** +`verify` checks that the ledger's links resolve, never that it is current, so nothing fails +when a feature ships and its requirement does not move. In the same commit, write the new or +changed claim, set its `@status` (`planned` to `live` when it lands, `retired` when it is +removed) and keep its `@implementedBy` pointing at the real nodes (a retired entry carries +none). If the commit should not +touch the ledger, say why in the commit message. + **Every requirement states its counterexample.** One sentence: what breaking it looks like. *"Every entity has a uuid primary key"* is violable — point at one with a composite string key. *"Things are persisted"* is not, and is a description rather than a requirement. Same diff --git a/agent-context/skills/metaobjects-verify/references/requirements.md b/agent-context/skills/metaobjects-verify/references/requirements.md index 103b3121b..6927d37f2 100644 --- a/agent-context/skills/metaobjects-verify/references/requirements.md +++ b/agent-context/skills/metaobjects-verify/references/requirements.md @@ -10,6 +10,7 @@ subverb**: requirements are metadata, so they are checked on *every* `meta verif - The status asymmetry — the one that surprises people - Exit codes - The strict switch — `--require-implementers` +- Keeping the ledger current - The error codes, and the fix for each - The authoring lint — a second section, never an error - `verify` does not look at your tests @@ -113,6 +114,17 @@ warning. If CI went red on this code, check whether the switch is set before tre as a new defect: the fix is to add the `@implementedBy` link (or drop the entry to `planned`), not to unset the switch. +Once the ledger is clean, set the switch where the project already runs `verify`: its verify +script, CI job or pre-push hook. Until then a stale `live` claim is only a warning that +nobody reads. + +## Keeping the ledger current + +`verify` proves the ledger's links resolve, not that the ledger is current. A commit that +adds, changes or removes behaviour updates the ledger in the same commit: the new or changed +claim, its `@status` and its `@implementedBy`. If the commit should not touch the ledger, the +commit message says why. + ## The error codes, and the fix for each Match on the stable `code`, never the message text (ADR-0009). The runtime messages already diff --git a/agent-context/templates/always-on.md.mustache b/agent-context/templates/always-on.md.mustache index 6445663b5..09cf315d8 100644 --- a/agent-context/templates/always-on.md.mustache +++ b/agent-context/templates/always-on.md.mustache @@ -59,6 +59,7 @@ from. - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/docs/features/requirements.md b/docs/features/requirements.md index fcf85df6a..5a7731480 100644 --- a/docs/features/requirements.md +++ b/docs/features/requirements.md @@ -430,6 +430,12 @@ more than it links. A project whose ledger has caught up can make it fail the bu | Java, Kotlin | `mvn metaobjects:verify -Dmeta.verify.requireImplementers=true` | `META_REQUIRE_IMPLEMENTERS=1` | | C# | `dotnet meta verify ./metadata --templates ./prompts --require-implementers` | `META_REQUIRE_IMPLEMENTERS=1` | +**Recommended once the ledger is clean:** set the switch in the project's verify script or +pre-push hook. Every adopter ledger we measured passed the default gate, because the gate +checks that links resolve, not that the ledger is current, and as a warning the finding goes +unread. Pair it with the authoring rule that a commit adding, changing or removing behaviour +updates its requirement in the same commit. + The switch raises that one finding to an error and keeps its code, so a report or a suppression keyed on the code still matches. No other warning changes severity. It is not called `--strict`, because `verify` already has `--lax` on a different axis and a `--strict` diff --git a/docs/ports/csharp.md b/docs/ports/csharp.md index 61a493069..a9fd9f95c 100644 --- a/docs/ports/csharp.md +++ b/docs/ports/csharp.md @@ -527,6 +527,7 @@ are core and are not ejectable. # The gate: runs on every verify, beside whichever drift gate it was given dotnet meta verify ./metaobjects --codegen --out ./Generated dotnet meta verify ./metaobjects --codegen --out ./Generated --require-implementers +# Recommended once the ledger is clean: use this form in the project's verify script or pre-push hook # The tests: the same run as everything else codegen/Generators.cs declares. The entry's # OutDir puts them in the test project, which references xunit; the app's --out does not. diff --git a/docs/ports/java.md b/docs/ports/java.md index dffb33439..b4ece24f2 100644 --- a/docs/ports/java.md +++ b/docs/ports/java.md @@ -591,7 +591,7 @@ checks them on every run, in either mode: it logs the ledger summary, and an err dangling reference on a live requirement, a link above the floor, a live policy applied to nothing) fails the build. `-Dmeta.verify.requireImplementers=true` (or `META_REQUIRE_IMPLEMENTERS=1`) also fails it on a live functional requirement that nothing -implements. Those checks are core and are not ejectable. +implements; once the ledger is clean, set it in the build or pre-push hook that runs the goal. Those checks are core and are not ejectable. This generator is the other half, and it is a reference helper and a recommended approach, not a contract: it writes one JUnit Jupiter test per tested requirement. The test does not diff --git a/docs/ports/kotlin.md b/docs/ports/kotlin.md index 3f0ce81e2..fb1696a5f 100644 --- a/docs/ports/kotlin.md +++ b/docs/ports/kotlin.md @@ -223,7 +223,7 @@ plugin generated; it has to be told about `generated-sources/kotlin` and your ow The `KotlinSpringConfigGenerator` reads its model from the classpath (``), so the same file goes under `src/main/resources/` as well as `src/main/metaobjects/`. -**Requirement tests.** `mvn metaobjects:verify` checks the model's `requirement.*` nodes on every run (the Java goal; Kotlin has no CLI of its own), and `-Dmeta.verify.requireImplementers=true` or `META_REQUIRE_IMPLEMENTERS=1` also fails the build on a live functional requirement that nothing implements. Those checks are core and are not ejectable. `KotlinRequirementTestsGenerator` is the other half: a reference helper and a recommended approach, not a contract. It is the Kotlin sibling of the Java `JUnitRequirementTestsGenerator` (the same args, the same test identities, one conformance corpus for both). It writes one JUnit Jupiter test per tested requirement; the test calls a **witness**, a function you write, and a live requirement with no witness fails. A JUnit 4 project replaces the text through the `renderer` arg, or ejects the generator. +**Requirement tests.** `mvn metaobjects:verify` checks the model's `requirement.*` nodes on every run (the Java goal; Kotlin has no CLI of its own), and `-Dmeta.verify.requireImplementers=true` or `META_REQUIRE_IMPLEMENTERS=1` also fails the build on a live functional requirement that nothing implements; once the ledger is clean, set it in the build or pre-push hook that runs the goal. Those checks are core and are not ejectable. `KotlinRequirementTestsGenerator` is the other half: a reference helper and a recommended approach, not a contract. It is the Kotlin sibling of the Java `JUnitRequirementTestsGenerator` (the same args, the same test identities, one conformance corpus for both). It writes one JUnit Jupiter test per tested requirement; the test calls a **witness**, a function you write, and a live requirement with no witness fails. A JUnit 4 project replaces the text through the `renderer` arg, or ejects the generator. ```xml diff --git a/docs/ports/python.md b/docs/ports/python.md index 05a8f7c9a..af948d6bb 100644 --- a/docs/ports/python.md +++ b/docs/ports/python.md @@ -436,7 +436,8 @@ Declare what the software must do as `requirement.*` nodes and `metaobjects veri them on every run: it prints the ledger summary, and an error (a dangling reference on a live requirement, a link above the floor, a live policy applied to nothing) exits non-zero. `--require-implementers` (or `META_REQUIRE_IMPLEMENTERS=1`) also fails the run on a live -functional requirement that nothing implements. Those checks are core and are not +functional requirement that nothing implements; once the ledger is clean, put it in the +project's verify script or pre-push hook. Those checks are core and are not ejectable. A model with no requirement sees no change. Metadata that does not load is not that: `verify` prints the load errors and exits non-zero, also when no drift gate ran and so none loaded it. diff --git a/docs/ports/typescript.md b/docs/ports/typescript.md index 21ec8f853..7cf812110 100644 --- a/docs/ports/typescript.md +++ b/docs/ports/typescript.md @@ -812,7 +812,8 @@ edit). The other four ports do not merge: their generated tests call a *witness* See [Generated requirement tests and witnesses](../features/requirements.md#generated-requirement-tests-and-witnesses). `meta verify --require-implementers` (or `META_REQUIRE_IMPLEMENTERS=1`) raises -`WARN_REQUIREMENT_NOTHING_IMPLEMENTS` to an error. See +`WARN_REQUIREMENT_NOTHING_IMPLEMENTS` to an error; once the ledger is clean, put it in the +project's verify script or pre-push hook. See [Requiring implementers](../features/requirements.md#requiring-implementers). ## Capability snapshot diff --git a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md index 14c60029b..85ac7bc5c 100644 --- a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md +++ b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of: Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current. + If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is: ```yaml diff --git a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/AGENTS.md b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/AGENTS.md index dd0e2f325..f1fd92f37 100644 --- a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/AGENTS.md +++ b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/AGENTS.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/CLAUDE.md b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/CLAUDE.md index dd0e2f325..f1fd92f37 100644 --- a/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/CLAUDE.md +++ b/fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.metaobjects/CLAUDE.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/java-react/expected/.claude/skills/metaobjects-authoring/SKILL.md b/fixtures/agent-context-conformance/java-react/expected/.claude/skills/metaobjects-authoring/SKILL.md index 14c60029b..85ac7bc5c 100644 --- a/fixtures/agent-context-conformance/java-react/expected/.claude/skills/metaobjects-authoring/SKILL.md +++ b/fixtures/agent-context-conformance/java-react/expected/.claude/skills/metaobjects-authoring/SKILL.md @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of: Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current. + If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is: ```yaml diff --git a/fixtures/agent-context-conformance/java-react/expected/.metaobjects/AGENTS.md b/fixtures/agent-context-conformance/java-react/expected/.metaobjects/AGENTS.md index 2051a2b50..129bbcd19 100644 --- a/fixtures/agent-context-conformance/java-react/expected/.metaobjects/AGENTS.md +++ b/fixtures/agent-context-conformance/java-react/expected/.metaobjects/AGENTS.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/java-react/expected/.metaobjects/CLAUDE.md b/fixtures/agent-context-conformance/java-react/expected/.metaobjects/CLAUDE.md index 2051a2b50..129bbcd19 100644 --- a/fixtures/agent-context-conformance/java-react/expected/.metaobjects/CLAUDE.md +++ b/fixtures/agent-context-conformance/java-react/expected/.metaobjects/CLAUDE.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/python/expected/.claude/skills/metaobjects-authoring/SKILL.md b/fixtures/agent-context-conformance/python/expected/.claude/skills/metaobjects-authoring/SKILL.md index 14c60029b..85ac7bc5c 100644 --- a/fixtures/agent-context-conformance/python/expected/.claude/skills/metaobjects-authoring/SKILL.md +++ b/fixtures/agent-context-conformance/python/expected/.claude/skills/metaobjects-authoring/SKILL.md @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of: Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current. + If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is: ```yaml diff --git a/fixtures/agent-context-conformance/python/expected/.metaobjects/AGENTS.md b/fixtures/agent-context-conformance/python/expected/.metaobjects/AGENTS.md index 1e16742ed..f405af695 100644 --- a/fixtures/agent-context-conformance/python/expected/.metaobjects/AGENTS.md +++ b/fixtures/agent-context-conformance/python/expected/.metaobjects/AGENTS.md @@ -34,6 +34,7 @@ from. - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/python/expected/.metaobjects/CLAUDE.md b/fixtures/agent-context-conformance/python/expected/.metaobjects/CLAUDE.md index 1e16742ed..f405af695 100644 --- a/fixtures/agent-context-conformance/python/expected/.metaobjects/CLAUDE.md +++ b/fixtures/agent-context-conformance/python/expected/.metaobjects/CLAUDE.md @@ -34,6 +34,7 @@ from. - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/ts-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md b/fixtures/agent-context-conformance/ts-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md index 14c60029b..85ac7bc5c 100644 --- a/fixtures/agent-context-conformance/ts-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md +++ b/fixtures/agent-context-conformance/ts-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of: Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current. + If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is: ```yaml diff --git a/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/AGENTS.md b/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/AGENTS.md index 5667b3da6..da8f6cc1c 100644 --- a/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/AGENTS.md +++ b/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/AGENTS.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/CLAUDE.md b/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/CLAUDE.md index 5667b3da6..da8f6cc1c 100644 --- a/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/CLAUDE.md +++ b/fixtures/agent-context-conformance/ts-react-tanstack/expected/.metaobjects/CLAUDE.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/SKILL.md b/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/SKILL.md index 14c60029b..85ac7bc5c 100644 --- a/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/SKILL.md +++ b/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/SKILL.md @@ -464,6 +464,8 @@ Reach for it when the project needs to answer any of: Every requirement must be **violable**: if you cannot say what breaking it looks like, it is a description, not a requirement. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit** (the new or changed claim, its `@status` and `@implementedBy`), or says in the commit message why it does not. `verify` checks that links resolve, not that the ledger is current. + If the project declares any `requirement.*` node, `references/requirements.md` is installed with the full authoring rules. If it does not and you are adding the first one, the shape is: ```yaml diff --git a/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/references/requirements.md b/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/references/requirements.md index e35490d5a..abd10504a 100644 --- a/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/references/requirements.md +++ b/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/references/requirements.md @@ -8,6 +8,7 @@ more than the rest. - A requirement is PRESCRIPTIVE — it states what should be true, never what happened - Do not delete a retired capability's entry - When you add an entity, claim it +- A feature commit updates the ledger in the same commit - Every requirement states its counterexample - Four prose slots, and `statement` is the one that means "description" - `title` is a LABEL and is chartered here; `summary` is not @@ -56,6 +57,14 @@ the requirement went stale. **2. When you add an entity, claim it.** Every `object.entity` should appear in some requirement's `implementedBy`, or `verify` says so. +**A commit that adds, changes or removes behaviour updates the ledger in the same commit.** +`verify` checks that the ledger's links resolve, never that it is current, so nothing fails +when a feature ships and its requirement does not move. In the same commit, write the new or +changed claim, set its `@status` (`planned` to `live` when it lands, `retired` when it is +removed) and keep its `@implementedBy` pointing at the real nodes (a retired entry carries +none). If the commit should not +touch the ledger, say why in the commit message. + **Every requirement states its counterexample.** One sentence: what breaking it looks like. *"Every entity has a uuid primary key"* is violable — point at one with a composite string key. *"Things are persisted"* is not, and is a description rather than a requirement. Same diff --git a/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-verify/references/requirements.md b/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-verify/references/requirements.md index 103b3121b..6927d37f2 100644 --- a/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-verify/references/requirements.md +++ b/fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-verify/references/requirements.md @@ -10,6 +10,7 @@ subverb**: requirements are metadata, so they are checked on *every* `meta verif - The status asymmetry — the one that surprises people - Exit codes - The strict switch — `--require-implementers` +- Keeping the ledger current - The error codes, and the fix for each - The authoring lint — a second section, never an error - `verify` does not look at your tests @@ -113,6 +114,17 @@ warning. If CI went red on this code, check whether the switch is set before tre as a new defect: the fix is to add the `@implementedBy` link (or drop the entry to `planned`), not to unset the switch. +Once the ledger is clean, set the switch where the project already runs `verify`: its verify +script, CI job or pre-push hook. Until then a stale `live` claim is only a warning that +nobody reads. + +## Keeping the ledger current + +`verify` proves the ledger's links resolve, not that the ledger is current. A commit that +adds, changes or removes behaviour updates the ledger in the same commit: the new or changed +claim, its `@status` and its `@implementedBy`. If the commit should not touch the ledger, the +commit message says why. + ## The error codes, and the fix for each Match on the stable `code`, never the message text (ADR-0009). The runtime messages already diff --git a/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/AGENTS.md b/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/AGENTS.md index 4ca195ea5..ea69ee556 100644 --- a/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/AGENTS.md +++ b/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/AGENTS.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes. diff --git a/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/CLAUDE.md b/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/CLAUDE.md index 4ca195ea5..ea69ee556 100644 --- a/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/CLAUDE.md +++ b/fixtures/agent-context-conformance/ts-requirements/expected/.metaobjects/CLAUDE.md @@ -37,6 +37,7 @@ Not there? List the missing generator — and a `docs` target, if `targets` has - Pattern-derivable from metadata = codegen, never hand-write — FKs, CRUD, validators, finders, and the database schema and migrations. The schema is a disposable, generated artifact: change the metadata and regenerate. Don't hand-write divergent SQL; where a stack owns its own migration files, GENERATE them — `meta migrate --migration-format flyway` emits the paired `V__`/`U__` files a Flyway runner expects, and leaves applying them to Flyway. The one exception is a schema this project deliberately owns outside MetaObjects (hand-written migrations by rule, or another team's database): when the root `CLAUDE.md` says so, follow it, and keep `meta verify --db` catching drift. - **Once this project declares any `template.prompt`, every LLM prompt is one** — the text in the template, the data in a payload value object, rendered by the render engine. A structured reply is read through a response model (`@responseRef` and its generated extractor), never a regex or string search. This covers code that does not look like MetaObjects: a format string, text block, template literal or string builder sent to an LLM, and it covers every module, including test harnesses and scripts. Changing a prompt that is still hand-built means migrating it first (snapshot its current output and keep the rendered text byte-identical), not adding a line to it; the `metaobjects-prompts` skill has the steps. - The **live database** is a derived artifact too — never hand-apply a schema change to a running DB (ad-hoc `psql`/console `ALTER`/`CREATE`/`DROP`), not even to preview a column or unblock a boot. Drive every schema change from the metadata, never ad-hoc: on the Node/TS-owned migrate stack that is `meta migrate` (metadata → DDL, ADR-0015); on a **Flyway**-owned JVM stack that is `meta migrate --migration-format flyway`, which emits the migration and deliberately refuses `--apply` because Flyway owns applying it. Hand-authoring a migration to match the generated schema is the LAST resort — for a runner MetaObjects has no writer for — never the default for Flyway. A hand-applied ad-hoc change drifts the live DB from the metadata + migration history and collides at the next migrate/boot ("column already exists") — a state no migration can reproduce. Run `meta verify --db ` after any DB-touching work to catch that drift early — the URL is required, and the bare form exits 2. +- **If this project declares `requirement.*` nodes, a commit that adds, changes or removes behaviour updates them in the same commit** (the claim, its `@status` and `@implementedBy`), or says in the commit message why not. `meta verify` checks that the links resolve, not that the ledger is current. - Never hand-edit generated **output** — change the metadata and regenerate. (That bounds the files codegen *emits*; it says nothing about the generator that emits them — next bullet.) What survives a regen DIFFERS BY TOOLCHAIN, so never rely on it: the Node/TS `meta gen` path three-way-merges, so an edit inside a generated file is preserved (and is refused rather than guessed at when it cannot tell yours from its own stale output); the JVM generators overwrite, and several Kotlin ones — including `.kt` — truncate unconditionally with no marker check. Put your own code in a subclass or a separate file, never in the generated one. - **Nothing is generated until you wire it — one generator list per port, documentation included.** No port ships a default generator suite, and there is no separate docs command or docs config block on any port: a docs page is one more entry in the same list as every other generator. The list lives in `generators` in `metaobjects.config.ts` (TypeScript; a fresh `meta init` writes `generators: []`), `codegen/Generators.cs` (C#, scaffolded by `dotnet meta init`), a target's `generators` in `metaobjects.config.yaml` (Python), or `` in the Maven plugin's `` (Java, Kotlin). `meta gen --list --probe` is the catalog — it runs every generator against your model and reports how many files each would emit — and each generator runs as shipped from its package: import it on the Node/TS path (the catalog row prints the line), select it by name on the others. `--generators ` (TypeScript, C#, Python; a second Maven `` of `generate`) NARROWS a run to entries the list already declares — it never adds one, and naming an undeclared entry is refused. Copy nothing you are not going to change. `meta eject ...` copies one into your repo when you need to change what it emits. - **Need an output the model describes that nothing here emits? WRITE A GENERATOR — that is the primary path, not an escape hatch.** OpenAPI, JSON Schema, Zod, DTOs, a client, a service layer, docs: MetaObjects is the core (model, loader, `verify`, `migrate`, render, extract), and the generators on it are yours to write. A generator is a name plus a function from the model to `{ path, content }` files, about 20 lines; `verify --codegen` gates it with nothing to register. Read the model through the RESOLVING accessors (`fields()`, `attr()`, `resolvedIsArray()` — never `own*()`), skip abstract objects, and resolve `@objectRef` with the port's helper. On the Node/TS path, `meta generator new ` scaffolds a working one and wires it into `metaobjects.config.ts`; the `metaobjects-codegen` skill has every port's shape. Run a reference generator as shipped when it already emits what you want, and eject one only when it is close and you need to change it; never hand-write output the model fully describes.