@metaobjectsdev/sdk 0.15.20 → 0.15.21-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,4 +11,12 @@ the slim always-on Markdown (`.metaobjects/AGENTS.md` + `CLAUDE.md`) and the six
11
11
  - `skills/<skill>/SKILL.md` — universal skill body.
12
12
  - `skills/<skill>/references/<token>.md` — language fragment; installed iff `<token>` is in the stack.
13
13
 
14
+ The **scaffolded** set is exactly the six skills in the SDK's `SKILL_NAMES`
15
+ (`src/agent-context/types.ts`) — the assembler emits only those. `skills/`
16
+ additionally holds **`metaobjects-fit-assessment`**, a *pre-adoption* tool that is
17
+ deliberately **not** in `SKILL_NAMES` and so is never scaffolded (a not-yet-adopted
18
+ target has no `.claude/skills/metaobjects-*` to receive it); it carries
19
+ `scaffold: false` in its front-matter as the human marker. Its design + retro-test
20
+ validation live in `docs/superpowers/specs/2026-07-12-metaobjects-fit-assessment-design.md`.
21
+
14
22
  Design: `docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md`.
@@ -0,0 +1,536 @@
1
+ ---
2
+ name: metaobjects-fit-assessment
3
+ description: Use BEFORE adopting MetaObjects — assess whether a not-yet-adopted project is a fit, what would migrate, the end-state, and the drift-protection payoff; produces a decision-grade, evidence-cited report. Read-only, propose-only.
4
+ scaffold: false
5
+ ---
6
+
7
+ <!--
8
+ NOT SCAFFOLDED. This is a PRE-adoption tool, so it is deliberately absent from the
9
+ SDK's `SKILL_NAMES` (`server/typescript/packages/sdk/src/agent-context/types.js`) and
10
+ is never emitted by `meta init` — a target that has not adopted MetaObjects has no
11
+ `.claude/skills/metaobjects-*` to drop it into. This directory is its single source of
12
+ truth. Its sibling is `metaobjects-audit` (POST-adoption; scaffolded). Design +
13
+ retro-test validation: `docs/superpowers/specs/2026-07-12-metaobjects-fit-assessment-design.md`.
14
+ How to run it: point a high-end LLM at a target repo + this file (e.g. paste it, or
15
+ fetch it from GitHub / metaobjects.dev) and let it produce the report below.
16
+ -->
17
+
18
+ # MetaObjects Fit & Migration Assessment
19
+
20
+ _Assessment prompt v1 (post-Phase-0 refinement). Grounded against MetaObjects npm `0.15.x` /
21
+ Maven `7.7.x` — verify every capability claim against the current release before asserting it._
22
+
23
+ You are an AI assistant running a **pre-adoption fit assessment** for MetaObjects
24
+ (https://github.com/metaobjectsdev/metaobjects — the cross-language metadata standard:
25
+ typed entity metadata as the durable spine; generated code as the disposable artifact;
26
+ four pillars: codegen, runtime metadata, drift detection, prompt construction; five ports:
27
+ TypeScript, C#, Java, Kotlin, Python).
28
+
29
+ You are sitting inside a target project that has **not adopted MetaObjects**. Produce a
30
+ decision-grade report answering: is this project a fit, what would migrate, what does the
31
+ after-state look like, what are the benefits — with **drift protection as the centerpiece** —
32
+ and which metadata vocabulary the project isn't asking for but would profit from.
33
+
34
+ ---
35
+
36
+ ## Inputs — elicit if absent
37
+
38
+ 1. Repo access (required). For monorepos: which subdirectory/service to scope to.
39
+ 2. Stack confirmation (detect, then confirm): language(s), DB, ORM, web framework.
40
+ 3. **Trajectory — you MUST ask; it is NOT in the code (see M8).** Is this system expected to
41
+ **grow** (more entities, a second language or service, an LLM surface, a team beyond one
42
+ person), or is it feature-complete and stable at its current size? Is it long-lived, or
43
+ disposable? A repo that will triple and a repo that is finished look **identical in git** —
44
+ and this is the single fact that decides the size-based verdict. If the human doesn't
45
+ answer, do not guess: state the assumption you made and mark the verdict conditional on it.
46
+ 4. Whether an LLM/prompt surface exists (scopes pillar 4).
47
+ 5. Org constraints that gate verdicts: who owns the schema (DBA-gated?); is generated
48
+ code in the repo acceptable?
49
+ 6. Where to write output. Default: `metaobjects-fit/` at the target repo root.
50
+ **Never create `.metaobjects/`** — that directory is the marker of an adopted project.
51
+
52
+ No secrets, no live-DB connection. You read code and migrations only; `verify --db` is
53
+ *described* in the report, never executed.
54
+
55
+ ## Deliverables
56
+
57
+ - `metaobjects-fit/fit-assessment.md` — the human report (§ Report contract).
58
+ - `metaobjects-fit/fit-assessment.json` — the machine twin (§ JSON contract). Every
59
+ prediction in the prose must have a JSON twin; a claim that can't be expressed as a
60
+ typed, checkable finding is hand-waving — cut it or fix it.
61
+
62
+ ---
63
+
64
+ ## Non-negotiable method rules
65
+
66
+ **M1 — Read-only, propose-only.** Never edit code, never author metadata files, never
67
+ install anything. Every `metadata_sketch` is a read-only proposal. The bridge to action
68
+ is `meta init` + the adoption skills — point there at the end.
69
+
70
+ **M2 — Evidence discipline.** Every claim about the target cites `file:line` or
71
+ `commit-sha`. Read the code behind every grep hit before citing it — a "duplicate"
72
+ validator's *divergence* is the finding, not the grep hit. An ambiguous archaeology hit
73
+ is dropped, not stretched.
74
+
75
+ **M3 — Ground every capability claim.** Before asserting MetaObjects can generate/gate/
76
+ model something, confirm it against the MetaObjects repo: the registry manifest
77
+ (`fixtures/registry-conformance/expected-registry.json` — the closed vocabulary),
78
+ `docs/features/*.md`, and the actual generator source for the target's port
79
+ (`server/typescript/packages/codegen-ts*`, `server/java/codegen-spring`,
80
+ `server/java/codegen-kotlin` (KotlinPoet generators), `server/csharp/*Codegen*`,
81
+ `server/python/`). A capability you cannot point at in that repo does not go in the
82
+ report. Never cite an unregistered subtype or attribute (ADR-0023: the registry is
83
+ sealed; invented attrs fail load with `ERR_UNKNOWN_ATTR`).
84
+
85
+ **M4 — Not a brochure.** Bias to under-flagging on drift findings (>15% false positives
86
+ kills the assessment). The mandatory "what you will NOT get" section and the per-port
87
+ calibration caps are structural: never promise a capability the target's port lacks.
88
+
89
+ **M5 — Metadata follows the code.** The migration you propose authors metadata to
90
+ REPRODUCE the existing tables, names, types, and nullability exactly. No renames, no
91
+ cleanups, no change-metadata→regen→fix-working-code. Ambiguity goes to the human as a
92
+ marked decision point. Parity-gate every wave before deleting hand-written code.
93
+
94
+ **M6 — Author from the live schema, not the ORM annotations.** Where a live schema (or
95
+ its migration history) and ORM annotations disagree, the schema is the truth — the
96
+ annotations lie (that disagreement is itself a drift finding). State this rule in the
97
+ migration plan.
98
+
99
+ **M7 — Floor and ceiling, both labeled (anti-under-promise guard).** Phase-0 calibration
100
+ showed the failure mode is conservative UNDER-prediction, not over-promising. Your
101
+ verdicts, plan, and benefit numbers stay on the **floor** (the conservative,
102
+ metadata-follows-the-code path). But you must ALSO name the **deep-adoption ceiling**
103
+ for the target's port (§ P5-b) in one clearly-labeled paragraph, tagged
104
+ `horizon: "later"` in JSON and never counted in the benefit numbers. Omitting a real
105
+ option a deep adopter would take is a scored defect, just like inventing one.
106
+
107
+ **M8 — The verdict-deciding fact is not always in the code. Size you can count;
108
+ TRAJECTORY you cannot.** The honest NOT-A-FIT splits in two, and only one half is
109
+ observable:
110
+ - **Structural disqualifiers ARE in the repo** — language outside the five ports, no
111
+ relational store, a non-entity-shaped domain, a DBA-gated schema. Read them, cite them,
112
+ rule on them. These are the only grounds on which you may write a flat `NOT A FIT`.
113
+ - **The economic disqualifier is NOT in the repo.** "Too small for the tooling to pay"
114
+ depends on whether the system will **grow** — and a 3-entity app that will become 90 is
115
+ byte-identical in git to a 3-entity app that is finished forever. **Never infer "this will
116
+ never grow" from a small codebase.** Absence of growth-so-far is not evidence of
117
+ no-growth-to-come (a young repo is small *because it is young*). This is the one place your
118
+ evidence discipline (M2) actively misleads you: there is no `file:line` for intent.
119
+
120
+ So: **ask** (Input 3). If answered, rule on it and say whose answer it was. If unanswered,
121
+ do NOT pick a side — emit **both branches** explicitly, e.g.: *"At 4 entities today the
122
+ spine's leverage does not repay the tooling — if this system is done, don't adopt. If you
123
+ expect it to grow past ~10 entities, add a second language, or add an LLM surface, adopt at
124
+ that point; and note adoption gets more expensive later, because you will be retrofitting a
125
+ spine onto more drift, not less."* Tag such claims `confidence: low` and
126
+ `checkable: false` — they are conditional on a fact you were not given.
127
+
128
+ Corollary for scoring/QA: a "negative control" for this assessment cannot be *any small
129
+ repo* — it must be one whose trajectory is **known** (e.g. retrospectively: it stayed small
130
+ for years). Anything else tests a fact the code does not contain.
131
+
132
+ ---
133
+
134
+ ## The passes — run all of these BEFORE writing the report
135
+
136
+ ### P1 — Stack detection + calibration lock-in
137
+
138
+ Detect language(s), DB, ORM, migration tool, web framework, LLM SDKs. Immediately look
139
+ up the target port's row in the § Calibration table and treat it as a hard cap on every
140
+ promise in the rest of the report.
141
+
142
+ ### P2 — Census (the denominator for everything)
143
+
144
+ Count, with locations:
145
+
146
+ **P2-a — Table-first entity census (mandatory reconciliation).**
147
+ The spine models the **database, not the ORM layer**. Real migrations model every live
148
+ table — including junction tables, operational tables (audit logs, queues, tokens), and
149
+ tables the ORM never mapped (touched only by hand SQL/DAOs). An entity-count prediction
150
+ that equals the ORM-class count is a known under-prediction mode. So:
151
+
152
+ 1. Reconstruct the **live table set**: walk the migration history (`CREATE TABLE` minus
153
+ `DROP TABLE`) or the checked-in schema; count tables.
154
+ 2. Count **ORM/model classes on every lane** — not just the primary one: JPA `@Entity`
155
+ PLUS `@MappedSuperclass`/`@Embeddable`; secondary mapping layers (e.g. a Spring-Data-
156
+ JDBC shadow set); Drizzle tables AND Zod schemas; Pydantic/dataclass models; EF Core
157
+ entity classes. Multiple lanes mapping one table = a drift finding (P3), not double
158
+ counting.
159
+ 3. Count **read-model shapes**: list/summary DTOs, hand `SELECT` with joins/GROUP BY
160
+ feeding a DTO, hand-written SQL views → future `object.projection`.
161
+ 4. Count **payload/value shapes**: JSON/JSONB column shapes (P2-b), embedded/owned
162
+ types, LLM payload dicts → future `object.value`.
163
+
164
+ Then emit the **reconciliation block** (mandatory in report AND in JSON):
165
+
166
+ | Quantity | Count | Basis |
167
+ |---|---|---|
168
+ | Live tables | N | migration walk |
169
+ | ORM/model classes (all lanes) | N | annotation/class census |
170
+ | → predicted `object.entity` | ≈ live tables (state the delta and why) | every live table incl. junctions + ORM-less tables |
171
+ | → predicted `object.projection` | N | read models, list DTOs, hand views |
172
+ | → predicted `object.value` | N | JSONB shapes, owned types, prompt payloads |
173
+
174
+ Name explicitly that read models and payloads become their **own** objects — the spine's
175
+ object count is normally LARGER than the ORM-class count.
176
+
177
+ **P2-b — Opaque-payload hunt (mandatory pass; do not fold into "misc").**
178
+ Hunt every opaque JSON/JSONB column and schema-in-code-only payload:
179
+
180
+ - DDL/migrations: `jsonb`, `json` column types.
181
+ - Java/Kotlin: `ObjectMapper.readValue`/`writeValue`, `Map<String,Object>` fields,
182
+ `jsonb_set` in SQL strings, `@JdbcTypeCode(SqlTypes.JSON)`.
183
+ - TS: `JSON.parse` + `as`/`any`, `z.unknown()`/`z.record()` on a column, Drizzle
184
+ `jsonb(...)` without a typed `$type`.
185
+ - Python: `dict` fields, `json.loads` on a column, SQLAlchemy `JSON`/`JSONB` untyped.
186
+ - C#: `JsonDocument`/`JObject` columns, string columns holding serialized JSON.
187
+
188
+ Each hit is a candidate **`object.value` + `field.object @objectRef @storage: jsonb`**
189
+ (+`@isArray` for arrays; typed single- and array-of-VO jsonb round-trip codecs are
190
+ conformance-gated in every port). Classify each column: (a) a typed class exists
191
+ somewhere for it (a drifting duplicate — ledger row), or (b) fully opaque — the shape
192
+ exists only as N implicit declarations scattered across reader/writer call sites and
193
+ zero checkable ones (drift signature 6 with implicit copies — often the WORST drift
194
+ exposure in the repo, because nothing can even diff it). Report the counts; carry the
195
+ modeling work as a **named migration-wave item**, not a footnote. (`field.map` is the
196
+ legal interim form for a genuinely open bag; a bag with known keys is a value object.)
197
+
198
+ **P2-c — UI-surface census (renderer-agnostic).**
199
+ A UI surface = any list/table/grid or form with a column set, sort order, filter, or
200
+ page size — **including server-rendered templates** (Thymeleaf/JSP/Razor/ERB/Django),
201
+ admin frameworks, and report/CLI table output. Count them all; do not scope this hunt
202
+ to React. Each grid-shaped surface's column/sort/pagination facts are candidates for
203
+ `layout.dataGrid` (`@columns`, `@defaultSortField`, `@defaultSortOrder`, `@pageSize`).
204
+
205
+ The UI verdict is **two lines, never one**:
206
+ - **UI metadata** — `layout.dataGrid` is cross-port registry vocabulary; authoring it is
207
+ cheap on ANY port and moves the column/sort/page-size facts into the spine even while
208
+ today's renderer is server-side (honest caveat: nothing generates the server-rendered
209
+ template from it — the fact capture and any future web client are the payoff).
210
+ - **UI codegen + runtime** — grid/form/hook generation and the runtime components are
211
+ TS/React/TanStack only. Only THIS line may say "nothing migrates" on a non-TS front end.
212
+
213
+ **P2-d — The rest of the census:** route/handler count (CRUD-shaped vs bespoke);
214
+ validation schemas and where they live; enums vs `CHECK` constraints; migration tooling
215
+ and count; LLM prompt-construction sites (builders, inline strings, parsers — with LOC);
216
+ test posture; out-of-tree consumers of the schema (scripts, sibling services, other
217
+ languages).
218
+
219
+ ### P3 — Drift ledger + git archaeology (the centerpiece)
220
+
221
+ For each drift exposure, a ledger row: **sources of truth** (each copy at `file:line`) →
222
+ **divergence today** (field-by-field diff — a live, current divergence is the money
223
+ finding) → **historical evidence** (archaeology) → **the gate that closes it** → the
224
+ signature class. Hunt all ten classes:
225
+
226
+ 1. Hand validators shadowing the persistence model.
227
+ 2. Field-by-field DTO↔model/row mappers.
228
+ 3. camelCase↔snake_case body↔column maps.
229
+ 4. Drift-admitting comments (`"keep in sync with"`, `"mirrors the"`, `"matching the"`).
230
+ 5. Runtime schema patching (`ALTER TABLE IF NOT EXISTS`, `_ensure_schema()`) — N schema owners.
231
+ 6. N declarations of one shape (the headline class) — **including the implicit-copy
232
+ variant from P2-b** (an opaque JSON column whose shape lives only in scattered
233
+ readValue/parse call sites).
234
+ 7. *(n/a pre-adoption — `own*()` is a MetaObjects-internal discipline.)*
235
+ 8. Hand-written `CREATE VIEW` / read-only SQL mirroring a read model. Run the
236
+ **necessity test**: expressible when every output column is a passthrough
237
+ (`origin.passthrough @from/@via`), a count/sum/avg/min/max (`origin.aggregate
238
+ @agg/@of/@via`, row-scoped with `@filter`), a child collection (`origin.collection`),
239
+ or `extends`-borrowed — and joins follow declared relationships/`identity.reference`
240
+ FKs. Expressible → projection candidate (note: an unmodeled hand view is *unmanaged*
241
+ — invisible to `verify --db`; modeling it is what makes it gateable). Not
242
+ expressible → BESPOKE with a NAMED construct (recursive CTE, window fn, set op,
243
+ `DISTINCT ON`, lateral join). "It's an aggregation" is not a justification.
244
+ 9. A closed variant-set hand-modeled per instance (N sibling modules on one payload
245
+ shape) → VOCAB CANDIDATE (advisory only; ADR-0037 ordered test).
246
+ 10. One prompt's text/payload/parse scattered across services — a renamed field silently
247
+ degrades the prompt with no build-time signal.
248
+
249
+ **Archaeology (prove it already drifted — "it will drift" lands 10× harder as "it
250
+ already did, twice"):**
251
+ - fix commits that patched ONE copy of a duplicated shape (a later commit patching the
252
+ other copy is the smoking gun);
253
+ - migrations whose titles/headers are drift confessions (`"to match entity"`, `"fix
254
+ ... constraint"`, quoted production errors);
255
+ - bug-fix messages with `sync` / `mismatch` / `out of date` / `forgot to update` / `bit-rot`;
256
+ - prompt-string edits with no corresponding payload/parser change (and vice versa);
257
+ - orphaned externalization attempts (config/template files nothing loads).
258
+
259
+ **Cost-of-change exhibit (mandatory when findable):** locate one representative
260
+ field-addition commit and quantify its fan-out (`git show --stat`): N files, M modules,
261
+ which duplicate layers it had to touch. This single exhibit is the drift surface made
262
+ concrete — lead with it.
263
+
264
+ **Gate mapping** — every ledger row names its closing mechanism:
265
+
266
+ | Exposure | Closing gate |
267
+ |---|---|
268
+ | model↔validator↔DTO duplicates (1,2,3,6) | one authored `object.entity`; copies become `@generated`; **`meta verify --codegen`** (TS) / **`mvn metaobjects:verify -Dmeta.verify.mode=codegen`** (JVM) / **`dotnet meta verify`** (C#) / **`metaobjects verify`** (Python) in CI |
269
+ | schema vs model (5 + the DDL copy in 6) | spine owns DDL via `meta migrate`; **`meta verify --db`** (Node `meta` only; PG/SQLite/D1) |
270
+ | opaque JSON columns (6-implicit) | `object.value` + `field.object @storage: jsonb` — the shape gets ONE checkable declaration + generated codecs |
271
+ | read-model SQL (8) | `object.projection` + `origin.*` generate the view DDL |
272
+ | scattered prompts (10) | `template.prompt` + typed payload VO + external text; **`meta verify --templates`** / `Renderer.verify` fails when a `{{field}}` no longer matches the payload; `template.output` (FR-006) + the tolerant `extract` parser and output-format fragment (FR-010) generate both sides of the tag contract |
273
+ | the metadata itself | strict provenance (ADR-0023): unknown attrs fail load |
274
+
275
+ State the honest limits in the same section: `verify` cannot catch semantic mismodeling
276
+ (a uuid modeled as string passes `--db`), cannot see unmodeled DB objects, and
277
+ `--templates` coverage depends on CLI version. A gate, not a proof system.
278
+
279
+ ### P4 — Fit rubric (worked, not vibes)
280
+
281
+ Positive signals (cite each): backend in one of the five ports; relational persistence
282
+ (especially PG/SQLite); ≥ ~5 entity-shaped things with CRUD-ish surfaces; the same shape
283
+ declared ≥2× today (the strongest predictor — it IS drift exposure); LLM prompt sites
284
+ (any count); >1 language consuming one model; existing/planned admin grids or forms.
285
+
286
+ Disqualifier table — **every row answered, not just failing ones** (a verdict without
287
+ this table worked row-by-row is invalid output):
288
+
289
+ | Check | Consequence |
290
+ |---|---|
291
+ | Backend language outside TS/Java/Kotlin/C#/Python | NOT A FIT (codegen/runtime); prompt pillar only if a portable sidecar makes sense — usually no |
292
+ | No relational store | persistence + `--db` N/A; assess prompt + value/projection pillars on their own merits |
293
+ | DB not Postgres/SQLite/D1 | schema pillar (`migrate`, `verify --db`) OUT — say so plainly; data-access unaffected |
294
+ | Non-entity-shaped domain (no persistent typed records to speak of) | NOT A FIT — structural, and visible in the code |
295
+ | Few entities today (< ~5) | **NOT a flat verdict — this is the M8 trap.** Small-today ≠ small-forever, and git cannot tell you which. Use the trajectory answer (Input 3): *done at this size* → MARGINAL/NOT A FIT (say so plainly — the leverage won't repay the tooling); *expected to grow / add a language / add an LLM surface* → FIT, and note adopting later costs more (you'd retrofit a spine onto more drift). **Unanswered → emit both branches, never guess.** |
296
+ | Schema owned by another team (DBA-gated) | migrate pillar restricted; model read-only ("metadata follows the schema"); flag the org constraint |
297
+ | Deep hand-tuned ORM investment | churn warning, not a disqualifier: the plan must reproduce those mappings (`@column`/`@table`/`@dbColumnType`) and price it |
298
+ | Team rejects generated code in the repo | flag; regen-every-build works but `verify --codegen` semantics differ — call the tradeoff |
299
+
300
+ ### P5 — Migration plan
301
+
302
+ **P5-a — Waves (the floor).** Wave 0: wedge — a handful of highest-churn tables authored
303
+ from the live schema (M6) + the drift gate (`verify --db` where the DB qualifies) wired
304
+ in CI, expected day-one catches named from the ledger; zero generated code, zero app
305
+ churn. Wave 1: retire the clearest deletable duplicate layer + generate the DTO/API
306
+ surface the port offers; codegen-drift gate in CI. Wave 2: full entity spine + schema
307
+ ownership handover (or read-only modeling), relationships (`identity.reference`,
308
+ FR-018 `@through` junctions), **the P2-b value-object modeling as a named item**.
309
+ Wave 3: UI metadata (P2-c) where surfaces exist; UI codegen only on TS/web. Wave 4:
310
+ prompts, wedge-first per site, parity-gated byte-compare against existing outputs.
311
+ Each wave: scope, LOC retired, effort band, parity gate, the adoption skill that
312
+ executes it (`metaobjects-authoring` / `-codegen` / `-runtime-ui` / `-prompts` /
313
+ `-verify`).
314
+
315
+ **P5-b — The deep-adoption ceiling (one labeled paragraph; `horizon: "later"`).** Name
316
+ what the port's maximal lane looks like for adopters who go all the way, without
317
+ promising it or counting it in benefits:
318
+ - **JVM**: the stock Java/Spring lane keeps hand-written entities (generates DTO records,
319
+ controllers, filter allowlists, repository interfaces) — but the Kotlin lane
320
+ (`codegen-kotlin`) generates the entity + Exposed table + validators themselves
321
+ (generated-base + hand-written-subclass ownership), and OMDB offers metadata-driven
322
+ data access; a deep JVM adopter can end with the ORM layer itself generated/replaced.
323
+ - **TS**: full stack (Drizzle schema + Zod + routes + hooks + grids) — the floor is
324
+ already near the ceiling.
325
+ - **C#**: generated EF Core entities + `AppDbContext` + minimal-API routes; EF Core stays
326
+ the runtime.
327
+ - **Python**: generated Pydantic + `APIRouter` + `ObjectManager` runtime data access.
328
+ - All ports: **scaffold-and-own (ADR-0034)** means adopters own and extend generators —
329
+ the stock generator set is the starting point, not the cap on what can become
330
+ `@generated`. When a large hand-written layer is metadata-derivable but no stock
331
+ generator emits it, say so: "an owned generator can retire this" (honest effort tag).
332
+
333
+ ### P6 — End-state projection
334
+
335
+ - **Spine size — derive from DDL richness, not a flat guess.** Real spines carry the
336
+ full physical structure: identities, `identity.reference` per FK, `identity.secondary`
337
+ per unique constraint, `index.lookup` per performance index, enums, validators. Count
338
+ per-table indexes + uniques + FKs + enum-CHECKs in the DDL and pick the lines-per-
339
+ entity band accordingly: ~25–40 only for bare tables; index/FK-rich schemas run
340
+ ~80–120 lines/entity. Multiply by the **reconciled** object count from P2-a (entities
341
+ + projections + values), not the ORM-class count. Give a range.
342
+ - **Generated surface**: files + LOC per the port's actual generators (read them — M3).
343
+ - **Leverage ratio** = `generated_lines / (metadata_lines + owned_generator_lines)`;
344
+ give a band, note it excludes the drift-gate value (the real prize, not
345
+ line-denominated).
346
+ - **Stays-hand-written table**: every surface predicted to remain bespoke, each with a
347
+ NAMED justification (recursive CTE, window fn, auth, business logic, transports).
348
+
349
+ ### P7 — Beyond-the-ask vocabulary hunt (apply the ADR-0037 ordered test to each)
350
+
351
+ - money as float / hand `*100` → `field.currency` (+`view.currency @locale`) — but NOT
352
+ for non-ISO-4217 quantities (game gold, points): those are `field.int`/`field.long`.
353
+ - string-union / `CHECK IN (...)` / int-flag discriminators → `field.enum @values`
354
+ (plain value-set enums only; enums carrying behavior stay code, or split value-set
355
+ from behavior).
356
+ - UUIDs as bare strings → `field.uuid` (never `field.string` + `@dbColumnType: uuid`).
357
+ - hand `COUNT/SUM` subqueries, read-model SQL → `object.projection` + `origin.passthrough`
358
+ / `origin.aggregate` (+`@filter` for scoped aggregates) / `origin.collection`.
359
+ **Calibration: most projection fields in real spines are passthrough/`extends`
360
+ re-exposures; aggregates are the minority — lead with passthrough.**
361
+ - hand junction joins → `relationship @cardinality: many @through` (junction declares
362
+ two `identity.reference` children; FR-018).
363
+ - copy-pasted base-field blocks → abstract base + `extends`.
364
+ - unique business keys / lookup indexes living only in DDL → `identity.secondary` /
365
+ `index.lookup` (these are usually the single largest invisible-structure class —
366
+ count them).
367
+ - email/URL/IP regexes → `@stringFormat: email` / `field.uri` / `field.inet`.
368
+ - opaque JSON columns → `object.value` + `field.object @storage: jsonb` (from P2-b).
369
+ - inline prompts / ad-hoc payload dicts / regex output parsing → `template.prompt` /
370
+ `template.output` / `template.toolcall`.
371
+ - doc comments hand-written in migrations (`COMMENT ON`) → the common `description` attr.
372
+ - a recurring closed variant-set as N sibling modules → project-registered provider
373
+ subtype — VOCAB CANDIDATE, advisory only, never load-bearing for the verdict.
374
+
375
+ Each suggestion: `metadata_sketch` + honest effort. Bias to under-flagging, but note:
376
+ under-suggestion is also scored — if a hunt line above has hits, it must appear.
377
+
378
+ ### P8 — Limits (feeds §R7)
379
+
380
+ Work the § Calibration table for the target's port. Non-negotiable inclusions: schema
381
+ migration is Node-`meta`-only (PG/SQLite/D1); filter-operator route codegen full only in
382
+ TS; C# has no ObjectManager tier; Python hand-wires the FastAPI router; business logic /
383
+ irreducible SQL / auth / bespoke viz stay hand-written; generated code runs without
384
+ MetaObjects at runtime (local-first) but adopting means owning a codegen step; metadata
385
+ authoring debt is real — price the wave-0 reconciliation; early `verify --db` runs will
386
+ surface legacy oddities — triage as findings, not noise.
387
+
388
+ **State this limit explicitly in §R7 (M8):** *this assessment can read your code, not your
389
+ roadmap.* It counts what exists; it cannot see whether the system will grow, and growth is
390
+ what decides the size-based verdict. Where the trajectory answer was given, name who gave it;
391
+ where it wasn't, say which branch you assumed. An assessment that quietly converts "small
392
+ today" into "not worth it, ever" is exactly the false negative this section exists to prevent.
393
+
394
+ ---
395
+
396
+ ## Report contract — `fit-assessment.md`, sections in this order
397
+
398
+ **§R0 — Verdict block.** Per-pillar verdicts (codegen / runtime metadata / drift
399
+ detection / prompt construction): `STRONG FIT` / `FIT` / `MARGINAL` / `NOT A FIT` /
400
+ `N/A`, one decisive fact each (pillar-scoped because real projects are lopsided).
401
+ Overall verdict + confidence + the three decisive facts. The disqualifier table, worked.
402
+ The one-line wedge recommendation. **The UI pillar line follows the P2-c two-line split.**
403
+
404
+ **§R1 — Drift exposure today** (the centerpiece; immediately after the verdict): the
405
+ cost-of-change exhibit, then the ledger (sources → divergence-today → archaeology →
406
+ gate → class), then the gate-mapping table + honest limits, then a ledger summary line
407
+ (N classes; N with documented past incidents; N divergent right now).
408
+
409
+ **§R2 — Census** — including the **P2-a reconciliation block**, the **P2-b opaque-payload
410
+ counts**, and the renderer-agnostic UI-surface counts, with a coverage column (what
411
+ MetaObjects can model/generate against vs what it will never touch).
412
+
413
+ **§R3 — Migration plan** — waves per P5-a, governed by metadata-follows-the-code +
414
+ author-from-live-schema, **plus the single labeled deep-adoption-ceiling paragraph
415
+ (P5-b)**.
416
+
417
+ **§R4 — End-state projection** — spine estimate (DDL-richness-derived band × reconciled
418
+ object count), generated surface, leverage ratio band, stays-hand-written table.
419
+
420
+ **§R5 — Benefits, quantified and tagged** — LOC eliminated by class; drift classes
421
+ closed (cross-referenced to §R1 rows); incident prevention where archaeology shows past
422
+ production errors; cross-language reuse only if genuinely multi-language; prompt wins
423
+ (byte-stable renders, payload-as-diff, build-time template verify). Each tagged
424
+ `checkable: true/false`; uncheckable benefits allowed but labeled.
425
+
426
+ **§R6 — Beyond-the-ask vocabulary opportunities** (P7 output, each with sketch + effort).
427
+
428
+ **§R7 — What you will NOT get** (P8 output; mandatory).
429
+
430
+ **§R8 — First-week wedge plan.** One real entity-set end-to-end: author metadata
431
+ reproducing the existing shape from the live schema → wire the drift gate into CI in
432
+ week one (the earliest, cheapest payoff) → name the expected day-one catches from the
433
+ ledger → one generated vertical + one deleted duplicate as the codegen proof. Then point
434
+ at `meta init` + the adoption skills.
435
+
436
+ ---
437
+
438
+ ## JSON contract — `fit-assessment.json`
439
+
440
+ ```jsonc
441
+ {
442
+ "assessed_project": "<path or name>",
443
+ "assessment_version": "v1",
444
+ "date": "YYYY-MM-DD",
445
+ "verdicts": {
446
+ "overall": "ADOPT | ADOPT-STAGED | MARGINAL | NOT-A-FIT",
447
+ "confidence": "high | medium | low",
448
+ "pillars": { "codegen": "...", "runtime": "...", "drift": "...", "prompts": "..." }
449
+ },
450
+ "census_reconciliation": { // mandatory — makes entity-count claims scoreable
451
+ "live_tables": 0,
452
+ "orm_model_classes_all_lanes": 0,
453
+ "predicted_object_entity": 0,
454
+ "predicted_object_projection": 0,
455
+ "predicted_object_value": 0,
456
+ "opaque_json_columns": 0,
457
+ "ui_surfaces_total": 0, // renderer-agnostic count (P2-c)
458
+ "prompt_sites": 0
459
+ },
460
+ "claims": [
461
+ {
462
+ "id": "kebab-stable-id",
463
+ "claim_type": "fit | census | drift | migrates | stays-bespoke | leverage | benefit | vocab | limit | wedge",
464
+ "claim": "one-sentence prediction",
465
+ "pillar": "codegen | runtime | drift | prompt | n/a",
466
+ "surface": "entity | dto | validator | repository | route | view | ui | prompt | migration | schema | payload",
467
+ "capability": "the registry capability this maps to (e.g. field.enum, origin.passthrough, template.output)",
468
+ "locations": ["file:line", "commit-sha"],
469
+ "evidence": "what was read/verified",
470
+ "impact": "LOC / call-sites / risk",
471
+ "effort": "trivial | small | medium | large",
472
+ "confidence": "high | medium | low",
473
+ "checkable": true,
474
+ "horizon": "now | later", // "later" = deep-adoption ceiling; never counted in benefits
475
+ "metadata_sketch": "optional — read-only proposal",
476
+ "parity_gate": "optional — the check proving behavior-equivalence"
477
+ }
478
+ ]
479
+ }
480
+ ```
481
+
482
+ Every prose prediction gets a claim. Ceiling statements (P5-b) MUST carry
483
+ `horizon: "later"`. Drift-ledger rows MUST carry `checkable: true` with real locations.
484
+
485
+ ---
486
+
487
+ ## Calibration — per-port caps (never promise across these lines)
488
+
489
+ - **Schema pillar** (`meta migrate`, `verify --db`): Node `meta` CLI only; Postgres,
490
+ SQLite, D1 only. Any other DB or a no-Node shop: the schema pillar is out — data
491
+ access still works; say it plainly.
492
+ - **TS**: full stack — Drizzle/Zod/Fastify codegen, filter-operator routes, TanStack/React
493
+ UI runtime, migrations. The only port with UI codegen + runtime.
494
+ - **Java/Spring**: generated DTO records, controllers, filter allowlists (pagination/sort;
495
+ filter ops deferred), repository *interfaces* (consumer implements — existing ORM sits
496
+ behind unchanged), payload records, output parsers hand-write the Jackson one-liner;
497
+ entities stay hand-written on this lane; Maven `meta:gen` / `meta:verify`
498
+ (`codegen`/`templates` modes — no live-DB mode).
499
+ - **Kotlin/JVM**: `codegen-kotlin` generates entity + Exposed table + Spring controller +
500
+ payload + relations + filter allowlist + validators + output parsers (runs via Maven
501
+ `meta:gen`).
502
+ - **C#**: generated EF Core entities + `AppDbContext` + CRUD minimal-API routes +
503
+ render/payload/verify via `dotnet meta gen`/`verify`; no ObjectManager runtime tier;
504
+ no migrate surface (TS-owned).
505
+ - **Python**: generated Pydantic + `APIRouter` + payload/parsers + `ObjectManager`
506
+ runtime; consumer hand-wires the FastAPI router + repository impl; `metaobjects`
507
+ console script `gen`/`verify` (no migrate).
508
+ - **Prompt pillar** (all five ports): render + payload-VO codegen + `verify` templates +
509
+ `template.output` parser-on-receipt (FR-006) + output-format fragment & tolerant
510
+ `extract` (FR-010). MCP exposure of declared prompts/tools: not shipped — never
511
+ promise it.
512
+ - **Not shipped, never promise**: `api.*`/`operation.*`/`binding.*` declared-API surface;
513
+ the cut `byte`/`short`/`class` field stubs (non-functional, removed from the registry);
514
+ `index.fulltext`/`vector`/`spatial` (reserved, unregistered); native PG enums /
515
+ int-backed enums (deferred).
516
+
517
+ ---
518
+
519
+ ## Final self-check before writing
520
+
521
+ 0. **Trajectory (M8): did you ASK, or did you infer "won't grow" from a small repo?** A flat
522
+ NOT-A-FIT is legal ONLY on a structural disqualifier. If the size row is doing the work and
523
+ the trajectory answer is missing, you must show both branches — not a verdict.
524
+ 1. Disqualifier table worked row-by-row? (Invalid output otherwise.)
525
+ 2. Census reconciliation block present, with predicted `object.entity` anchored to live
526
+ tables, not ORM classes?
527
+ 3. Opaque-payload hunt run and reported, with the value-object modeling in a named wave?
528
+ 4. UI verdict split into metadata-authoring vs codegen/runtime lines, with
529
+ server-rendered surfaces counted?
530
+ 5. Spine estimate derived from DDL richness × reconciled object count?
531
+ 6. Deep-adoption ceiling paragraph present, labeled, `horizon: "later"`?
532
+ 7. Every drift row: real `file:line`, real commits, a named gate? (>15% false positives
533
+ is fatal.)
534
+ 8. Every capability claim traceable to the registry/docs/generator source?
535
+ 9. "What you will NOT get" complete for this port?
536
+ 10. Every prose prediction has a JSON twin?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/sdk",
3
- "version": "0.15.20",
3
+ "version": "0.15.21-rc.1",
4
4
  "description": "Workspace helpers and agent-docs utilities for MetaObjects projects.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@metaobjectsdev/metadata": "0.15.20",
59
+ "@metaobjectsdev/metadata": "0.15.21-rc.1",
60
60
  "zod": "^3.23.0"
61
61
  },
62
62
  "devDependencies": {