@metaobjectsdev/sdk 0.24.4 → 0.24.5

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.
@@ -150,12 +150,13 @@ don't silently churn the existing code.
150
150
 
151
151
  ## Write your own generators — the built-ins rarely fit an app exactly
152
152
 
153
- The built-in generators (entity, queries, routes, form, grid, barrel) cover the
154
- common shape, but **real apps routinely need output the built-ins don't emit as-is**
155
- — a bespoke REST contract, custom DTO/response shapes, an app-specific service or
156
- repository layer, a UI the defaults don't produce. When that happens the model-first
157
- move is **not** to abandon metadata and hand-write the layer. Write a **custom
158
- generator** that reads the same metadata and emits *your* app's shape.
153
+ The built-in generators (entity, queries, routes, routes-hono, barrel, form, hooks,
154
+ grid, grid-hook) cover the common shape, but **real apps routinely need output the
155
+ built-ins don't emit as-is** — a bespoke REST contract, custom DTO/response shapes,
156
+ an app-specific service or repository layer, a UI the defaults don't produce. When
157
+ that happens the model-first move is **not** to abandon metadata and hand-write the
158
+ layer. Write a **custom generator** that reads the same metadata and emits *your*
159
+ app's shape.
159
160
 
160
161
  Treat this as a first-class, expected activity — not an escape hatch. A custom
161
162
  generator is still model-first: it derives from the metadata spine, so it
@@ -163,6 +164,12 @@ regenerates on change and stays consistent across every entity — the leverage
163
164
  forfeit by hand-writing. Hand-rolling *away from* metadata is the anti-pattern;
164
165
  generating *your own shape from* metadata is the point.
165
166
 
167
+ This is for when the *shape* itself needs to change. If a built-in's shape is
168
+ already right and only the *target* is wrong — a different framework than the
169
+ shipped reference emits for — take ownership of that generator instead of writing
170
+ one from scratch; see "Your framework isn't the default" below, and your language
171
+ reference for the command that does it.
172
+
166
173
  The plugin interface is small (`@metaobjectsdev/codegen-ts`): a `Generator` is
167
174
  `{ name, filter?, generate }`, where `generate(ctx)` returns `EmittedFile[]`
168
175
  (`{ path, content }`). `perEntity` / `oncePerRun` wrap the common cases:
@@ -190,6 +197,31 @@ the `generators` array in `metaobjects.config.ts` next to the built-ins — it r
190
197
  the same pass, writes under the same target rules, and carries the `@generated`
191
198
  header so it round-trips like any other.
192
199
 
200
+ ## Your framework isn't the default — the retargeting procedure
201
+
202
+ If the shipped templates do not emit for your stack, retargeting is the **normal first
203
+ move** — not a workaround and not a sign of a bug. Owning a generator is the supported
204
+ path to any framework; MetaObjects does not ship a codegen package per framework and is
205
+ not waiting to.
206
+
207
+ The doctrine, in order of what to try:
208
+
209
+ 1. **Check config first.** Several apparent codegen failures are one config value
210
+ (module-specifier style, output directory, dialect, API prefix). Change it and retest
211
+ before writing any code.
212
+ 2. **Own the generator, not the renderer.** Take a copy of the reference template for the
213
+ artifact that is wrong and edit the one step your framework disagrees about. Each
214
+ template's header names what its emit is coupled to and which call to swap.
215
+ 3. **Compose, do not fork.** Call the exported render function and wrap its result where
216
+ you can, so you keep receiving upstream fixes. Forking a whole renderer is the thing
217
+ to avoid — not owning the generator.
218
+ 4. **Server-tier output is usually already portable.** The entity module and the query
219
+ helpers carry no HTTP-framework coupling; retargeting is usually only needed at the
220
+ routes and UI tiers.
221
+
222
+ Hand-rolling *away from* metadata is the anti-pattern. Generating *your own shape from*
223
+ metadata is the point.
224
+
193
225
  ### Never read metadata through an `own*()` accessor (ADR-0039) — top bug source
194
226
 
195
227
  When writing OR reviewing a generator, **read every field/node property and iterate
@@ -239,6 +271,18 @@ output/template · doesn't fit → write a generator that emits your shape *from
239
271
  metadata* · only the genuinely un-modelable (business algorithms, external calls) is
240
272
  hand-written outside codegen — and it still imports the generated types.
241
273
 
274
+ **The commands and config keys that implement the steps above differ per port, and the
275
+ ports differ in how much is written down.** TypeScript has the whole procedure as a
276
+ documented one — `meta eject`, the `metaobjects.config.ts` keys, the exported `render*`
277
+ functions — in this skill's `references/typescript.md`. **The other ports have no eject
278
+ command.** There, owning a generator means implementing that port's generator interface
279
+ — `com.metaobjects.generator.Generator` (Java / Kotlin),
280
+ `metaobjects.codegen.generator.Generator` (Python), `MetaObjects.Codegen.IGenerator`
281
+ (C#) — and registering it with the build tool that runs codegen for your port. Their
282
+ `references/` fragments document what each built-in emits, which is what you compare
283
+ your own emit against; they do not carry a step-by-step retargeting procedure, so do
284
+ not go looking for one.
285
+
242
286
  ## Dialects
243
287
 
244
288
  Generated DB schema/DDL targets a SQL **dialect**:
@@ -11,6 +11,7 @@ packages. Codegen runs through the Node `meta` CLI (`@metaobjectsdev/cli`, binar
11
11
  - Run
12
12
  - Multiple output targets
13
13
  - Field subtype → column mapping
14
+ - Retargeting to another framework — the TypeScript procedure
14
15
 
15
16
  ## Install
16
17
 
@@ -43,6 +44,7 @@ import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack
43
44
  export default defineConfig({
44
45
  outDir: "src/generated",
45
46
  dialect: "postgres", // "postgres" | "sqlite" | "d1" (D1 is TS-only)
47
+ extStyle: "js", // "js" (default) for Node ESM / plain tsc; "none" for a bundler-resolution toolchain — see SKILL.md "Your framework isn't the default"
46
48
  apiPrefix: "/api", // flows to routes AND client fetch URLs
47
49
  columnNamingStrategy: "snake_case", // "snake_case" (default) | "literal" | "kebab-case"
48
50
  timestampMode: "string", // "string" (default, ISO-8601 wire contract) | "date" (Drizzle native Date)
@@ -231,3 +233,106 @@ The VO type, its Zod `InsertSchema`, and this `.$type<>()` all import the VO fro
231
233
  the same module (layout/package/`extStyle`-aware resolution). An opaque jsonb column
232
234
  (`field.string @dbColumnType: jsonb`) gets no `.$type<>()` — it stays `unknown`,
233
235
  which is the correct shape for freeform payloads with no fixed VO.
236
+
237
+ ## Retargeting to another framework — the TypeScript procedure
238
+
239
+ This is the TypeScript implementation of the retargeting doctrine in SKILL.md
240
+ ("Your framework isn't the default"). Read that first for the order of moves;
241
+ everything below — `meta eject`, `metaobjects.config.ts` keys, the exported
242
+ `render*` functions — is Node-CLI-specific and exists only on this port.
243
+
244
+ The shipped reference templates emit for **Fastify on Node** (plus a Hono variant) with
245
+ Drizzle and Zod. If that is not your stack, retargeting is the **normal first move** — not
246
+ a workaround and not a sign of a bug. Each template's header carries a `targets:` line
247
+ naming exactly what its emit is coupled to and which call to swap.
248
+
249
+ Work the list in order; the first two cost nothing.
250
+
251
+ **1. Check the target-shaped config first.** Several apparent codegen failures are one
252
+ config value in `metaobjects.config.ts`:
253
+
254
+ - **`extStyle`** — `"js"` emits `./Entity.js` specifiers, correct for Node ESM and a plain
255
+ `tsc` with `nodenext`. Bundlers disagree on whether they perform the TypeScript
256
+ `.js`→`.ts` rewrite: it fails outright under **Turbopack** — including between two
257
+ generated files, which makes the whole generated tree unresolvable — while Vite and
258
+ esbuild are documented to accept it and webpack needs `resolve.extensionAlias` to do the
259
+ same. **If a generated import fails to resolve, set `extStyle: "none"` and retest** for
260
+ your toolchain rather than assuming either setting from this list.
261
+ - **`clientDirective`** — `true` prepends `"use client";` to the generated form, hooks,
262
+ columns and grid-hook modules. Defaults to `false`. **Set it if your framework compiles
263
+ server and client from one tree** (React Server Components — Next.js App Router and
264
+ friends); leave it off otherwise, where the directive is inert and some bundlers warn
265
+ about it.
266
+ - **`outDir`** / **`targets`** — where output lands, per generator.
267
+ - **`apiPrefix`**, **`dialect`** — route mounting and column mapping.
268
+
269
+ **2. Ask whether your framework splits the module graph.** Some frameworks compile server
270
+ and client from one source tree and resolve each half under *different export conditions*
271
+ (React Server Components, Angular universal, Qwik). Where they do:
272
+
273
+ - a generated artifact using client-only APIs may need a **marker directive** or a distinct
274
+ import path, and
275
+ - the resulting error frequently **names a package that is installed and present** — because
276
+ resolution failed under the server condition, not because the dependency is missing.
277
+
278
+ Read that error as a *boundary* problem, not a dependency problem. The fix belongs in the
279
+ generator that emits the artifact, which you own.
280
+
281
+ **3. If the emit is wrong for your framework, own the generator.**
282
+
283
+ meta eject --list # every template you can take ownership of
284
+ meta eject form # copies it to codegen/generators/form.ts
285
+
286
+ Then compose the engine and replace only the step that differs. Every generator's renderer
287
+ is exported, so wrapping is available — but **how much that buys you differs by tier, and
288
+ it is worth knowing which one you are in before you start**:
289
+
290
+ - **Entity module (`entity`)** — genuinely composable. `renderDrizzleSchema`,
291
+ `renderZodValidators`, `renderInferredTypes`, `renderFilterAllowlist` and friends are
292
+ separate exported sections the template assembles into a `Code[]`. Swap or drop one and
293
+ keep the rest.
294
+ - **Routes and UI (`routes`, `routes-hono`, `form`, `hooks`, `grid`, `grid-hook`)** — one
295
+ whole-file renderer each, so "replace a step" really means wrap the whole output. That
296
+ is enough for a marker directive, a header, or a post-process, and it is what the RSC
297
+ case below needs. It is **not** enough to retarget the emitted framework: if you need
298
+ Svelte or Angular instead of React, you are writing a renderer, and the honest move is
299
+ to keep the generator's metadata walk and replace the render call entirely.
300
+
301
+ **`"use client"` needs no ejecting at all — it is a config knob.** The generated form,
302
+ hooks, columns and grid-hook modules are client components; React Server Components
303
+ frameworks (Next.js App Router and friends) require the directive saying so. Set it once:
304
+
305
+ ```ts
306
+ export default defineConfig({
307
+ clientDirective: true, // prepend `"use client";` to generated client artifacts
308
+ // ...
309
+ });
310
+ ```
311
+
312
+ Defaults to `false`, because the directive is only *required* under RSC and is inert
313
+ (and warned about by some bundlers) everywhere else. It is applied ahead of the
314
+ `@generated` header, exactly once, and only to the four client artifacts — the entity
315
+ module, the query helpers and `<Entity>.meta.ts` are untouched, since `.meta.ts` is plain
316
+ data and in RSC the boundary is the importing component, not everything it reaches.
317
+
318
+ For the general wrap-the-output case — a directive or header MetaObjects does not model:
319
+
320
+ ```ts
321
+ // codegen/generators/form.ts — OWNED
322
+ import { renderFormFile } from "@metaobjectsdev/codegen-ts-react";
323
+
324
+ // ...inside generate():
325
+ if (!ctx.renderContext) throw new Error("renderContext is required (provided by runGen)");
326
+ const body = renderFormFile(entity, ctx.renderContext);
327
+ return { path, content: `// @my-framework:client\n` + body };
328
+ ```
329
+
330
+ You keep receiving upstream fixes to `renderFormFile` while owning the one line your
331
+ framework cares about. **Forking the whole renderer is the thing to avoid**, not owning the
332
+ generator.
333
+
334
+ **4. Server-tier output is usually already portable.** The entity module (a table plus
335
+ validation schemas) and the query helpers (which take `db` as a parameter rather than
336
+ importing a singleton) carry no HTTP-framework coupling — a server-rendered component can
337
+ call a generated query directly. Retargeting is usually only needed at the routes and UI
338
+ tiers.
@@ -22,7 +22,16 @@ provider/LLM-call layer — you compose the call yourself.
22
22
  ```ts
23
23
  // metaobjects.config.ts
24
24
  import { defineConfig } from "@metaobjectsdev/cli";
25
- import { entityFile, queriesFile, barrel, promptRender, outputParser } from "@metaobjectsdev/codegen-ts/generators";
25
+ // The entity trio + barrel come from the OWNED copies `meta init` scaffolded into
26
+ // ./codegen/generators/ (ADR-0034). Importing them from the package instead is the
27
+ // deprecated path, and quietly hands their shape back to the package — so keep these
28
+ // lines as `meta init` wrote them and add only the prompt pair below.
29
+ import { entityFile } from "./codegen/generators/entity.js";
30
+ import { queriesFile } from "./codegen/generators/queries.js";
31
+ import { barrel } from "./codegen/generators/barrel.js";
32
+ // promptRender / outputParser are NOT in the ownable set — the render and parse engines
33
+ // are upstream-owned, so importing them from the package is the supported pattern.
34
+ import { promptRender, outputParser } from "@metaobjectsdev/codegen-ts/generators";
26
35
 
27
36
  export default defineConfig({
28
37
  outDir: "src/generated",
@@ -20,9 +20,14 @@ this package.
20
20
  ```bash
21
21
  npm install @metaobjectsdev/tanstack @metaobjectsdev/runtime-web
22
22
  npm install --save-dev @metaobjectsdev/codegen-ts-tanstack
23
+ npm i @tanstack/react-table@^8.21.3
23
24
  ```
24
25
 
25
- Peer-deps: `@tanstack/react-query`, `@tanstack/react-table`.
26
+ Peer-deps: `@tanstack/react-query`, `@tanstack/react-table`. **Pin the react-table
27
+ major explicitly** — the registry's `latest` is v9, which removed `useReactTable`
28
+ and `getCoreRowModel` (both used by `<EntityGrid>`), so a bare
29
+ `npm i @tanstack/react-table` installs a version this package's `^8.20.0` peer range
30
+ rejects and poisons every later install in the project with `ERESOLVE`.
26
31
 
27
32
  ## Key exports
28
33
 
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/agent-context/scaffold.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEvD,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,qCAAqC,CAAC;AAE9E,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,CAAC;IACX;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,sFAAsF;IACtF,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,uFAAuF;IACvF,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,6CAA6C;IAC7C,QAAQ,EAAE,QAAQ,CAAC;IACnB,wHAAwH;IACxH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,gBAAgB,CA8BnB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,GAAG,IAAI,CAYhB"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/agent-context/scaffold.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEvD,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,qCAAqC,CAAC;AAE9E,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,CAAC;IACX;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,sFAAsF;IACtF,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,uFAAuF;IACvF,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,6CAA6C;IAC7C,QAAQ,EAAE,QAAQ,CAAC;IACnB,wHAAwH;IACxH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,gBAAgB,CA8BnB;AAwDD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,GAAG,IAAI,CAahB"}
@@ -39,6 +39,61 @@ export function planScaffold(opts) {
39
39
  removed,
40
40
  };
41
41
  }
42
+ /**
43
+ * The ordered release coordinate of a version — `[minor, patch]` — or `null` when the
44
+ * version cannot be ordered as a plain release.
45
+ *
46
+ * The MAJOR is deliberately dropped. It is a per-registry constant, not information:
47
+ * npm/PyPI/NuGet ship `0.<m>.<p>` and Maven Central the same `<m>.<p>` on its historical
48
+ * major `7`, so the minor.patch IS the shared release coordinate across all four (this is
49
+ * the same reduction the JVM's `releaseCoordinate` has always made for equality).
50
+ *
51
+ * Returns `null` — meaning "not orderable, so nudge" — for anything that is not exactly
52
+ * three dot-separated integers. That deliberately covers prereleases (`0.24.5-rc.1`),
53
+ * build metadata (`0.24.5+abc`), and the `0.0.0` sentinel a port emits when it cannot
54
+ * resolve its own installed version. Each must keep nudging: an RC-scaffolded context
55
+ * against a final release is still worth refreshing, and an unknown install must never
56
+ * be allowed to assert "in sync".
57
+ */
58
+ function releaseSeries(version) {
59
+ if (version === undefined)
60
+ return null;
61
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
62
+ if (m === null)
63
+ return null;
64
+ if (version.trim() === UNRESOLVED_VERSION)
65
+ return null; // never assert in-sync on unknown
66
+ return [Number(m[2]), Number(m[3])];
67
+ }
68
+ /** The sentinel a port stamps when it cannot resolve its own installed version. */
69
+ const UNRESOLVED_VERSION = "0.0.0";
70
+ /**
71
+ * True when the manifest was stamped by a release STRICTLY NEWER than the installed one.
72
+ *
73
+ * This is the one exemption from "any drift nudges", and it exists because of the
74
+ * publish-what-changed rule (docs/RELEASING.md): a registry publishes only when it has a
75
+ * changed product file, so a port legitimately sits behind npm — while `meta agent-docs`,
76
+ * the canonical scaffolder for EVERY port, stamps the npm version it was run from. A
77
+ * Python install at `0.24.4` whose context was scaffolded by npm `0.24.7` is correct, and
78
+ * nudging it is [#347](https://github.com/metaobjectsdev/metaobjects/issues/347) exactly:
79
+ * the remedy re-runs the scaffolder, which re-stamps `0.24.7`, so the advisory can never
80
+ * be satisfied and fires on every build forever. An advisory that cries wolf in the inner
81
+ * loop gets tuned out, and then it is not there for the upgrade it exists for.
82
+ *
83
+ * KNOWN BOUND, stated rather than hidden: ordering on minor.patch assumes both versions
84
+ * sit in the same release SERIES. That holds for every release to date and for every
85
+ * release after the 1.0/8.0 cut, but not ACROSS it — at that one cut a `0.24.x`-stamped
86
+ * context against a `1.0.0` install compares (24,x) > (0,0) and is read as "ahead", so the
87
+ * nudge is suppressed once when it should fire. The cost is a missed advisory, never a
88
+ * wrong action, and re-scaffolding at 1.0 is part of the cut anyway.
89
+ */
90
+ function contextIsAheadOfInstall(generatedBy, currentVersion) {
91
+ const stamped = releaseSeries(generatedBy);
92
+ const installed = releaseSeries(currentVersion);
93
+ if (stamped === null || installed === null)
94
+ return false; // not orderable → nudge
95
+ return stamped[0] > installed[0] || (stamped[0] === installed[0] && stamped[1] > installed[1]);
96
+ }
42
97
  /**
43
98
  * A one-line nudge if the scaffolded agent context predates the installed MetaObjects
44
99
  * (so `gen`/`verify` can remind the user to refresh the skills after an upgrade), or
@@ -49,11 +104,13 @@ export function agentContextStaleness(opts) {
49
104
  const { manifest, currentVersion } = opts;
50
105
  if (manifest === undefined)
51
106
  return null; // no agent context here → nothing to nudge
52
- // Exact-equality on purpose: ANY drift nudges (a re-scaffold is cheap + idempotent).
53
- // Don't "fix" this into a semver compare a prerelease/build-metadata difference is
54
- // still a reason to refresh, and the nudge is advisory, never a gate.
107
+ // Exact-equality FIRST: ANY drift nudges (a re-scaffold is cheap + idempotent). A
108
+ // prerelease/build-metadata difference is still a reason to refresh, so this is not a
109
+ // semver compare see releaseSeries() for the ONE case that is exempt.
55
110
  if (manifest.generatedBy === currentVersion)
56
111
  return null; // in sync
112
+ if (contextIsAheadOfInstall(manifest.generatedBy, currentVersion))
113
+ return null;
57
114
  const from = manifest.generatedBy ?? "an older MetaObjects";
58
115
  return (`MetaObjects agent context was generated by ${from}; you're on ${currentVersion}. ` +
59
116
  `Re-run 'meta init --docs-only --refresh-docs' to refresh the .claude/skills docs.`);
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/agent-context/scaffold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/agent-context/scaffold.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/sdk",
3
- "version": "0.24.4",
3
+ "version": "0.24.5",
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.24.4",
59
+ "@metaobjectsdev/metadata": "0.24.5",
60
60
  "zod": "^3.23.0"
61
61
  },
62
62
  "devDependencies": {
@@ -79,6 +79,60 @@ export function planScaffold(opts: {
79
79
  };
80
80
  }
81
81
 
82
+ /**
83
+ * The ordered release coordinate of a version — `[minor, patch]` — or `null` when the
84
+ * version cannot be ordered as a plain release.
85
+ *
86
+ * The MAJOR is deliberately dropped. It is a per-registry constant, not information:
87
+ * npm/PyPI/NuGet ship `0.<m>.<p>` and Maven Central the same `<m>.<p>` on its historical
88
+ * major `7`, so the minor.patch IS the shared release coordinate across all four (this is
89
+ * the same reduction the JVM's `releaseCoordinate` has always made for equality).
90
+ *
91
+ * Returns `null` — meaning "not orderable, so nudge" — for anything that is not exactly
92
+ * three dot-separated integers. That deliberately covers prereleases (`0.24.5-rc.1`),
93
+ * build metadata (`0.24.5+abc`), and the `0.0.0` sentinel a port emits when it cannot
94
+ * resolve its own installed version. Each must keep nudging: an RC-scaffolded context
95
+ * against a final release is still worth refreshing, and an unknown install must never
96
+ * be allowed to assert "in sync".
97
+ */
98
+ function releaseSeries(version: string | undefined): [number, number] | null {
99
+ if (version === undefined) return null;
100
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
101
+ if (m === null) return null;
102
+ if (version.trim() === UNRESOLVED_VERSION) return null; // never assert in-sync on unknown
103
+ return [Number(m[2]), Number(m[3])];
104
+ }
105
+
106
+ /** The sentinel a port stamps when it cannot resolve its own installed version. */
107
+ const UNRESOLVED_VERSION = "0.0.0";
108
+
109
+ /**
110
+ * True when the manifest was stamped by a release STRICTLY NEWER than the installed one.
111
+ *
112
+ * This is the one exemption from "any drift nudges", and it exists because of the
113
+ * publish-what-changed rule (docs/RELEASING.md): a registry publishes only when it has a
114
+ * changed product file, so a port legitimately sits behind npm — while `meta agent-docs`,
115
+ * the canonical scaffolder for EVERY port, stamps the npm version it was run from. A
116
+ * Python install at `0.24.4` whose context was scaffolded by npm `0.24.7` is correct, and
117
+ * nudging it is [#347](https://github.com/metaobjectsdev/metaobjects/issues/347) exactly:
118
+ * the remedy re-runs the scaffolder, which re-stamps `0.24.7`, so the advisory can never
119
+ * be satisfied and fires on every build forever. An advisory that cries wolf in the inner
120
+ * loop gets tuned out, and then it is not there for the upgrade it exists for.
121
+ *
122
+ * KNOWN BOUND, stated rather than hidden: ordering on minor.patch assumes both versions
123
+ * sit in the same release SERIES. That holds for every release to date and for every
124
+ * release after the 1.0/8.0 cut, but not ACROSS it — at that one cut a `0.24.x`-stamped
125
+ * context against a `1.0.0` install compares (24,x) > (0,0) and is read as "ahead", so the
126
+ * nudge is suppressed once when it should fire. The cost is a missed advisory, never a
127
+ * wrong action, and re-scaffolding at 1.0 is part of the cut anyway.
128
+ */
129
+ function contextIsAheadOfInstall(generatedBy: string | undefined, currentVersion: string): boolean {
130
+ const stamped = releaseSeries(generatedBy);
131
+ const installed = releaseSeries(currentVersion);
132
+ if (stamped === null || installed === null) return false; // not orderable → nudge
133
+ return stamped[0] > installed[0] || (stamped[0] === installed[0] && stamped[1] > installed[1]);
134
+ }
135
+
82
136
  /**
83
137
  * A one-line nudge if the scaffolded agent context predates the installed MetaObjects
84
138
  * (so `gen`/`verify` can remind the user to refresh the skills after an upgrade), or
@@ -91,10 +145,11 @@ export function agentContextStaleness(opts: {
91
145
  }): string | null {
92
146
  const { manifest, currentVersion } = opts;
93
147
  if (manifest === undefined) return null; // no agent context here → nothing to nudge
94
- // Exact-equality on purpose: ANY drift nudges (a re-scaffold is cheap + idempotent).
95
- // Don't "fix" this into a semver compare a prerelease/build-metadata difference is
96
- // still a reason to refresh, and the nudge is advisory, never a gate.
148
+ // Exact-equality FIRST: ANY drift nudges (a re-scaffold is cheap + idempotent). A
149
+ // prerelease/build-metadata difference is still a reason to refresh, so this is not a
150
+ // semver compare see releaseSeries() for the ONE case that is exempt.
97
151
  if (manifest.generatedBy === currentVersion) return null; // in sync
152
+ if (contextIsAheadOfInstall(manifest.generatedBy, currentVersion)) return null;
98
153
  const from = manifest.generatedBy ?? "an older MetaObjects";
99
154
  return (
100
155
  `MetaObjects agent context was generated by ${from}; you're on ${currentVersion}. ` +