Skip to content

Commit dbe156e

Browse files
dmealingclaude
andcommitted
fix(agent-context): the agent-facing docs learn ADR-0052's direction split and the sources set
Two shipped-and-wrong agent-facing surfaces. The human-facing docs were correct in both cases — only the agent context missed the pass, which is the worse half to miss, because an agent reads it as ground truth and acts on it without a second source. 1. @PromptStyle WAS DOCUMENTED EXACTLY BACKWARDS. capability-checklist.md claimed it "is on `template.output` ONLY; authoring it on `template.prompt` fails load with ERR_UNKNOWN_ATTR". The registry says the opposite — @PromptStyle is registered on `template.prompt` (spec/metamodel/template.json:120, byte-gated at expected-registry.json:4147). An agent following the checklist would have authored the attribute on the one subtype where it actually fails to load, then been told by the same file that the resulting error was impossible. The claim appeared TWICE in that block (the inventory line and the parenthetical), so deleting either alone left it standing. ADR-0052 Phase G swept the `metaobjects-prompts` skill and its five per-port references; `metaobjects-codegen` and `metaobjects-audit` were missed. Both now carry the ruling: a template subtype's axis is DIRECTION — `template.prompt` owns both halves of talking to a model (a prompt carrying @responseRef owns the parser, the tolerant extract and the FR-010 output-format fragment), while `template.output` is outbound only and emits no parser. The audit heuristics were the load-bearing part: five of them told an auditor to flag a hand-rolled parser "where a `template.output` node exists", which is now the wrong node to look for — that check would miss every real finding and manufacture false ones. All re-keyed onto a responding `template.prompt`. Attr inventories reconciled against the registry rather than hand-inverted, which turned up four MORE errors the report did not mention: template.prompt was missing @PromptStyle and @responseFormat, template.output was missing @payloadRef/@textRef/@format/@maxchars, and template.toolcall was missing @maxtokens. The emitted TS filenames were stale too (.output.ts is now <Name>.response.ts, plus .responseFormat.ts / .extractor.ts) — verified against the generators, not the ADR, since an accepted ADR is a decision and the emitter is the fact. Generator CLASS names (SpringOutputParserGenerator et al) are unchanged and kept. 2. `sources` WAS ENTIRELY ABSENT from the scaffolded agent context — `grep -c sources` on agent-docs/body.ts returned 0 — while :225 actively asserted `.metaobjects/config.json` "is unchanged — it still holds static project state". It holds `sources`, the release's headline feature (config.ts:132). So every project scaffolded by `meta init` got agent docs that could not describe where its own metadata comes from, and positively said nothing had changed. Adds a "Where metadata comes from" section stating the rule the feature doc reserves this very file for: `metaobjects/` IS THE DEFAULT VALUE of `sources`, never a requirement, and must not be assumed to exist. Covers the declaration shape, that all five commands read the same set, that a path is read in place and never installed, four-CLI-surface support, set-not-list ordering, and strict unknown-key rejection. This closes the wording gap no-hardcoded-metadata-dir.test.ts's own allowlist entry recorded as known-and-open; that reason is updated rather than left describing a gap that no longer exists. All five byte-gated agent-context-conformance expected trees regenerated via sdk/scripts/regen-agent-context-conformance.ts. Verified: sdk 278 pass / 0 fail. cli 596 pass / 2 fail — both pre-existing on main (verify-requirements-e2e "dangling reference on status=abandoned exits 0" and requirement-check "allowedValues"), fixed in a peer's unpushed commit, neither touched here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3d3695c commit dbe156e

40 files changed

Lines changed: 272 additions & 154 deletions

File tree

agent-context/skills/metaobjects-audit/SKILL.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,12 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
347347
`object.value` fail load with `ERR_SUBTYPE_RULE_VIOLATION`; `@payloadRef` accepts the sourceless
348348
projection).
349349
- Silent-degradation hack (`try/except KeyError` or `?? ''` around formatting) — flag every instance.
350-
- Hand-rolled output parsing (regex / XML / ad-hoc JSON) vs declared `template.output` +
351-
generated `parse*` / `safeParse*` / `extract*` parser — **generated in all five ports**
352-
(Java's generated `<Name>Parser` owns the Jackson `readValue`); flag a hand-rolled parser
353-
in a **non-generated** file where a `template.output` node exists.
350+
- Hand-rolled output parsing (regex / XML / ad-hoc JSON) vs a declared **responding
351+
`template.prompt`** (one carrying `@responseRef`) + generated `parse*` / `safeParse*` /
352+
`extract*` parser — **generated in all five ports** (Java's generated `<Name>Parser` owns
353+
the Jackson `readValue`); flag a hand-rolled parser in a **non-generated** file where a
354+
responding `template.prompt` exists. ADR-0052: a `template.output` is outbound only and
355+
generates no parser, so it is not the node to look for here.
354356
- Engine-side formatting breaking byte-identical render (prompt-cache exact-prefix hits
355357
depend on byte-stability).
356358
- `template.toolcall` candidates: LLM tool schemas hand-defined per call vs modeled
@@ -477,7 +479,8 @@ The audit never edits code. Pattern: **dry-run → review the diff → apply**.
477479
leading-wildcard gating — do NOT flag the absence of those in a non-TS port.
478480
- **Output-parser codegen** ships in **all five ports** — Java's `SpringOutputParserGenerator`
479481
*generates* the `<Name>Parser` (the Jackson `readValue` lives inside that generated file). A
480-
hand-rolled parser in a **non-generated** file where a `template.output` node exists IS a finding.
482+
hand-rolled parser in a **non-generated** file where a **responding `template.prompt`**
483+
(`@responseRef`) exists IS a finding. Per ADR-0052 a `template.output` emits no parser.
481484
- **Python** still hand-wires the FastAPI router + repository impl around a generated
482485
`APIRouter`; relationship / non-`table` source-kind / `field.object flattened` codegen is partial.
483486
- **C#** has no ObjectManager runtime tier (EF Core is the runtime) — hand services over the generated `DbContext` are expected.

agent-context/skills/metaobjects-audit/references/capability-checklist.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,26 @@ soft-delete / status / type view it models.
171171

172172
## Template — `template.*` (prompt pillar)
173173

174-
- **`template.prompt`** (`@payloadRef`, `@textRef`, `@responseRef`, `@requiredSlots`,
175-
`@requiredTags`, `@maxTokens`, `@maxChars`, `@format`, `@model`) — hunt prompt strings
176-
assembled inline in services, payloads built ad-hoc, output parsing without a typed
177-
`@responseRef`, or token/char budgets enforced by hand.
178-
- **`template.output`** (`@kind` = `document` | `email`; `@subjectRef`, `@htmlBodyRef`,
179-
`@textBodyRef`, `@promptStyle`, `@requiredTags`) — hunt hand-built document/email rendering +
180-
hand-written parse-on-receipt the output template + generated render helper/parser cover.
181-
(`@promptStyle` — the FR-010 output-format presentation — is on `template.output` ONLY;
182-
authoring it on `template.prompt` fails load with `ERR_UNKNOWN_ATTR`.)
183-
- **`template.toolcall`** (`@toolName`, `@payloadRef`) — hunt hand-declared LLM tool schemas
184-
a modeled tool call describes.
174+
**A template subtype's axis is DIRECTION** (ADR-0052): `template.prompt` owns everything about
175+
talking to a model — both the request and the reply — while `template.output` renders an
176+
artifact for a person or a file and generates **no parser**.
177+
178+
- **`template.prompt`** (`@payloadRef`, `@textRef`, `@responseRef`, `@responseFormat`,
179+
`@promptStyle`, `@requiredSlots`, `@requiredTags`, `@maxTokens`, `@maxChars`, `@format`,
180+
`@model`) — hunt prompt strings assembled inline in services, payloads built ad-hoc,
181+
token/char budgets enforced by hand, and **hand-written parse-on-receipt**: a prompt
182+
declaring `@responseRef` owns the inbound half, so the strict parser, the tolerant
183+
`extract` mapper and the FR-010 output-format fragment are all generated from it.
184+
(`@promptStyle` — the FR-010 output-format presentation, `guide` / `inline` /
185+
`exampleOnly` — is on `template.prompt` ONLY; authoring it on `template.output` fails load
186+
with `ERR_UNKNOWN_ATTR`.)
187+
- **`template.output`** (`@kind` = `document` | `email`; `@textRef`, `@subjectRef`,
188+
`@htmlBodyRef`, `@textBodyRef`, `@payloadRef`, `@format`, `@maxChars`, `@requiredTags`) —
189+
**outbound only.** Hunt hand-built document/email rendering the output template + generated
190+
render helper cover. It parses nothing: a parser here would be reading back text the system
191+
just rendered and sent.
192+
- **`template.toolcall`** (`@toolName`, `@payloadRef`, `@maxTokens`) — hunt hand-declared LLM
193+
tool schemas a modeled tool call describes.
185194
- **`template.base`** — abstract base.
186195

187196
## Attr — `attr.*`
@@ -256,8 +265,10 @@ subtypes with opposite polarity: `requirement.functional` fails when NOTHING imp
256265
is **generated in all five ports** (api-contract corpus, both lanes) — **flag hand-rolled
257266
filter parsing anywhere.** Only the richer surface (`?search=`, explicit
258267
`filter[or][N]` / `filter[and][N]` combinators, leading-wildcard gating) is TS-only.
259-
Output-parser codegen also ships in **all five ports** — Java's `SpringOutputParserGenerator`
260-
*generates* the parser (the Jackson `readValue` lives inside that generated file). **Python**
268+
Output-parser codegen also ships in **all five ports**, keyed on a **responding
269+
`template.prompt`** (`@responseRef`), never on a `template.output` (ADR-0052) — Java's
270+
`SpringOutputParserGenerator` *generates* the parser (the Jackson `readValue` lives inside
271+
that generated file). **Python**
261272
still hand-wires the FastAPI router around a generated `APIRouter` (relationship /
262273
non-`table` source-kind / flattened-object codegen is partial). **C#** has no
263274
ObjectManager runtime tier (EF Core *is* the runtime) — hand services over the generated

agent-context/skills/metaobjects-audit/references/csharp.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,5 @@ version across these packages is an intra-port skew finding.
104104
TS-only — do NOT flag its absence in C#.
105105
- **Output-parser codegen ships in C#.** `output-parser` / `extractor` / `render-helper`
106106
generators are available (`dotnet meta gen --generators output-parser`). Absence of
107-
wired output parsers where `template.output` nodes exist IS a finding.
107+
wired output parsers where a **responding `template.prompt`** (`@responseRef`) exists IS a
108+
finding. ADR-0052: a `template.output` is outbound only and generates no parser.

agent-context/skills/metaobjects-audit/references/java.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ plugin `<configuration>` — that is the source of truth for which generators ru
4949
| Hand-written `@RestController` on a CRUD entity | `SpringControllerGenerator` should own this; trust the `pom.xml` `<generators>` configuration, not stale docs |
5050
| `interface <Entity>Repository` with no `@generated` comment | `SpringRepositoryGenerator` emits the stub; compare field by field |
5151
| `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
52-
| `ObjectMapper.readValue(` outside a `*Parser.java` file | check if a `template.output` node exists — output-parser codegen ships in Java |
52+
| `ObjectMapper.readValue(` outside a `*Parser.java` file | check if a responding `template.prompt` (`@responseRef`) exists — output-parser codegen ships in Java |
5353
| `LIMIT ?` / `OFFSET ?` assembled by hand | generated CRUD routes handle pagination; OMDB `getObjects` accepts `QueryOptions` |
5454

5555
---
@@ -92,7 +92,8 @@ versioning — **do not flag it** (only intra-port skew matters).
9292
*generates* the typed `<Name>Parser` class; the Jackson `readValue` call lives inside that
9393
generated file. It is NOT a defect to see Jackson deserialization in a generated `*Parser.java`
9494
file. **Do not flag Jackson `readValue` calls in generated `*Parser.java` files.** DO flag a
95-
hand-rolled parser in a *non*-generated file where a `template.output` node exists.
95+
hand-rolled parser in a *non*-generated file where a responding `template.prompt`
96+
(`@responseRef`) exists — per ADR-0052 a `template.output` emits no parser.
9697
- **Core filter-operator codegen ships in Java — do NOT treat it as deferred.**
9798
`SpringControllerGenerator` generates the `?filter[field][op]=value` grammar (all 9 operators
9899
`eq/ne/gt/gte/lt/lte/in/like/isNull`): it parses via the runtime `FilterParser`, validates

agent-context/skills/metaobjects-audit/references/kotlin.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ cross-port versioning — **do not flag it** (only intra-port skew matters).
101101
`KotlinOutputParserGenerator` generates the typed parser class AND its deserialization body
102102
(a kotlinx `Json.decodeFromString` call inside the generated file — kotlinx, not Jackson: the
103103
#187 Jackson move was jsonb-codec-only, prompt payloads/parsers stay on kotlinx). Only flag
104-
a hand-rolled parser in a NON-generated file where a `template.output` node exists.
104+
a hand-rolled parser in a NON-generated file where a responding `template.prompt`
105+
(`@responseRef`) exists — per ADR-0052 a `template.output` emits no parser.
105106
- **Core filter-operator codegen ships in Kotlin — do NOT treat it as deferred.**
106107
`KotlinSpringControllerGenerator` generates the `?filter[field][op]=value` grammar (all 9
107108
operators `eq/ne/gt/gte/lt/lte/in/like/isNull`) validated against the generated filter allowlist

agent-context/skills/metaobjects-audit/references/typescript.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ via `metamodelVersion` (see the Phase 0 cross-language consistency item).
107107
TypeScript is the reference implementation — it ships the full feature set:
108108

109109
- Filter-operator codegen: **complete** — the core `?filter[field][op]` grammar (all 9 operators `eq/ne/gt/gte/lt/lte/in/like/isNull`, `<Entity>FilterAllowlist`) generates in every port; TS additionally ships the richer surface (`?search=`, `filter[or][N]` / `filter[and][N]` combinators, leading-wildcard gating).
110-
- Output-parser codegen: **complete** (`outputParser()` generator; `parse*`/`safeParse*`/`extract*` per `template.output`).
110+
- Output-parser codegen: **complete** (`outputParser()` generator; `parse*`/`safeParse*`/`extract*` emitted as `<Name>.response.ts` per **responding `template.prompt`** — one carrying `@responseRef`; ADR-0052).
111111
- ObjectManager: **complete** (`@metaobjectsdev/runtime-ts`; `kyselyDriver` / `inMemoryDriver`).
112112
- Schema migrate / `meta verify --db`: **complete** (TS owns the shared migrate engine — ADR-0015).
113113
- D1 dialect: **TS-only by design**`dialect: "d1"` in `metaobjects.config.ts` targets Cloudflare D1 via the Wrangler CLI layout. Do NOT recommend porting D1 support to other languages.

agent-context/skills/metaobjects-codegen/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ name** (kebab-case) that surfaces in diagnostics — reference generators by tha
6161
name, never by inlining what they emit. Typical generators cover: the entity
6262
type/model, the DB table/schema, query/finder helpers, REST routes, client
6363
form/grid/hook artifacts, filter + sort allowlists, payload value-objects, and
64-
`template.output` parsers. You enable the subset your project needs; an abstract
65-
entity never emits instance/write artifacts regardless.
64+
parsers for a responding `template.prompt` (one carrying `@responseRef`). You
65+
enable the subset your project needs; an abstract entity never emits
66+
instance/write artifacts regardless.
6667

6768
Per-entity opt-outs exist (e.g. skipping client-side artifacts for a given
6869
entity) and are set as attributes on the entity in metadata, not in code.

agent-context/skills/metaobjects-codegen/references/csharp.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ or run the default set. Output lands under `--namespace` in `--output-dir`.
4545
| `routes` | `<Entity>Routes.cs` — ASP.NET **Minimal API** CRUD per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). A TPH base emits polymorphic `GET /<base>(+/{id})` + a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` (create injects the discriminator, cross-subtype get/update/delete → 404). |
4646
| `filter-allowlist` | per-entity `<Entity>FilterAllowlist` (FR-009 — the server-side field+operator allowlist the routes validate against). |
4747
| `callable` | `<Entity>.callable.g.cs` — an FR-015 calling method for a `source.rdb @kind="storedProc"|"tableFunction"`, via EF `FromSqlInterpolated` (args from the `@parameterRef` value object in declaration order). |
48-
| `payload` | `<Payload>.payload.cs` — the strict typed payload `record` (+ any nested element records) per `template.output` `@payloadRef` (an `object.value`) that the parser/extractor bind to. |
49-
| `output-parser` / `extractor` / `output-prompt` / `render-helper` | the `template.output` prompt-pillar artifacts — the strict parser, the tolerant `extract`, the **output-format prompt fragment** (`output-prompt`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), and the typed render helper. See the **prompts** reference. |
48+
| `payload` | `<Payload>.payload.cs` — the strict typed payload `record` (+ any nested element records) per `template.prompt` `@payloadRef` / `@responseRef` (each an `object.value`): `@payloadRef` types the REQUEST, `@responseRef` the REPLY the parser/extractor bind to. |
49+
| `output-parser` / `extractor` / `output-prompt` / `render-helper` | the prompt-pillar artifacts for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: these tiers are INBOUND; `template.output` is outbound only and emits no parser). The strict parser, the tolerant `extract`, the **output-format prompt fragment** (`output-prompt`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), and the typed render helper. See the **prompts** reference. |
5050
| `template` | the generic Mustache `templateGenerator()` primitive. |
5151

5252
Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the

agent-context/skills/metaobjects-codegen/references/java.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ separate `metaobjects-codegen-base` module instead.)
101101
| `SpringRepositoryGenerator` | `<Entity>Repository.java` — a hand-stubbed `interface` the consumer implements with their persistence layer (Spring Data JPA / jOOQ / JDBC). For a TPH base the interface is polymorphic + per-subtype-scoped (`listByType`/`findByIdAndType`/`createWithType`/`updateByIdAndType`/`deleteByIdAndType`) over the single table; subtype entities emit no own controller/DTO/repository — they fold into the base. |
102102
| `SpringValueObjectGenerator` | a Java 21 `record` per `object.value` reached through a `field.object @storage: jsonb` column (single or `@isArray`, transitively through nested VOs) — the typed component the Jackson jsonb codec serializes to/from (carries jakarta validation, unlike a plain payload record). Program D typed-jsonb VOs. |
103103
| `SpringPayloadGenerator` | a Java 21 `record` per `template` payload VO |
104-
| `SpringOutputParserGenerator` | the `template.output` strict parser-on-receipt (see the prompts reference) |
105-
| `SpringOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.output` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
104+
| `SpringOutputParserGenerator` | the strict parser-on-receipt for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: INBOUND; a `template.output` emits no parser). See the prompts reference. |
105+
| `SpringOutputPromptGenerator` | the FR-010 output-format prompt fragment for a responding `template.prompt` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
106106
| `SpringRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
107107
| `LlmTraceHelperGenerator` | `<Entity>TraceHelper.java` per concrete entity — the LLM-trace helper |
108108
| `SpringFilterAllowlistGenerator` | per-entity filter allowlist |

agent-context/skills/metaobjects-codegen/references/kotlin.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ All live in `metaobjects-codegen-kotlin` under
114114
| `KotlinSpringControllerGenerator` | `<Entity>Controller.kt` — Spring `@RestController`, five CRUD endpoints on the cross-port REST contract, for writable entities (`source.rdb` `@kind="table"`). A TPH `@discriminator` base emits ONE controller: polymorphic `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete are scoped to the subtype (cross-subtype → 404); the discriminator is immutable. |
115115
| `KotlinRepositoryGenerator` | `<Entity>RepositoryBase.kt` — an `open class` consumer persistence seam per writable entity (the Kotlin peer of Java's `SpringRepositoryGenerator`, but with method bodies). Carries the #203 `@autoSet` CRUD stamping: `onCreate` columns stamped once at insert, `onUpdate` columns re-stamped on every write, both excluded from the caller-supplied set. |
116116
| `KotlinPayloadGenerator` | `<Template>Payload.kt``@Serializable` payload data class from a template's `@payloadRef` |
117-
| `KotlinOutputParserGenerator` | the `template.output` strict parser-on-receipt (see the prompts reference) |
118-
| `KotlinExtractorGenerator` | the FR-010 tolerant `extract` mapper for a `template.output` (all-nullable mirror → strict payload) |
119-
| `KotlinOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.output` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
117+
| `KotlinOutputParserGenerator` | the strict parser-on-receipt for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: INBOUND; a `template.output` emits no parser). See the prompts reference. |
118+
| `KotlinExtractorGenerator` | the FR-010 tolerant `extract` mapper for a responding `template.prompt` (all-nullable mirror → strict payload) |
119+
| `KotlinOutputPromptGenerator` | the FR-010 output-format prompt fragment for a responding `template.prompt` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
120120
| `KotlinRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
121121
| `KotlinValidatorGenerator` | `MetadataStartupValidator.kt` + `ExposedTableValidator.kt` (once per project) |
122122
| `KotlinSpringConfigGenerator` | `MetadataExposedConfig.kt``@Configuration` wiring `Database.connect()` + the startup validator (once per project) |

0 commit comments

Comments
 (0)