@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
@@ -8,13 +8,14 @@ import {
8
8
  assemble, resolveAgentContextRoot, planScaffold,
9
9
  AGENT_CONTEXT_MANIFEST_PATH, type Manifest, type Stack,
10
10
  } from "@metaobjectsdev/sdk/agent-context";
11
- import { resolveStack } from "../lib/detect-stack.js";
11
+ import { assertKnownStackValues, resolveStack } from "../lib/detect-stack.js";
12
+ import { reportIgnoredScaffold } from "../lib/ignored-scaffold-check.js";
12
13
  import { parseInitArgs } from "../lib/args.js";
13
14
  import { log } from "../lib/log.js";
14
15
  import { cliVersion } from "../lib/version.js";
15
16
  import { declaredDependencyNames, type PackageManifest } from "../lib/package-manifest.js";
16
17
  import { findWranglerConfig, parseWranglerConfig } from "@metaobjectsdev/migrate-ts";
17
- import { readReferenceTemplate, type ReferenceGeneratorName } from "@metaobjectsdev/codegen-ts";
18
+ import { DEFAULT_DOCS_DIR, readReferenceTemplate, type ReferenceGeneratorName } from "@metaobjectsdev/codegen-ts";
18
19
 
19
20
  // ADR-0034 scaffold-and-own — `meta init` copies the codegen reference templates into
20
21
  // the consumer's repo so they OWN them; metaobjects.config.ts imports them locally.
@@ -138,9 +139,17 @@ export default defineConfig({
138
139
  barrel(),
139
140
  ],
140
141
  docs: {
141
- outDir: "./docs", // model + api surfaces both land here (run: meta docs)
142
+ outDir: "${DEFAULT_DOCS_DIR}", // every surface lands here (run: meta docs).
143
+ // A SUB-directory on purpose: these pages are regenerated
144
+ // and overwritten, and docs/ itself is usually yours.
142
145
  layout: "flat", // or "package" for multi-package models
143
- surfaces: ["model", "api"],
146
+ // surfaces defaults to ["model", "api", "requirements", "agent"] — all four.
147
+ // Deliberately NOT narrowed here: the requirements and agent surfaces emit ZERO
148
+ // files for a project that has nothing for them to describe, and the always-on
149
+ // agent context points at the agent/ pages by name. Scaffolding a narrower list
150
+ // turned that pointer into a pointer at nothing. Narrow it yourself if you want
151
+ // fewer docs — but note it says what your docs ARE, not which of them you commit
152
+ // (a page you do not commit is exempt from verify --docs once it is git-ignored).
144
153
  },
145
154
  });
146
155
  `;
@@ -474,6 +483,57 @@ async function writeOwnedGenerators(opts: InitOptions, result: InitResult): Prom
474
483
  await writeFile(abs, readReferenceTemplate(name), "utf8");
475
484
  result.created.push(rel);
476
485
  }
486
+ await writeCodegenTsconfig(opts, result);
487
+ }
488
+
489
+ /** The owned codegen tier's own tsconfig — see writeCodegenTsconfig. */
490
+ const CODEGEN_TSCONFIG_REL = "tsconfig.codegen.json";
491
+
492
+ const CODEGEN_TSCONFIG_BODY = `{
493
+ // The owned codegen tier typechecks itself.
494
+ //
495
+ // \`meta gen\` loads metaobjects.config.ts and ./codegen/** through jiti, which
496
+ // TRANSPILES WITHOUT TYPECHECKING. So a generator here can import a symbol the
497
+ // engine no longer exports, or call a method that does not exist, and every gate
498
+ // stays green until the import is evaluated — which for a generator you have not
499
+ // wired into \`generators: [...]\` may be never.
500
+ //
501
+ // A project's app tsconfig usually covers src/ and tests/ and NOT this tier, so
502
+ // without this file nothing compiles the code your build depends on. Run it with:
503
+ // npx tsc -p tsconfig.codegen.json --noEmit
504
+ "compilerOptions": {
505
+ "target": "ES2022",
506
+ "module": "nodenext",
507
+ "moduleResolution": "nodenext",
508
+ "strict": true,
509
+ "noEmit": true,
510
+ "skipLibCheck": true
511
+ },
512
+ "include": ["metaobjects.config.ts", "codegen/**/*.ts"]
513
+ }
514
+ `;
515
+
516
+ /**
517
+ * Scaffold a tsconfig that covers the tier `meta init` just handed the adopter.
518
+ *
519
+ * Reported from an estate that found TWO real defects the moment one existed: a
520
+ * generator importing `CODEGEN_ATTR_EMIT_ROUTES` (retired with the `@emit*` family,
521
+ * and invisible because that generator was not in `generators: [...]`), and a wired
522
+ * generator carrying five type errors including `ownFields()` on a node that has no
523
+ * such method. Both found on the first run.
524
+ *
525
+ * Written only when absent, like every other scaffolded file — an adopter who has
526
+ * their own arrangement keeps it, and it is reported as preserved rather than
527
+ * silently skipped.
528
+ */
529
+ async function writeCodegenTsconfig(opts: InitOptions, result: InitResult): Promise<void> {
530
+ const abs = join(opts.cwd, CODEGEN_TSCONFIG_REL);
531
+ if (await fileExists(abs)) {
532
+ result.preserved.push(CODEGEN_TSCONFIG_REL);
533
+ return;
534
+ }
535
+ await writeFile(abs, CODEGEN_TSCONFIG_BODY, "utf8");
536
+ result.created.push(CODEGEN_TSCONFIG_REL);
477
537
  }
478
538
 
479
539
  /**
@@ -543,6 +603,16 @@ async function writeConfigFile(opts: InitOptions, result: InitResult, agentDir:
543
603
  }
544
604
 
545
605
  export async function init(opts: InitOptions): Promise<InitResult> {
606
+ // Refused HERE, in the shared function, before anything is written — NOT at one CLI
607
+ // entry point. It was at `initCommand`'s arg parse only, and `meta agent-docs` calls
608
+ // this directly: `meta init --docs-only --server klingon` exited 2 with the message
609
+ // while `meta agent-docs --server klingon` exited 0, reported "11 files", and recorded
610
+ // `"servers": []`. That is the worse of the two doors to miss — `agent-docs` is the
611
+ // canonical redirect target for every language port, so the ports used the unguarded
612
+ // one. A non-empty override array suppresses both the prior manifest's stack AND
613
+ // detection, so a dropped value does not degrade, it inverts three statements in the
614
+ // generated context.
615
+ assertKnownStackValues({ servers: opts.servers ?? [], clients: opts.clients ?? [] });
546
616
  const result: InitResult = { created: [], preserved: [], removed: [], warnings: [] };
547
617
  const agentDir = join(opts.cwd, DEFAULT_METAOBJECTS_DIR);
548
618
  const metaobjectsDir = join(opts.cwd, DEFAULT_METADATA_DIR);
@@ -607,6 +677,7 @@ export async function init(opts: InitOptions): Promise<InitResult> {
607
677
  );
608
678
  result.created.push(".metaobjects/AGENTS.md", ".metaobjects/CLAUDE.md", ".claude/skills/metaobjects-*", AGENT_CONTEXT_MANIFEST_PATH);
609
679
  for (const name of SCAFFOLDED_GENERATOR_NAMES) result.created.push(`${OWNED_GENERATORS_DIR}/${name}.ts`);
680
+ result.created.push(CODEGEN_TSCONFIG_REL);
610
681
  result.created.push("metaobjects.config.ts", DB_STUB_REL_PATH, ".gitignore");
611
682
  return result;
612
683
  }
@@ -713,7 +784,7 @@ export async function init(opts: InitOptions): Promise<InitResult> {
713
784
  result.preserved.push(".gitignore");
714
785
  }
715
786
 
716
- await ensureEsmPackageType(opts.cwd, result);
787
+ await prepareManifestForScaffold(opts.cwd, result);
717
788
 
718
789
  return result;
719
790
  }
@@ -734,8 +805,13 @@ export async function init(opts: InitOptions): Promise<InitResult> {
734
805
  * So set it — but never silently take a real CommonJS project with it. A project
735
806
  * that has actual CJS sources gets a loud, specific warning instead of an edit,
736
807
  * because changing a module system out from under working code is not ours to do.
808
+ *
809
+ * IT ALSO DECLARES WHAT THE SCAFFOLD AND THE GENERATED OUTPUT IMPORT, and the name it used
810
+ * to carry (`ensureEsmPackageType`) is why that went wrong: a function named for one job
811
+ * accreted a second, and the second silently inherited the first's early returns. The
812
+ * declarations are unconditional on a readable manifest — see the comment at that point.
737
813
  */
738
- async function ensureEsmPackageType(cwd: string, result: InitResult): Promise<void> {
814
+ async function prepareManifestForScaffold(cwd: string, result: InitResult): Promise<void> {
739
815
  const pkgPath = join(cwd, "package.json");
740
816
  if (!(await fileExists(pkgPath))) {
741
817
  // No package.json at all: say what is needed rather than inventing a manifest
@@ -760,39 +836,59 @@ async function ensureEsmPackageType(cwd: string, result: InitResult): Promise<vo
760
836
  return;
761
837
  }
762
838
 
763
- if (pkg.type === "module") return; // already correct — nothing to do, nothing to say
764
-
765
- if (await hasCommonJsSources(cwd)) {
766
- result.warnings.push(
767
- 'this project has CommonJS sources, so `"type": "module"` was NOT set for you — ' +
768
- "but the scaffolded generators and all generated code are ESM and will not " +
769
- "compile without it. Either migrate the project to ESM, or keep the generated " +
770
- "code in a sub-directory with its own package.json declaring `\"type\": \"module\"`.",
771
- );
772
- return;
839
+ // TWO INDEPENDENT JOBS FROM HERE, and they must not share an exit.
840
+ //
841
+ // The module system is one; declaring what the scaffold and the generated output import
842
+ // is the other, and it is true of every project with a readable manifest — including the
843
+ // one that already had `"type": "module"` and needs no module-system edit at all. Those
844
+ // two were folded together, below this function's early returns, and the consequence was
845
+ // that the adopter who had set their project up CORRECTLY got no declarations and no
846
+ // warning: `meta gen` worked (the CLI aliases those specifiers for itself) while `tsc`
847
+ // reported TS2307 on all five files `meta init` had just written. Under a strict
848
+ // installer that is every one of them; npm's hoisting hid roughly half.
849
+ //
850
+ // `moduleSystemNote` is computed first and applied last so the two jobs share ONE write
851
+ // of the manifest, rather than one job's early return deciding the other's fate.
852
+ let moduleSystemNote: string | undefined;
853
+
854
+ if (pkg.type !== "module") {
855
+ if (await hasCommonJsSources(cwd)) {
856
+ // `"type": "module"` is NOT set — changing a module system out from under working
857
+ // CJS code is not ours to do. The dependency declarations below still apply: they
858
+ // are about what the generated code imports, not about how it is loaded.
859
+ result.warnings.push(
860
+ 'this project has CommonJS sources, so `"type": "module"` was NOT set for you — ' +
861
+ "but the scaffolded generators and all generated code are ESM and will not " +
862
+ "compile without it. Either migrate the project to ESM, or keep the generated " +
863
+ "code in a sub-directory with its own package.json declaring `\"type\": \"module\"`.",
864
+ );
865
+ } else {
866
+ const declaredType = pkg.type; // read BEFORE the mutation below overwrites it
867
+ pkg.type = "module";
868
+ // Past tense, deliberately: this reports an edit already made. The imperative
869
+ // ("set `\"type\": \"module\"`") read as a TODO on the one line a newcomer sees
870
+ // last, so a scaffold that had just done the right thing looked like it had failed.
871
+ //
872
+ // And it must not claim the manifest was SILENT on the point: `npm init -y` writes
873
+ // `"type": "commonjs"` explicitly (npm 11.x), which is the dominant first-touch path,
874
+ // so "declared no module system" was false exactly where it is read most. Report what
875
+ // was actually there.
876
+ const previous = typeof declaredType === "string" ? declaredType : undefined;
877
+ moduleSystemNote =
878
+ `package.json ${previous === undefined ? "declared no module system" : `declared "type": "${previous}"`} — ` +
879
+ 'set `"type": "module"` for you, because MetaObjects scaffolds and generates ESM, ' +
880
+ "which a CommonJS project cannot compile.";
881
+ }
773
882
  }
774
883
 
775
- const declaredType = pkg.type; // read BEFORE the mutation below overwrites it
776
- pkg.type = "module";
777
884
  const added = addScaffoldDevDependencies(pkg);
778
885
  const addedRuntime = addScaffoldRuntimeDependencies(pkg);
779
- // Preserve the file's existing indentation rather than reformatting someone's manifest.
780
- const indent = /\n(\s+)"/.exec(raw)?.[1] ?? " ";
781
- await writeFile(pkgPath, `${JSON.stringify(pkg, null, indent)}\n`, "utf8");
782
- // Past tense, deliberately: this reports an edit already made. The imperative
783
- // ("set `\"type\": \"module\"`") read as a TODO on the one line a newcomer sees
784
- // last, so a scaffold that had just done the right thing looked like it had failed.
785
- //
786
- // And it must not claim the manifest was SILENT on the point: `npm init -y` writes
787
- // `"type": "commonjs"` explicitly (npm 11.x), which is the dominant first-touch path,
788
- // so "declared no module system" was false exactly where it is read most. Report what
789
- // was actually there.
790
- const previous = typeof declaredType === "string" ? declaredType : undefined;
791
- result.warnings.push(
792
- `package.json ${previous === undefined ? "declared no module system" : `declared "type": "${previous}"`} — ` +
793
- 'set `"type": "module"` for you, because MetaObjects scaffolds and generates ESM, ' +
794
- "which a CommonJS project cannot compile.",
795
- );
886
+ if (moduleSystemNote !== undefined || added.length > 0 || addedRuntime.length > 0) {
887
+ // Preserve the file's existing indentation rather than reformatting someone's manifest.
888
+ const indent = /\n(\s+)"/.exec(raw)?.[1] ?? " ";
889
+ await writeFile(pkgPath, `${JSON.stringify(pkg, null, indent)}\n`, "utf8");
890
+ }
891
+ if (moduleSystemNote !== undefined) result.warnings.push(moduleSystemNote);
796
892
  if (added.length > 0) {
797
893
  result.warnings.push(
798
894
  `added ${added.join(" + ")} to devDependencies — the scaffolded ` +
@@ -807,6 +903,22 @@ async function ensureEsmPackageType(cwd: string, result: InitResult): Promise<vo
807
903
  "installed. Run your package manager's install before `meta gen`.",
808
904
  );
809
905
  }
906
+ // Never degrade in silence. The first version of this returned `{}` on any failure to
907
+ // read runtime-ts's peer ranges and skipped the three packages that come from them —
908
+ // which is exactly what happened against the published package, and the run looked
909
+ // successful while leaving the project with the six TS2307s this code exists to
910
+ // prevent. A fallback that cannot be observed is indistinguishable from a bug.
911
+ const undeclarable = SCAFFOLD_OUTPUT_PEERS.filter(
912
+ (name) => !declaredDependencyNames(pkg as PackageManifest).has(name),
913
+ );
914
+ if (undeclarable.length > 0) {
915
+ result.warnings.push(
916
+ `could NOT declare ${undeclarable.join(" + ")} — the version ranges are read from ` +
917
+ "@metaobjectsdev/runtime-ts's own peerDependencies and that manifest could not be " +
918
+ "read. Generated code imports them, so add them to dependencies by hand or " +
919
+ "`npx tsc` will report TS2307 on files `meta gen` writes.",
920
+ );
921
+ }
810
922
  }
811
923
 
812
924
  /**
@@ -904,13 +1016,39 @@ function scaffoldRuntimeDependencies(): Record<string, string> {
904
1016
  return wanted;
905
1017
  }
906
1018
 
907
- /** `@metaobjectsdev/runtime-ts`'s declared peer ranges, or `{}` if it cannot be read. */
1019
+ /**
1020
+ * `@metaobjectsdev/runtime-ts`'s declared peer ranges, or `{}` if they cannot be read.
1021
+ *
1022
+ * Resolves the package's ENTRY and walks up to the nearest package.json, rather than
1023
+ * asking for `"@metaobjectsdev/runtime-ts/package.json"` directly. That direct form is
1024
+ * the obvious one and it is WRONG: a package's `exports` map gates every subpath, and
1025
+ * runtime-ts's exports are `.` / `./drivers` / `./fastify` / `./drizzle-fastify` /
1026
+ * `./hono` — no `./package.json`. Under real Node it throws
1027
+ * ERR_PACKAGE_PATH_NOT_EXPORTED. It appeared to work here only because the workspace
1028
+ * runs this under bun against a symlinked source tree; against the published package it
1029
+ * failed on the first try, silently, and `meta init` declared one package instead of
1030
+ * four. Resolving the entry is never gated — `.` is the one subpath every package
1031
+ * exports — so this form works under npm, pnpm, bun and a linked checkout alike.
1032
+ */
908
1033
  function runtimeTsPeerRanges(): Record<string, string> {
909
1034
  try {
910
1035
  const req = createRequire(import.meta.url);
911
- const manifestPath = req.resolve("@metaobjectsdev/runtime-ts/package.json");
912
- const manifest = JSON.parse(readFileSyncWrap(manifestPath, "utf8")) as PackageManifest;
913
- return (manifest.peerDependencies ?? {}) as Record<string, string>;
1036
+ let dir = dirname(req.resolve("@metaobjectsdev/runtime-ts"));
1037
+ // The entry sits under dist/; the manifest is at the package root above it.
1038
+ for (let hops = 0; hops < 8; hops++) {
1039
+ const candidate = join(dir, "package.json");
1040
+ if (existsSyncWrap(candidate)) {
1041
+ const manifest = JSON.parse(readFileSyncWrap(candidate, "utf8")) as PackageManifest & { name?: string };
1042
+ // Guard against stopping at a nested manifest that is not the package itself.
1043
+ if (manifest.name === "@metaobjectsdev/runtime-ts") {
1044
+ return (manifest.peerDependencies ?? {}) as Record<string, string>;
1045
+ }
1046
+ }
1047
+ const up = dirname(dir);
1048
+ if (up === dir) break;
1049
+ dir = up;
1050
+ }
1051
+ return {};
914
1052
  } catch {
915
1053
  return {};
916
1054
  }
@@ -1010,6 +1148,9 @@ export async function initCommand(args: string[], cwd: string): Promise<number>
1010
1148
  let flags;
1011
1149
  try {
1012
1150
  flags = parseInitArgs(args);
1151
+ // Also here, so a bad value is a USAGE error (exit 2) rather than the exit 1 an
1152
+ // init failure gets. `init()` refuses it too, which is what covers every other door.
1153
+ assertKnownStackValues({ servers: flags.servers ?? [], clients: flags.clients ?? [] });
1013
1154
  } catch (err) {
1014
1155
  log.error((err as Error).message);
1015
1156
  return 2;
@@ -1038,6 +1179,14 @@ export async function initCommand(args: string[], cwd: string): Promise<number>
1038
1179
  }
1039
1180
 
1040
1181
  if (!flags.quiet) {
1182
+ // A count is not a guarantee that the files reached the repository. Many projects
1183
+ // git-ignore `.claude/` under a comment about credentials — a common and defensible
1184
+ // convention — and eight of the eleven files land there. Ignored, they exist only on
1185
+ // the machine that ran this: not committed, absent from CI and from a fresh clone,
1186
+ // invisible to a teammate, while `.agent-context.json` (which IS committed) tracks
1187
+ // them. That silently voids the whole downstream agent-context design, and the answer
1188
+ // was available from the outputs this command had just written.
1189
+ reportIgnoredScaffold(cwd, result.created);
1041
1190
  if (flags.docsOnly) {
1042
1191
  log.info(`Scaffolded the MetaObjects agent context (${result.created.length} files): .metaobjects/AGENTS.md + .claude/skills/metaobjects-*.`);
1043
1192
  for (const w of result.warnings) log.info(` ${w}`);
@@ -58,8 +58,16 @@ import {
58
58
  } from "../lib/wrangler.js";
59
59
  import { buildProjectionViews } from "@metaobjectsdev/codegen-ts";
60
60
  import { tokensToAllowOptions, describeChange } from "../lib/allow.js";
61
+ import { reportLoadError } from "../lib/load-error.js";
61
62
 
62
- const MIGRATE_HELP_TEXT = `meta migrate — diff metadata vs live DB; emit migration SQL files
63
+ /**
64
+ * Print a load failure with everything the loader's ADR-0009 envelope carried — the stable
65
+ * code, the file, the json path, and the loader's own next steps — instead of the bare
66
+ * `err.message` five commands used to print. See `lib/load-error.ts`.
67
+ */
68
+
69
+
70
+ export const MIGRATE_HELP_TEXT = `meta migrate — diff metadata vs live DB; emit migration SQL files
63
71
 
64
72
  USAGE:
65
73
  meta migrate [baseline|apply-pending] [flags]
@@ -628,7 +636,7 @@ export async function migrateCommand(
628
636
  ...postgresLoadOptions,
629
637
  });
630
638
  } catch (err) {
631
- log.error(`failed to load metadata: ${(err as Error).message}`);
639
+ reportLoadError(log, "failed to load metadata", err);
632
640
  return 2;
633
641
  }
634
642
 
@@ -1056,7 +1064,7 @@ export async function runBaseline(
1056
1064
  ...baselineLoadOptions,
1057
1065
  });
1058
1066
  } catch (err) {
1059
- log.error(`migrate baseline: failed to load metadata: ${(err as Error).message}`);
1067
+ reportLoadError(log, "migrate baseline: failed to load metadata", err);
1060
1068
  return 2;
1061
1069
  }
1062
1070
  const baselineViews = buildProjectionViews(metadata, { dialect: config.dialect, columnNamingStrategy: baselineStrategy });
@@ -1195,7 +1203,7 @@ export async function runOfflineGenerate(
1195
1203
  ...offlineLoadOptions,
1196
1204
  });
1197
1205
  } catch (err) {
1198
- log.error(`migrate: failed to load metadata: ${(err as Error).message}`);
1206
+ reportLoadError(log, "migrate: failed to load metadata", err);
1199
1207
  return 2;
1200
1208
  }
1201
1209
 
@@ -1460,7 +1468,7 @@ async function runD1Migrate(
1460
1468
  ...d1LoadOptions,
1461
1469
  });
1462
1470
  } catch (err) {
1463
- log.error(`migrate: failed to load metadata: ${(err as Error).message}`);
1471
+ reportLoadError(log, "migrate: failed to load metadata", err);
1464
1472
  return 2;
1465
1473
  }
1466
1474
 
@@ -14,6 +14,7 @@ import { parsePromptSnapshotArgs } from "../lib/args.js";
14
14
  import { log } from "../lib/log.js";
15
15
  import { FileProvider } from "../lib/file-provider.js";
16
16
  import { snapshotPaths, unifiedDiff } from "../lib/snapshot.js";
17
+ import { reportLoadError } from "../lib/load-error.js";
17
18
  import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
18
19
  import { loadMemory, resolveCollection } from "@metaobjectsdev/sdk";
19
20
  import { TYPE_TEMPLATE, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_FORMAT } from "@metaobjectsdev/metadata";
@@ -78,7 +79,7 @@ export async function promptSnapshotCommand(args: string[], cwd: string): Promis
78
79
  ...configLoadOptions,
79
80
  });
80
81
  } catch (err) {
81
- log.error(`failed to load metadata: ${(err as Error).message}`);
82
+ reportLoadError(log, "failed to load metadata", err);
82
83
  return 1;
83
84
  }
84
85
 
@@ -20,8 +20,8 @@
20
20
  // The `--json` flag this once advertised in its own --help, twice, is not coming back: the
21
21
  // CLI rejects a bare `--json` before a command ever sees its args (index.ts, `formatAlias`),
22
22
  // on purpose — one global spelling for all three formats.
23
- import { composeRegistry, coreProviders, buildVocabularyCatalog } from "@metaobjectsdev/metadata";
24
- import { forgeTypesProvider } from "@metaobjectsdev/sdk";
23
+ import { composeRegistry, buildVocabularyCatalog } from "@metaobjectsdev/metadata";
24
+ import { defaultLoadMemoryProviders } from "@metaobjectsdev/sdk";
25
25
  import { log } from "../lib/log.js";
26
26
  import { emitStructured, type OutputFormat } from "../lib/format.js";
27
27
 
@@ -226,7 +226,16 @@ export async function typesCommand(args: string[], fmt: OutputFormat = "text"):
226
226
  // @sortable, @dbColumnType), `view.textarea` with none (no @rows), and the eight
227
227
  // documentation commonAttrs — @title among them — were absent entirely. This is the same
228
228
  // provider set the loader composes, so what this prints is what the loader accepts.
229
- const registry = composeRegistry([...coreProviders, forgeTypesProvider], { validate: true });
229
+ // `defaultLoadMemoryProviders` — THE composition, not a second spelling of it. This read
230
+ // `[...coreProviders]`, which is what that constant happens to equal today, so the claim
231
+ // above ("the same provider set the loader composes") was true by coincidence and the gate
232
+ // that pins the default composition against the canonical manifest had no jurisdiction
233
+ // here: add or drop a provider in the default set and `meta types` would silently print a
234
+ // vocabulary the loader does not accept, with nothing failing. `forgeTypesProvider` was
235
+ // once in this list, and printing it as registered vocabulary was part of how a TS-only
236
+ // registration read as cross-port: `meta types forge --all` listed 20 attrs no other port
237
+ // has. One composition, one door.
238
+ const registry = composeRegistry([...defaultLoadMemoryProviders], { validate: true });
230
239
  const catalog = buildVocabularyCatalog(registry);
231
240
 
232
241
  // Flatten the catalog into searchable entries.
@@ -21,7 +21,13 @@
21
21
  import { readFile, writeFile } from "node:fs/promises";
22
22
  import { extname, relative } from "node:path";
23
23
  import { resolveCollection } from "@metaobjectsdev/sdk";
24
- import { rewriteDocument } from "@metaobjectsdev/metadata";
24
+ import {
25
+ rewriteDocument,
26
+ composeRegistry,
27
+ coreProviders,
28
+ SUBTYPE_BASE,
29
+ retirementSuggestions,
30
+ } from "@metaobjectsdev/metadata";
25
31
  import { log } from "../lib/log.js";
26
32
 
27
33
  /** YAML authoring (ADR-0006). Rewritten by the `yaml`-backed arm, loaded on demand below. */
@@ -96,7 +102,29 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
96
102
  ? (await import("@metaobjectsdev/metadata/vocabulary-rewrite-yaml")).rewriteYamlDocument
97
103
  : undefined;
98
104
 
99
- const opts = flags.maxVersion !== undefined ? { maxVersion: flags.maxVersion } : {};
105
+ // Which types have an ABSTRACT ANCHOR at `<type>.base`, derived from the core registry
106
+ // rather than listed here: `base` is an anchor exactly when the type registers some OTHER
107
+ // subtype for it to anchor. The rewriter is registry-free on purpose, so the caller — this
108
+ // command, which can build a registry — supplies the fact.
109
+ //
110
+ // Without it, `meta upgrade` answered "nothing to rewrite" on an estate whose
111
+ // `{"object.base": …}` the loader refuses (ERR_ABSTRACT_SUBTYPE_AUTHORED) — the change
112
+ // §10A of the 1.0 guide LEADS WITH. The one command whose job is "what does the new
113
+ // version need me to change?" said "nothing", and the adopter then hit the load failure.
114
+ const registry = composeRegistry(coreProviders);
115
+ // `allTypes()` yields one TypeId per registered type.subType pair, so take the distinct
116
+ // TYPE names off it.
117
+ const abstractAnchorTypes = [...new Set(registry.allTypes().map((id) => id.type))]
118
+ .filter((t) => {
119
+ const subs = registry.allSubTypesOf(t);
120
+ return subs.includes(SUBTYPE_BASE) && subs.some((sub) => sub !== SUBTYPE_BASE);
121
+ })
122
+ .sort();
123
+
124
+ const opts = {
125
+ abstractAnchorTypes,
126
+ ...(flags.maxVersion !== undefined ? { maxVersion: flags.maxVersion } : {}),
127
+ };
100
128
 
101
129
  for (const file of files) {
102
130
  const rel = relative(projectRoot, file);
@@ -120,10 +148,22 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
120
148
  log.info(`\n${rel}`);
121
149
  for (const c of r.changes) log.info(` ${c.line}: @${c.from} → ${c.to}`);
122
150
  for (const f of r.refusals) {
151
+ // The WHY and the replacement both print, and the guide is not an alternative to
152
+ // them. This used to read `Retired in <since>. See <guide>` — dropping `why`
153
+ // whenever a guide existed, which is almost always, and dropping `replacedBy`
154
+ // unconditionally. For the `@forge*` entries that removed the only actionable
155
+ // sentence there is ("opt the provider in explicitly"), and for an authored
156
+ // `<type>.base` it announced a retirement of something that was never authorable —
157
+ // telling the reader they used to have a working feature. Hence "no longer accepted
158
+ // as of", which is true of both, with the carefully-written `why` beside it.
123
159
  log.warn(
124
- ` ${f.line}: ${f.subject}${f.value !== undefined ? `: ${f.value}` : ""} — needs a decision. ` +
125
- `Retired in ${f.since}. ${f.migration !== undefined ? `See ${f.migration}` : f.why}`,
160
+ ` ${f.line}: ${f.subject}${f.value !== undefined ? `: ${f.value}` : ""} — needs a decision ` +
161
+ `(no longer accepted as of ${f.since}).`,
126
162
  );
163
+ log.warn(` ${f.why}`);
164
+ // The same suggestion list every OTHER door prints for a retirement, rather than a
165
+ // second rendering of the same note that can drift from it.
166
+ for (const line of retirementSuggestions(f)) log.warn(` ${line}`);
127
167
  }
128
168
 
129
169
  totalChanges += r.changes.length;