@metaobjectsdev/cli 1.0.0-rc.4 → 1.0.0-rc.6

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.
Files changed (116) hide show
  1. package/dist/src/commands/docs.d.ts.map +1 -1
  2. package/dist/src/commands/docs.js +41 -9
  3. package/dist/src/commands/docs.js.map +1 -1
  4. package/dist/src/commands/gen.d.ts +8 -0
  5. package/dist/src/commands/gen.d.ts.map +1 -1
  6. package/dist/src/commands/gen.js +17 -4
  7. package/dist/src/commands/gen.js.map +1 -1
  8. package/dist/src/commands/init.d.ts.map +1 -1
  9. package/dist/src/commands/init.js +181 -35
  10. package/dist/src/commands/init.js.map +1 -1
  11. package/dist/src/commands/migrate.d.ts +6 -0
  12. package/dist/src/commands/migrate.d.ts.map +1 -1
  13. package/dist/src/commands/migrate.js +11 -5
  14. package/dist/src/commands/migrate.js.map +1 -1
  15. package/dist/src/commands/prompt-snapshot.d.ts.map +1 -1
  16. package/dist/src/commands/prompt-snapshot.js +2 -1
  17. package/dist/src/commands/prompt-snapshot.js.map +1 -1
  18. package/dist/src/commands/types.d.ts.map +1 -1
  19. package/dist/src/commands/types.js +12 -3
  20. package/dist/src/commands/types.js.map +1 -1
  21. package/dist/src/commands/upgrade.d.ts.map +1 -1
  22. package/dist/src/commands/upgrade.js +38 -4
  23. package/dist/src/commands/upgrade.js.map +1 -1
  24. package/dist/src/commands/verify.d.ts.map +1 -1
  25. package/dist/src/commands/verify.js +108 -30
  26. package/dist/src/commands/verify.js.map +1 -1
  27. package/dist/src/index.d.ts +2 -0
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +55 -8
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/lib/advisory.d.ts +6 -0
  32. package/dist/src/lib/advisory.d.ts.map +1 -1
  33. package/dist/src/lib/advisory.js +13 -0
  34. package/dist/src/lib/advisory.js.map +1 -1
  35. package/dist/src/lib/anti-patterns.d.ts.map +1 -1
  36. package/dist/src/lib/anti-patterns.js +2 -4
  37. package/dist/src/lib/anti-patterns.js.map +1 -1
  38. package/dist/src/lib/args.d.ts +251 -12
  39. package/dist/src/lib/args.d.ts.map +1 -1
  40. package/dist/src/lib/args.js +119 -92
  41. package/dist/src/lib/args.js.map +1 -1
  42. package/dist/src/lib/base-url-advisory.d.ts +37 -0
  43. package/dist/src/lib/base-url-advisory.d.ts.map +1 -0
  44. package/dist/src/lib/base-url-advisory.js +162 -0
  45. package/dist/src/lib/base-url-advisory.js.map +1 -0
  46. package/dist/src/lib/codegen-drift.d.ts +8 -0
  47. package/dist/src/lib/codegen-drift.d.ts.map +1 -1
  48. package/dist/src/lib/codegen-drift.js +43 -10
  49. package/dist/src/lib/codegen-drift.js.map +1 -1
  50. package/dist/src/lib/detect-stack.d.ts +17 -0
  51. package/dist/src/lib/detect-stack.d.ts.map +1 -1
  52. package/dist/src/lib/detect-stack.js +64 -2
  53. package/dist/src/lib/detect-stack.js.map +1 -1
  54. package/dist/src/lib/docs-drift.d.ts +12 -1
  55. package/dist/src/lib/docs-drift.d.ts.map +1 -1
  56. package/dist/src/lib/docs-drift.js +85 -16
  57. package/dist/src/lib/docs-drift.js.map +1 -1
  58. package/dist/src/lib/git-ignore.d.ts +59 -0
  59. package/dist/src/lib/git-ignore.d.ts.map +1 -0
  60. package/dist/src/lib/git-ignore.js +120 -0
  61. package/dist/src/lib/git-ignore.js.map +1 -0
  62. package/dist/src/lib/ignored-scaffold-check.d.ts +12 -0
  63. package/dist/src/lib/ignored-scaffold-check.d.ts.map +1 -0
  64. package/dist/src/lib/ignored-scaffold-check.js +75 -0
  65. package/dist/src/lib/ignored-scaffold-check.js.map +1 -0
  66. package/dist/src/lib/load-error.d.ts +46 -0
  67. package/dist/src/lib/load-error.d.ts.map +1 -0
  68. package/dist/src/lib/load-error.js +51 -0
  69. package/dist/src/lib/load-error.js.map +1 -0
  70. package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
  71. package/dist/src/lib/load-metaobjects-config.js +65 -1
  72. package/dist/src/lib/load-metaobjects-config.js.map +1 -1
  73. package/dist/src/lib/manifest-ignored-check.d.ts.map +1 -1
  74. package/dist/src/lib/manifest-ignored-check.js +23 -36
  75. package/dist/src/lib/manifest-ignored-check.js.map +1 -1
  76. package/dist/src/lib/output.d.ts +22 -1
  77. package/dist/src/lib/output.d.ts.map +1 -1
  78. package/dist/src/lib/output.js +39 -12
  79. package/dist/src/lib/output.js.map +1 -1
  80. package/dist/src/lib/rel-posix.d.ts +5 -0
  81. package/dist/src/lib/rel-posix.d.ts.map +1 -0
  82. package/dist/src/lib/rel-posix.js +23 -0
  83. package/dist/src/lib/rel-posix.js.map +1 -0
  84. package/dist/src/lib/replay-remedy.d.ts +2 -0
  85. package/dist/src/lib/replay-remedy.d.ts.map +1 -0
  86. package/dist/src/lib/replay-remedy.js +69 -0
  87. package/dist/src/lib/replay-remedy.js.map +1 -0
  88. package/dist/src/lib/requirement-lint.d.ts.map +1 -1
  89. package/dist/src/lib/requirement-lint.js +25 -5
  90. package/dist/src/lib/requirement-lint.js.map +1 -1
  91. package/package.json +10 -10
  92. package/src/commands/docs.ts +51 -12
  93. package/src/commands/gen.ts +19 -4
  94. package/src/commands/init.ts +188 -39
  95. package/src/commands/migrate.ts +13 -5
  96. package/src/commands/prompt-snapshot.ts +2 -1
  97. package/src/commands/types.ts +12 -3
  98. package/src/commands/upgrade.ts +44 -4
  99. package/src/commands/verify.ts +120 -32
  100. package/src/index.ts +55 -8
  101. package/src/lib/advisory.ts +15 -0
  102. package/src/lib/anti-patterns.ts +2 -5
  103. package/src/lib/args.ts +154 -109
  104. package/src/lib/base-url-advisory.ts +173 -0
  105. package/src/lib/codegen-drift.ts +52 -9
  106. package/src/lib/detect-stack.ts +73 -2
  107. package/src/lib/docs-drift.ts +107 -17
  108. package/src/lib/git-ignore.ts +159 -0
  109. package/src/lib/ignored-scaffold-check.ts +78 -0
  110. package/src/lib/load-error.ts +89 -0
  111. package/src/lib/load-metaobjects-config.ts +58 -1
  112. package/src/lib/manifest-ignored-check.ts +25 -33
  113. package/src/lib/output.ts +64 -11
  114. package/src/lib/rel-posix.ts +25 -0
  115. package/src/lib/replay-remedy.ts +78 -0
  116. package/src/lib/requirement-lint.ts +26 -5
@@ -12,12 +12,14 @@ import { parseVerifyArgs, type MigrateFlags } from "../lib/args.js";
12
12
  import { log } from "../lib/log.js";
13
13
  import { emitStructured, type OutputFormat } from "../lib/format.js";
14
14
  import {
15
- antiPatternRows, ranSection, skippedSection, warnCapped,
15
+ antiPatternRows, missingBaseUrlRows, ranSection, skippedSection, warnCapped,
16
16
  type AdvisoryDiagnosticRow, type AdvisoryFindingRow, type AdvisorySection,
17
17
  } from "../lib/advisory.js";
18
18
  import { warnIfAgentContextStale } from "../lib/agent-context-staleness.js";
19
19
  import { warnIfManifestIgnored } from "../lib/manifest-ignored-check.js";
20
20
  import { scanSourceForAntiPatterns } from "../lib/anti-patterns.js";
21
+ import { scanForMissingBaseUrl, type BaseUrlFinding } from "../lib/base-url-advisory.js";
22
+ import { replayRemedy } from "../lib/replay-remedy.js";
21
23
  import { FileProvider } from "../lib/file-provider.js";
22
24
  import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
23
25
  import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
@@ -85,6 +87,7 @@ import {
85
87
  REQUIREMENT_STATUSES,
86
88
  } from "@metaobjectsdev/metadata";
87
89
  import { verify, ERR_REQUIRED_SLOT_UNUSED, ERR_PARTIAL_UNRESOLVED } from "@metaobjectsdev/render";
90
+ import { describeLoadError, reportLoadError } from "../lib/load-error.js";
88
91
 
89
92
  const DEFAULT_PROMPTS_DIR = "prompts";
90
93
 
@@ -250,7 +253,10 @@ export async function verifyCommand(
250
253
  });
251
254
  } catch (err) {
252
255
  const msg = (err as Error).message;
253
- log.error(`failed to load metadata: ${msg}`);
256
+ // Everything the loader's envelope carried, not just the message: the stable code a
257
+ // CI job keys on, and the FILE + json path the message itself can never name.
258
+ const report = describeLoadError(err);
259
+ log.error(`failed to load metadata: ${report.text}`);
254
260
  // Strict-load rejection (ADR-0023). Two different failures reach here and they need
255
261
  // different advice:
256
262
  //
@@ -263,8 +269,11 @@ export async function verifyCommand(
263
269
  // retired attribute and the author gets a green `meta verify` over a value that now
264
270
  // reaches nothing: a correct, loud failure converted into a quiet, wrong pass. That is
265
271
  // strictly worse than the error it replaced.
266
- const code = (err as { code?: string }).code;
267
- const suggestions = (err as { suggestions?: string[] }).suggestions;
272
+ // Off the REPORT, not the raw error a second time: `describeLoadError` has already
273
+ // decided what the envelope carried (and filtered empty entries), so re-reading the
274
+ // throwable would be a second answer to a question already answered above.
275
+ const code = report.code;
276
+ const suggestions = report.suggestions;
268
277
  const strictHint =
269
278
  "meta verify is strict (ADR-0023): every authored @attr must be declared. " +
270
279
  "Fix: register the attr on a metadata provider, OR move arbitrary " +
@@ -280,6 +289,14 @@ export async function verifyCommand(
280
289
  // report — only why there is none.
281
290
  emitStructured({
282
291
  error: `failed to load metadata: ${msg}`,
292
+ // ADR-0009 promises a stable `code`, and the docs name codes a CI job is meant to
293
+ // branch on — but this payload used to carry the message string and nothing else,
294
+ // so there was nothing to branch on. Each field is present only when the loader
295
+ // actually supplied it; absence here means the loader had none, never that it was
296
+ // dropped in transit.
297
+ ...(report.code !== undefined ? { code: report.code } : {}),
298
+ ...(report.files !== undefined ? { files: [...report.files] } : {}),
299
+ ...(report.jsonPath !== undefined ? { jsonPath: report.jsonPath } : {}),
283
300
  hint: suggestions !== undefined && suggestions.length > 0
284
301
  ? suggestions.join(" ")
285
302
  : isStrictAttr ? strictHint : "fix the metadata error above and re-run",
@@ -404,12 +421,23 @@ export async function verifyCommand(
404
421
  return 2;
405
422
  }
406
423
 
407
- // --dialect wins; else migrate's resolved dialect; else refuse. There is no --db
408
- // to infer from, so guessing would replay a postgres chain through sqlite.
409
- const dialect: Dialect | undefined = flags.dialect ?? migrateConfig.dialect;
424
+ // --dialect wins; else migrate's resolved dialect; else the project's DECLARED
425
+ // dialect from metaobjects.config.ts; else refuse. There is no --db to infer
426
+ // from, so guessing would replay a postgres chain through sqlite.
427
+ //
428
+ // The last fallback was missing, and its absence made one fact need two
429
+ // spellings: a TypeScript project declares `dialect: "postgres"` in
430
+ // metaobjects.config.ts, `meta gen`, `meta verify --codegen` and `meta migrate`
431
+ // all honour it, and `--replay` alone refused and demanded `migrate.dialect` in
432
+ // .metaobjects/config.json — the spelling a TypeScript project is least likely
433
+ // to have. migrate's own resolution still WINS where it has an answer, because
434
+ // it is the more specific statement about the chain being replayed.
435
+ const dialect: Dialect | undefined =
436
+ flags.dialect ?? migrateConfig.dialect ?? forgeConfig?.dialect;
410
437
  if (dialect === undefined) {
411
438
  log.error(
412
- `meta verify --replay: no dialect — pass --dialect <postgres|sqlite>, or set migrate.dialect in .metaobjects/config.json`,
439
+ `meta verify --replay: no dialect — pass --dialect <postgres|sqlite>, ` +
440
+ `set 'dialect' in metaobjects.config.ts, or set migrate.dialect in .metaobjects/config.json`,
413
441
  );
414
442
  return 2;
415
443
  }
@@ -438,11 +466,7 @@ export async function verifyCommand(
438
466
  applied = await applyPending(engine.db, dir, { dryRun: false, dialect });
439
467
  } catch (err) {
440
468
  log.error(`meta verify --replay: ${(err as Error).message}`);
441
- log.error(
442
- `meta verify --replay: the committed chain does not apply to an empty database. ` +
443
- `Applied migrations are immutable, so fix this with a NEW migration that creates the ` +
444
- `missing object — not by editing a committed up.sql.`,
445
- );
469
+ for (const line of replayRemedy(err)) log.error(`meta verify --replay: ${line}`);
446
470
  return 1;
447
471
  }
448
472
 
@@ -700,13 +724,33 @@ export async function verifyCommand(
700
724
  antiPatternSection = skippedSection(`the scan failed: ${(err as Error).message}`);
701
725
  return;
702
726
  }
703
- antiPatternSection = ranSection(antiPatternRows(findings));
704
- if (findings.length === 0) return;
705
- log.warn(
706
- `meta verify — ${findings.length} place(s) hand-roll what MetaObjects can model ` +
707
- `(advisory — does not fail the build):`,
708
- );
709
- warnCapped(findings.map((f) => ` ${f.message}`), flags.limit, { structured });
727
+ // F52 — the one gate that can see a provider silently missing its base. Folded into
728
+ // this pass rather than given its own, because it is the same KIND of finding (a
729
+ // teachable warning that never touches the exit code) and one --no-antipatterns
730
+ // should suppress the whole advisory tier, not half of it.
731
+ let baseUrl: BaseUrlFinding[] = [];
732
+ try {
733
+ baseUrl = scanForMissingBaseUrl(projectRoot, forgeConfig?.apiPrefix ?? "");
734
+ } catch {
735
+ // Same discipline as above: an advisory scan never breaks verify. The anti-pattern
736
+ // half already ran, so its rows are still reported rather than lost to this catch.
737
+ }
738
+
739
+ antiPatternSection = ranSection([...antiPatternRows(findings), ...missingBaseUrlRows(baseUrl)]);
740
+ if (findings.length > 0) {
741
+ log.warn(
742
+ `meta verify — ${findings.length} place(s) hand-roll what MetaObjects can model ` +
743
+ `(advisory — does not fail the build):`,
744
+ );
745
+ warnCapped(findings.map((f) => ` ${f.message}`), flags.limit, { structured });
746
+ }
747
+ if (baseUrl.length > 0) {
748
+ log.warn(
749
+ `meta verify — ${baseUrl.length} entity-fetcher provider(s) mounted with no baseUrl ` +
750
+ `while apiPrefix is "${forgeConfig?.apiPrefix ?? ""}" (advisory — does not fail the build):`,
751
+ );
752
+ warnCapped(baseUrl.map((f) => ` ${f.message}`), flags.limit, { structured });
753
+ }
710
754
  }
711
755
 
712
756
  // -- template (prompt / output) drift --------------------------------------
@@ -1189,11 +1233,11 @@ export async function verifyCommand(
1189
1233
  strict: !flags.lax,
1190
1234
  });
1191
1235
  } catch (err) {
1192
- log.error(`verify --codegen: failed to load this package's metadata: ${(err as Error).message}`);
1193
- // The second door onto the same strict load. It carried no remedy at all, which
1194
- // meant a retirement diagnosed here told the author what broke and nothing about
1195
- // how to fix it — the half-true rule this file's sibling comment warns about.
1196
- for (const s of (err as { suggestions?: string[] }).suggestions ?? []) log.error(` ${s}`);
1236
+ // The second door onto the same strict load, and it must carry the same provenance
1237
+ // as the first: the ADR-0009 code, the FILE and json path the message cannot name,
1238
+ // and the loader's own remedies. It printed a bare message plus a hand-rolled
1239
+ // suggestions read — the half-true rule this file's sibling comment warns about.
1240
+ reportLoadError(log, "verify --codegen: failed to load this package's metadata", err);
1197
1241
  return 2;
1198
1242
  }
1199
1243
  }
@@ -1212,7 +1256,11 @@ export async function verifyCommand(
1212
1256
  }
1213
1257
 
1214
1258
  if (result.clean) {
1215
- say("meta verify — generated output is in sync with the metadata (no codegen drift).");
1259
+ // Says what it CHECKED, not more. Since 0.24.3 this gate asks whether each file's
1260
+ // GENERATED contribution is current — a hand edit `meta gen` preserves is exempt —
1261
+ // so "output is in sync with the metadata" claimed a stronger property than it had
1262
+ // verified, and did so over a file whose committed content contradicted the model.
1263
+ say("meta verify — every file's generated contribution is current (no codegen drift).");
1216
1264
  return 0;
1217
1265
  }
1218
1266
 
@@ -1220,15 +1268,32 @@ export async function verifyCommand(
1220
1268
  `meta verify — codegen drift (${result.driftedFiles.length} file(s) differ from a fresh regen):`,
1221
1269
  );
1222
1270
  for (const line of result.lines) log.error(` ${line}`);
1223
- log.error("Run 'meta gen' to regenerate, then commit the result.");
1271
+ // The remedy is PER CONVICTION KIND, not one line for the whole gate. `meta gen`
1272
+ // fixes stale generated output; it cannot fix a hand-edited names artifact, because
1273
+ // it MERGES the edit and exits 0 — so prescribing it there was the unbreakable loop
1274
+ // the hand-edit exemption exists to end, reintroduced for the one file the exemption
1275
+ // deliberately excludes. Both lines print when the drift set holds both kinds.
1276
+ if (result.driftedFiles.length > result.handEditedNames.length) {
1277
+ log.error("Run 'meta gen' to regenerate, then commit the result.");
1278
+ }
1279
+ if (result.handEditedNames.length > 0) {
1280
+ log.error(
1281
+ "For the names artifact(s) above, 'meta gen' will NOT help — it preserves a hand " +
1282
+ "edit by design. Revert the edit (or delete the file and re-run 'meta gen' to " +
1283
+ "re-emit it), and keep constants of your own in a file of your own.",
1284
+ );
1285
+ }
1224
1286
  return 1;
1225
1287
  }
1226
1288
 
1227
1289
  // -- docs drift -------------------------------------------------------------
1228
1290
  // Gated on --docs. Runs `meta docs` into a temp dir and diffs the committed docs
1229
- // tree. See lib/docs-drift.ts for the two deliberate differences from --codegen
1230
- // (a byte difference IS drift here, and a committed file a regen would not emit is
1231
- // never reported — `docs.outDir` is full of files MetaObjects did not write).
1291
+ // tree. See lib/docs-drift.ts for the deliberate differences from --codegen: a byte
1292
+ // difference IS drift here; a committed file a regen would not emit is reported ONLY
1293
+ // under `agent/` and only when it opens with the @generated marker (`docs.outDir` is
1294
+ // otherwise full of files MetaObjects did not write); and a page the project
1295
+ // GIT-IGNORES is exempt, because `docs.outDir` is a directory rather than a namespace
1296
+ // MetaObjects owns.
1232
1297
  //
1233
1298
  // It needs a config for the same reason `--codegen` does: `docs.outDir` says which
1234
1299
  // tree to compare against, and the `agent` surface will not even materialise
@@ -1264,21 +1329,44 @@ export async function verifyCommand(
1264
1329
  return 2;
1265
1330
  }
1266
1331
 
1332
+ // What the gate did NOT check, on BOTH the passing and the failing line. A gate
1333
+ // that quietly compared two of 589 pages and reported "no drift" would be
1334
+ // indistinguishable from one that compared everything.
1335
+ const notChecked =
1336
+ result.ignoreReason !== undefined
1337
+ ? `; .gitignore not consulted — ${result.ignoreReason} — so every page a fresh run emits was expected to be committed`
1338
+ : result.ignored > 0
1339
+ ? `; ${result.ignored} git-ignored page(s) not checked`
1340
+ : "";
1341
+
1267
1342
  if (result.clean) {
1268
1343
  // The denominator is what was actually COMPARED, so the passing line and the
1269
1344
  // failing line below count the same set. A gate whose two halves divide by
1270
1345
  // different numbers is how `verify --templates` came to report seven templates
1271
1346
  // vanishing between a red run and a green one.
1272
- say(`meta verify — ${result.checked} docs page(s) match a fresh 'meta docs' (no docs drift).`);
1347
+ say(
1348
+ `meta verify — ${result.checked} docs page(s) match a fresh 'meta docs' ` +
1349
+ `(no docs drift${notChecked}).`,
1350
+ );
1273
1351
  return 0;
1274
1352
  }
1275
1353
 
1276
1354
  log.error(
1277
1355
  `meta verify — docs drift (${result.driftedFiles.length} of ${result.checked} page(s) ` +
1278
- `differ from a fresh 'meta docs'):`,
1356
+ `differ from a fresh 'meta docs'${notChecked}):`,
1279
1357
  );
1280
1358
  for (const line of result.lines) log.error(` ${line}`);
1281
1359
  log.error("Run 'meta docs' to regenerate, then commit the result.");
1360
+ // The one-command path to green, named at the point of conviction — a page reported
1361
+ // as uncommitted is the one case where regenerating is NOT the answer.
1362
+ if (result.lines.some((l) => l.startsWith("+ "))) {
1363
+ log.error(
1364
+ result.ignoreReason === undefined
1365
+ ? "A page you deliberately do not commit is exempt once it is git-ignored."
1366
+ : `.gitignore was not consulted (${result.ignoreReason}), so a page you ` +
1367
+ `deliberately do not commit is reported above.`,
1368
+ );
1369
+ }
1282
1370
  return 1;
1283
1371
  }
1284
1372
  }
package/src/index.ts CHANGED
@@ -55,6 +55,14 @@ GLOBAL OPTIONS:
55
55
 
56
56
  GEN FLAGS:
57
57
  --dry-run Compute and print, don't write
58
+ --list Print the generator registry (name, tier, options) and exit —
59
+ no config or metadata required
60
+ --baseline <default|adopt|fresh>
61
+ First-time-on-existing-file behavior. Default: refuse a file that
62
+ cannot be proved to be generated output. "adopt" records the files
63
+ you have as the merge base and writes NOTHING — the first run a
64
+ project with no committed .hashes.json can perform. "fresh"
65
+ overwrites them with fresh output and re-baselines (discards edits).
58
66
  --no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
59
67
  --limit <n|all> How many advisory lines TEXT output prints before it truncates
60
68
  (default 20). Never applies to --format toon/json, which
@@ -69,7 +77,11 @@ DOCS FLAGS:
69
77
  [<project-root>] PROJECT ROOT to resolve metadata from — the directory that CONTAINS
70
78
  your metadata, NOT the metadata directory. Passing it SCOPES the run
71
79
  (default: cwd)
72
- --out <dir>, -o Output directory for the pages (default: ./docs)
80
+ --out <dir>, -o Output directory for THIS run (default: ./docs/generated). It does
81
+ NOT survive the run — set 'docs: { outDir }' in metaobjects.config.ts
82
+ for a directory that persists, which is what 'meta verify --docs'
83
+ measures. The default is a sub-directory on purpose: these pages are
84
+ regenerated and overwritten, and 'docs/' itself is usually yours.
73
85
  --templates <dir> Project root to resolve adopter templates/ overrides (default: <project-root>)
74
86
  --prompts <dir> Extra dir holding prompt .mustache sources for --site (e.g. data/templates/)
75
87
 
@@ -127,7 +139,7 @@ ship in later sub-projects. See https://metaobjects.com for docs.
127
139
  `;
128
140
 
129
141
  /** Focused per-subcommand usage slices shown by `<cmd> --help`. */
130
- const COMMAND_HELP: Record<string, string> = {
142
+ export const COMMAND_HELP: Record<string, string> = {
131
143
  gen: `meta gen — codegen TS targets from your declared metadata
132
144
 
133
145
  USAGE:
@@ -135,6 +147,14 @@ USAGE:
135
147
 
136
148
  FLAGS:
137
149
  --dry-run Compute and print, don't write
150
+ --list Print the generator registry (name, tier, options) and exit —
151
+ no config or metadata required
152
+ --baseline <default|adopt|fresh>
153
+ First-time-on-existing-file behavior. Default: refuse a file that
154
+ cannot be proved to be generated output. "adopt" records the files
155
+ you have as the merge base and writes NOTHING — the first run a
156
+ project with no committed .hashes.json can perform. "fresh"
157
+ overwrites them with fresh output and re-baselines (discards edits).
138
158
  --no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
139
159
  --limit <n|all> Advisory lines TEXT output prints before truncating (default 20)
140
160
  --format <toon|json|text> Output format (global flag; default toon off-TTY)
@@ -179,10 +199,17 @@ USAGE:
179
199
 
180
200
  FLAGS:
181
201
  --templates Template/prompt {{field}}↔payload drift (default when bare)
182
- --codegen Codegen drift — regenerate to temp dir and diff committed output
202
+ --codegen Codegen drift — is the GENERATED contribution current? A hand
203
+ edit 'meta gen' preserves is not drift; a stale generated part is.
204
+ An <Entity>.names.ts artifact is the exception: it must match a
205
+ fresh regen exactly — a hand edit in it is itself the defect.
183
206
  Needs metaobjects.config.ts; exit 2 if absent.
184
207
  --docs Docs drift — run 'meta docs' to a temp dir and diff docs.outDir
185
- Needs metaobjects.config.ts; exit 2 if absent.
208
+ Needs metaobjects.config.ts; exit 2 if absent. A page the
209
+ project GIT-IGNORES is exempt (docs.outDir is a directory, not
210
+ a namespace MetaObjects owns); the count of exempt pages is
211
+ reported either way, and a run where every page is ignored is
212
+ refused rather than reported clean.
186
213
  --db <url> Schema drift — live DB URL enables the schema-drift gate.
187
214
  Supports: file:, libsql:, postgres:, postgresql:
188
215
  D1 has no URL — use --dialect d1 / --d1 <binding> instead.
@@ -204,6 +231,10 @@ FLAGS:
204
231
  --d1 <binding> D1 binding name from wrangler.toml (only with --dialect d1)
205
232
  --remote Target remote D1 instead of local (only with --dialect d1) —
206
233
  the ONLY way to verify the actual deployed D1 database
234
+ --lax Load with the legacy OPEN attribute set instead of strict
235
+ (ADR-0023). Strict is the default: an undeclared or typo'd own
236
+ @attr fails verify with ERR_UNKNOWN_ATTR. Use only while
237
+ migrating a model onto a registered provider.
207
238
  --no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
208
239
  --no-requirement-lint Suppress the advisory requirement AUTHORING lint (not the gate)
209
240
  --limit <n|all> Advisory lines TEXT output prints PER SECTION before truncating
@@ -249,7 +280,11 @@ FLAGS:
249
280
  it SCOPES the run to that directory's own sources; no ancestor
250
281
  .metaobjects/config.json is consulted. Omitted (default), the project
251
282
  is discovered by walking up.
252
- --out <dir>, -o Output directory for the pages (default: ./docs)
283
+ --out <dir>, -o Output directory for THIS run (default: ./docs/generated). It does
284
+ NOT survive the run — set 'docs: { outDir }' in metaobjects.config.ts
285
+ for a directory that persists, which is what 'meta verify --docs'
286
+ measures. The default is a sub-directory on purpose: these pages are
287
+ regenerated and overwritten, and 'docs/' itself is usually yours.
253
288
  --model Emit the markdown model surface (entity + template pages)
254
289
  --api Emit the markdown api surface (generated SDK reference)
255
290
  --requirements Emit the declared requirement ledger (requirements.md + .toon).
@@ -258,7 +293,9 @@ FLAGS:
258
293
  agent/ui.md (the generated forms and grids) and
259
294
  agent/requirements.md (the ledger plus a claimed-node index).
260
295
  Needs metaobjects.config.ts (the dialect and physical names come
261
- from it); each page is skipped when its tier has nothing to describe.
296
+ from it); each page is skipped when its tier has nothing to describe
297
+ — agent/ui.md needs a UI generator (a form, hook or grid) in the
298
+ suite, not merely objects that could have one.
262
299
  --metamodel Document the built-in metamodel vocabulary (no metadata needed)
263
300
  --site Generate the browsable HTML documentation site (<out>/site/)
264
301
  --scaffold-site Copy the site's templates + assets into codegen/docs-site/ to own (theme) them
@@ -274,6 +311,16 @@ USAGE:
274
311
 
275
312
  FLAGS:
276
313
  --refresh-docs Refresh .metaobjects/AGENTS.md + CLAUDE.md after CLI upgrades
314
+ --docs-only Write only the agent-context files — no metadata sources, no
315
+ codegen scaffold, no config. Pair with --refresh-docs to update
316
+ an existing project after upgrading.
317
+ --server <lang> Declare a server language for the agent context (repeatable):
318
+ typescript | java | kotlin | csharp | python
319
+ ('node' is accepted as an alias for typescript; an unknown
320
+ value is refused, never dropped)
321
+ --client <fw> Declare a client framework for the agent context (repeatable):
322
+ react | tanstack | angular
323
+ --no-skills Skip the .claude/skills/ scaffold
277
324
  --force Overwrite existing files
278
325
  --quiet Suppress output
279
326
  --print-only Print what would be written, don't write
@@ -289,8 +336,8 @@ USAGE:
289
336
  meta agent-docs [--server <lang>]... [--client <fw>]... [--out <dir>] [flags]
290
337
 
291
338
  FLAGS:
292
- --server <lang> Server language (repeatable; e.g. csharp, kotlin, python, node)
293
- --client <fw> Client framework (repeatable; e.g. react, vue)
339
+ --server <lang> Server language (repeatable): typescript|java|kotlin|csharp|python
340
+ --client <fw> Client framework (repeatable): react|tanstack|angular
294
341
  --out <dir> Output directory (default: current directory)
295
342
  --no-skills Skip .claude/skills/ scaffold
296
343
  --no-wire-root Skip wiring root CLAUDE.md @import
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { log } from "./log.js";
24
24
  import type { AntiPatternFinding } from "./anti-patterns.js";
25
+ import type { BaseUrlFinding } from "./base-url-advisory.js";
25
26
 
26
27
  /**
27
28
  * How many advisory lines TEXT output prints before it truncates.
@@ -119,6 +120,20 @@ export function antiPatternRows(findings: readonly AntiPatternFinding[]): Adviso
119
120
  }));
120
121
  }
121
122
 
123
+ /**
124
+ * F52 findings in the same row shape, so the structured payload has ONE advisory list
125
+ * rather than a second one a consumer has to learn about. `rule` is the discriminator.
126
+ */
127
+ export function missingBaseUrlRows(findings: readonly BaseUrlFinding[]): AdvisoryFindingRow[] {
128
+ return findings.map((f) => ({
129
+ file: f.file,
130
+ line: f.line,
131
+ rule: "missing-base-url",
132
+ construct: f.construct,
133
+ message: f.message,
134
+ }));
135
+ }
136
+
122
137
  // ---------------------------------------------------------------------------
123
138
  // text output
124
139
  // ---------------------------------------------------------------------------
@@ -16,7 +16,8 @@
16
16
  // nag a legitimate one.
17
17
 
18
18
  import { readdirSync, readFileSync, statSync } from "node:fs";
19
- import { join, relative, sep } from "node:path";
19
+ import { join } from "node:path";
20
+ import { relPosix } from "./rel-posix.js";
20
21
 
21
22
  export interface AntiPatternFinding {
22
23
  file: string; // path relative to the scan root (posix-ish, sep-normalized)
@@ -220,10 +221,6 @@ function findingFor(file: string, lineNo: number, raw: string, isSql: boolean):
220
221
  };
221
222
  }
222
223
 
223
- function relPosix(root: string, abs: string): string {
224
- return relative(root, abs).split(sep).join("/");
225
- }
226
-
227
224
  function walk(dir: string, root: string, ignore: readonly RegExp[], acc: AntiPatternFinding[]): void {
228
225
  let entries;
229
226
  try {