cyber-sdd 0.7.0 → 0.7.2

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 (150) hide show
  1. package/.claude-plugin/plugin.json +1 -2
  2. package/.codex-plugin/plugin.json +1 -2
  3. package/CHANGELOG.md +320 -0
  4. package/agents/sdd-scanner.md +1 -1
  5. package/package.json +11 -12
  6. package/plugin.json +55 -0
  7. package/skills/align-spec/SKILL.md +1 -1
  8. package/skills/align-spec/scripts/align-spec.mjs +6755 -12
  9. package/skills/blast-estimate/README.md +3 -3
  10. package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -12
  11. package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -12
  12. package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -12
  13. package/skills/check-plan-safety/SKILL.md +2 -3
  14. package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -12
  15. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -12
  16. package/skills/check-project-specs/SKILL.md +1 -1
  17. package/skills/check-project-specs/scripts/check-project-specs.mjs +593 -12
  18. package/skills/check-retired-terms/SKILL.md +2 -3
  19. package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -12
  20. package/skills/check-scenario-overlap/SKILL.md +1 -2
  21. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +177 -12
  22. package/skills/check-spec-references/SKILL.md +2 -2
  23. package/skills/check-spec-references/scripts/check-spec-references.mjs +287 -12
  24. package/skills/check-spec-structure/SKILL.md +1 -2
  25. package/skills/check-spec-structure/scripts/check-spec-structure.mjs +243 -12
  26. package/skills/collision-ladder/README.md +2 -2
  27. package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -12
  28. package/skills/combat-log-governance/SKILL.md +1 -1
  29. package/skills/concept-index/SKILL.md +1 -1
  30. package/skills/concept-index/scripts/concept-index.mjs +183 -12
  31. package/skills/discover-plans/README.md +3 -3
  32. package/skills/discover-plans/SKILL.md +1 -1
  33. package/skills/discover-plans/scripts/discover-plans.mjs +137 -12
  34. package/skills/discover-specs/README.md +3 -3
  35. package/skills/discover-specs/SKILL.md +1 -1
  36. package/skills/discover-specs/scripts/discover-specs.mjs +321 -12
  37. package/skills/doctrine-loop/SKILL.md +1 -1
  38. package/skills/gate-validation-governance/SKILL.md +1 -1
  39. package/skills/init/README.md +2 -2
  40. package/skills/init/scripts/wire-statusline.mjs +168 -12
  41. package/skills/manage-ignore/README.md +3 -3
  42. package/skills/manage-ignore/SKILL.md +1 -2
  43. package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -12
  44. package/skills/manage-scenario-bridge/README.md +3 -3
  45. package/skills/manage-scenario-bridge/SKILL.md +1 -1
  46. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +239 -12
  47. package/skills/manage-spec-anchors/README.md +3 -3
  48. package/skills/manage-spec-anchors/SKILL.md +1 -1
  49. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -12
  50. package/skills/mission-graph/README.md +2 -2
  51. package/skills/mission-graph/SKILL.md +1 -1
  52. package/skills/mission-graph/scripts/mission-graph.mjs +1040 -12
  53. package/skills/pause-mission/SKILL.md +1 -1
  54. package/skills/place-node/SKILL.md +2 -2
  55. package/skills/place-node/scripts/place-node.mjs +117 -12
  56. package/skills/plan-retirement/README.md +2 -2
  57. package/skills/plan-retirement/scripts/retire-plans.mjs +123 -12
  58. package/skills/resolve-governances/README.md +3 -3
  59. package/skills/resolve-governances/SKILL.md +1 -1
  60. package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -12
  61. package/skills/resolve-tracking/SKILL.md +1 -1
  62. package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -12
  63. package/skills/scaffold-project-spec/SKILL.md +1 -1
  64. package/skills/spec-gate/README.md +12 -9
  65. package/skills/spec-gate/SKILL.md +2 -2
  66. package/skills/spec-gate/scripts/check-spec-state.mjs +422 -12
  67. package/skills/spec-gate/scripts/check-suite.mjs +6500 -12
  68. package/skills/spec-gate/scripts/classify-edit-class.mjs +6579 -12
  69. package/skills/spec-producer-governance/SKILL.md +2 -2
  70. package/skills/start-mission/SKILL.md +1 -1
  71. package/skills/suite-format-governance/README.md +1 -1
  72. package/skills/suite-format-governance/SKILL.md +1 -1
  73. package/skills/touch-set-correction/README.md +5 -5
  74. package/skills/touch-set-correction/SKILL.md +1 -1
  75. package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -12
  76. package/skills/verify-scenarios/README.md +6 -6
  77. package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -12
  78. package/.plugin/pins.json +0 -3
  79. package/.plugin/plugin.json +0 -19
  80. package/dist/align-spec/scripts/align-spec.js +0 -102
  81. package/dist/check-project-specs/scripts/check-project-specs.js +0 -312
  82. package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +0 -175
  83. package/dist/check-spec-references/scripts/check-spec-references.js +0 -285
  84. package/dist/check-spec-structure/scripts/check-spec-structure.js +0 -241
  85. package/dist/concept-index/scripts/concept-index.js +0 -181
  86. package/dist/discover-specs/scripts/discover-specs.js +0 -318
  87. package/dist/spec-gate/scripts/check-spec-state.js +0 -420
  88. package/dist/spec-gate/scripts/check-suite.js +0 -317
  89. package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
  90. package/dist/spec-gate/vendor/gherkin-cli.js +0 -6369
  91. package/skills/align-spec/scripts/align-spec.bundle.mjs +0 -6753
  92. package/skills/align-spec/scripts/align-spec.mts +0 -188
  93. package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +0 -593
  94. package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
  95. package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +0 -251
  96. package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
  97. package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +0 -233
  98. package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
  99. package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +0 -107
  100. package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
  101. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +0 -248
  102. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
  103. package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +0 -584
  104. package/skills/check-project-specs/scripts/check-project-specs.mts +0 -370
  105. package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +0 -184
  106. package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
  107. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +0 -175
  108. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
  109. package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +0 -285
  110. package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
  111. package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +0 -241
  112. package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
  113. package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +0 -7041
  114. package/skills/collision-ladder/scripts/collision-ladder.mts +0 -662
  115. package/skills/concept-index/scripts/concept-index.bundle.mjs +0 -181
  116. package/skills/concept-index/scripts/concept-index.mts +0 -246
  117. package/skills/discover-plans/scripts/discover-plans.bundle.mjs +0 -135
  118. package/skills/discover-plans/scripts/discover-plans.mts +0 -215
  119. package/skills/discover-specs/scripts/discover-specs.bundle.mjs +0 -319
  120. package/skills/discover-specs/scripts/discover-specs.mts +0 -420
  121. package/skills/init/scripts/wire-statusline.bundle.mjs +0 -166
  122. package/skills/init/scripts/wire-statusline.mts +0 -279
  123. package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +0 -244
  124. package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
  125. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +0 -237
  126. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
  127. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +0 -308
  128. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
  129. package/skills/mission-graph/scripts/mission-graph.bundle.mjs +0 -1038
  130. package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
  131. package/skills/place-node/scripts/place-node.bundle.mjs +0 -115
  132. package/skills/place-node/scripts/place-node.mts +0 -158
  133. package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +0 -121
  134. package/skills/plan-retirement/scripts/retire-plans.mts +0 -205
  135. package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +0 -350
  136. package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
  137. package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +0 -150
  138. package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
  139. package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +0 -420
  140. package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
  141. package/skills/spec-gate/scripts/check-suite.bundle.mjs +0 -6498
  142. package/skills/spec-gate/scripts/check-suite.mts +0 -538
  143. package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +0 -6577
  144. package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
  145. package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
  146. package/skills/spec-gate/vendor/gherkin-cli.mjs +0 -6545
  147. package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +0 -6655
  148. package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -429
  149. package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +0 -6537
  150. package/skills/verify-scenarios/scripts/verify-scenarios.mts +0 -470
@@ -1,12 +1,310 @@
1
- // Launcher for the engine of the same name. Run it as `node <skill>/scripts/<engine>.mjs`.
2
- // A checkout runs the .mts source. An npm install keeps the skill under node_modules, where Node
3
- // will not strip types, so it runs the self-contained bundle built for the package.
4
- import { spawnSync } from 'node:child_process'
5
- import { fileURLToPath } from 'node:url'
6
-
7
- const self = fileURLToPath(import.meta.url)
8
- const installed = /[\\/]node_modules[\\/]/.test(self)
9
- const target = self.replace(/\.mjs$/, installed ? '.bundle.mjs' : '.mts')
10
- const run = spawnSync(process.execPath, [...process.execArgv, target, ...process.argv.slice(2)], { stdio: 'inherit' })
11
- if (run.error) throw run.error
12
- process.exitCode = run.status ?? 1
1
+ #!/usr/bin/env node
2
+ // Compiled by `universal-plugin plugin build` from src/manage-spec-anchors/manage-spec-anchors.mts. Edit the source, not this file.
3
+ globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs","commands":{}};
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ //#region src/manage-spec-anchors/manage-spec-anchors.mts
8
+ const ANCHORS_CONFIG = ".agents/sdd/spec-anchors.toml";
9
+ const LIFECYCLE_STATUSES = /* @__PURE__ */ new Set([
10
+ "draft",
11
+ "approved",
12
+ "implemented",
13
+ "deprecated"
14
+ ]);
15
+ const SKIP_DIRS = /* @__PURE__ */ new Set([
16
+ "node_modules",
17
+ ".git",
18
+ "dist",
19
+ ".turbo",
20
+ ".next",
21
+ "coverage"
22
+ ]);
23
+ const FIXED_ANCHORS = [
24
+ {
25
+ pattern: ".agents/spec/",
26
+ kind: "fixed",
27
+ explanation: "the repo-root single-project spec"
28
+ },
29
+ {
30
+ pattern: ".agents/specs/<project>/",
31
+ kind: "fixed",
32
+ explanation: "each repo-root multi-project spec (the folder names the project)"
33
+ },
34
+ {
35
+ pattern: "<project-path>/.agents/spec/",
36
+ kind: "fixed",
37
+ explanation: "a nested project's hoisted spec, at any depth"
38
+ }
39
+ ];
40
+ function parseAnchorsToml(text) {
41
+ const m = /(^|\n)\s*anchors\s*=\s*\[([\s\S]*?)\]/.exec(text);
42
+ if (!m) return [];
43
+ const out = [];
44
+ for (const q of m[2].matchAll(/"([^"]*)"|'([^']*)'/g)) out.push((q[1] ?? q[2]).trim());
45
+ return out.filter((s) => s !== "");
46
+ }
47
+ function readCustomAnchors(root) {
48
+ const file = join(root, ANCHORS_CONFIG);
49
+ if (!existsSync(file)) return [];
50
+ try {
51
+ return parseAnchorsToml(readFileSync(file, "utf8"));
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+ function serializeAnchors(patterns) {
57
+ if (patterns.length === 0) return "anchors = []\n";
58
+ return `anchors = [\n${patterns.map((p) => ` "${p}",`).join("\n")}\n]\n`;
59
+ }
60
+ function writeCustomAnchors(root, patterns) {
61
+ const file = join(root, ANCHORS_CONFIG);
62
+ mkdirSync(dirname(file), { recursive: true });
63
+ writeFileSync(file, serializeAnchors(patterns));
64
+ }
65
+ function isValidPattern(pattern) {
66
+ const p = pattern.trim().replace(/\\/g, "/");
67
+ if (p === "" || p.startsWith("/") || /(^|\/)\.\.(\/|$)/.test(p)) return false;
68
+ const segs = p.replace(/^\/+|\/+$/g, "").split("/");
69
+ if (segs.some((s) => s === "")) return false;
70
+ return segs.every((s) => s === "*" || s === "**" || s === "<project>" || !/[<>]/.test(s));
71
+ }
72
+ function isFixedConvention(pattern) {
73
+ const p = pattern.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
74
+ return p === ".agents/spec" || p === ".agents/specs/<project>" || p === ".agents/specs/*" || p.endsWith("/.agents/spec") || p.endsWith("/.agents/specs/<project>");
75
+ }
76
+ function listAnchors(root) {
77
+ const custom = readCustomAnchors(root).map((pattern) => ({
78
+ pattern,
79
+ kind: "custom",
80
+ explanation: "a declared extra anchor"
81
+ }));
82
+ return [...FIXED_ANCHORS, ...custom];
83
+ }
84
+ function addAnchor(root, pattern) {
85
+ const p = pattern.trim();
86
+ if (isFixedConvention(p)) return {
87
+ ok: false,
88
+ reason: "that is a fixed convention — always scanned, never curated"
89
+ };
90
+ if (!isValidPattern(p)) return {
91
+ ok: false,
92
+ reason: "malformed anchor pattern"
93
+ };
94
+ const current = readCustomAnchors(root);
95
+ if (current.includes(p)) return {
96
+ ok: true,
97
+ changed: false,
98
+ anchors: current,
99
+ message: "already present"
100
+ };
101
+ const next = [...current, p];
102
+ writeCustomAnchors(root, next);
103
+ return {
104
+ ok: true,
105
+ changed: true,
106
+ anchors: next,
107
+ message: `added ${p}`
108
+ };
109
+ }
110
+ function removeAnchor(root, pattern) {
111
+ const p = pattern.trim();
112
+ const current = readCustomAnchors(root);
113
+ if (!current.includes(p)) return {
114
+ ok: true,
115
+ changed: false,
116
+ anchors: current,
117
+ message: "nothing was removed"
118
+ };
119
+ const next = current.filter((a) => a !== p);
120
+ writeCustomAnchors(root, next);
121
+ return {
122
+ ok: true,
123
+ changed: true,
124
+ anchors: next,
125
+ message: `removed ${p}`
126
+ };
127
+ }
128
+ function editAnchor(root, oldPattern, newPattern) {
129
+ const oldP = oldPattern.trim();
130
+ const newP = newPattern.trim();
131
+ if (isFixedConvention(oldP)) return {
132
+ ok: false,
133
+ reason: "cannot edit a fixed convention"
134
+ };
135
+ if (!isValidPattern(newP)) return {
136
+ ok: false,
137
+ reason: "malformed replacement pattern"
138
+ };
139
+ if (isFixedConvention(newP)) return {
140
+ ok: false,
141
+ reason: "replacement is a fixed convention"
142
+ };
143
+ const current = readCustomAnchors(root);
144
+ if (!current.includes(oldP)) return {
145
+ ok: false,
146
+ reason: `no such custom anchor: ${oldP}`
147
+ };
148
+ const next = current.map((a) => a === oldP ? newP : a);
149
+ writeCustomAnchors(root, next);
150
+ return {
151
+ ok: true,
152
+ changed: true,
153
+ anchors: next,
154
+ message: `edited ${oldP} -> ${newP}`
155
+ };
156
+ }
157
+ function inducePatterns(root, samplePath) {
158
+ const rel = samplePath.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
159
+ if (rel === "" || /(^|\/)\.\.(\/|$)/.test(rel)) return {
160
+ ok: false,
161
+ reason: "path is not repo-relative"
162
+ };
163
+ let ok = false;
164
+ try {
165
+ ok = statSync(join(root, rel)).isDirectory();
166
+ } catch {
167
+ ok = false;
168
+ }
169
+ if (!ok) return {
170
+ ok: false,
171
+ reason: `sample path does not resolve to a directory under the repo: ${rel}`
172
+ };
173
+ const generalized = [...rel.split("/").slice(0, -1), "<project>"].join("/");
174
+ return {
175
+ ok: true,
176
+ candidates: generalized === rel ? [rel] : [rel, generalized]
177
+ };
178
+ }
179
+ function collectDescendants(root, startDir) {
180
+ const out = [startDir];
181
+ let entries;
182
+ try {
183
+ entries = readdirSync(join(root, startDir), { withFileTypes: true });
184
+ } catch {
185
+ return out;
186
+ }
187
+ for (const e of entries) {
188
+ if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue;
189
+ out.push(...collectDescendants(root, startDir ? `${startDir}/${e.name}` : e.name));
190
+ }
191
+ return out;
192
+ }
193
+ function previewPattern(root, pattern) {
194
+ const p = pattern.trim();
195
+ if (!isValidPattern(p)) return {
196
+ ok: false,
197
+ reason: "malformed candidate pattern"
198
+ };
199
+ const segs = p.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
200
+ let frontier = [{ dir: "" }];
201
+ for (const seg of segs) {
202
+ const next = [];
203
+ if (seg === "**") {
204
+ for (const node of frontier) for (const dir of collectDescendants(root, node.dir)) next.push({
205
+ dir,
206
+ capturedName: node.capturedName
207
+ });
208
+ frontier = next;
209
+ continue;
210
+ }
211
+ const isGlob = seg === "*" || seg === "<project>";
212
+ for (const node of frontier) if (isGlob) {
213
+ let entries;
214
+ try {
215
+ entries = readdirSync(join(root, node.dir), { withFileTypes: true });
216
+ } catch {
217
+ continue;
218
+ }
219
+ for (const e of entries) {
220
+ if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue;
221
+ next.push({
222
+ dir: node.dir ? `${node.dir}/${e.name}` : e.name,
223
+ capturedName: seg === "<project>" ? e.name : node.capturedName
224
+ });
225
+ }
226
+ } else next.push({
227
+ dir: node.dir ? `${node.dir}/${seg}` : seg,
228
+ capturedName: node.capturedName
229
+ });
230
+ frontier = next;
231
+ }
232
+ const matches = [];
233
+ for (const node of frontier) {
234
+ const rel = node.dir ? `${node.dir}/spec.md` : "spec.md";
235
+ let status;
236
+ try {
237
+ status = /(^|\n)status:\s*["']?([a-z]+)/.exec(readFileSync(join(root, rel), "utf8"))?.[2];
238
+ } catch {
239
+ continue;
240
+ }
241
+ if (!status || !LIFECYCLE_STATUSES.has(status)) continue;
242
+ matches.push({
243
+ path: node.dir,
244
+ name: node.capturedName ?? node.dir.split("/").pop() ?? node.dir
245
+ });
246
+ }
247
+ matches.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
248
+ return {
249
+ ok: true,
250
+ matches
251
+ };
252
+ }
253
+ function flag(argv, name) {
254
+ const i = argv.indexOf(name);
255
+ return i === -1 ? void 0 : argv[i + 1];
256
+ }
257
+ function main(argv) {
258
+ const root = flag(argv, "--root") ?? ".";
259
+ const w = (s) => process.stdout.write(`${s}\n`);
260
+ if (argv.includes("--list")) {
261
+ w("Spec anchors discovery scans (fixed conventions are always scanned):");
262
+ for (const a of listAnchors(root)) w(` [${a.kind}] ${a.pattern} — ${a.explanation}`);
263
+ return 0;
264
+ }
265
+ if (argv.includes("--add")) {
266
+ const r = addAnchor(root, flag(argv, "--add") ?? "");
267
+ w(r.ok ? r.message : `refused: ${r.reason}`);
268
+ return r.ok ? 0 : 1;
269
+ }
270
+ if (argv.includes("--remove")) {
271
+ const r = removeAnchor(root, flag(argv, "--remove") ?? "");
272
+ w(r.ok ? r.message : `refused: ${r.reason}`);
273
+ return r.ok ? 0 : 1;
274
+ }
275
+ if (argv.includes("--edit")) {
276
+ const i = argv.indexOf("--edit");
277
+ const r = editAnchor(root, argv[i + 1] ?? "", argv[i + 2] ?? "");
278
+ w(r.ok ? r.message : `refused: ${r.reason}`);
279
+ return r.ok ? 0 : 1;
280
+ }
281
+ if (argv.includes("--induce")) {
282
+ const r = inducePatterns(root, flag(argv, "--induce") ?? "");
283
+ if (!r.ok) {
284
+ w(`unusable: ${r.reason}`);
285
+ return 1;
286
+ }
287
+ w("Candidate anchor patterns:");
288
+ for (const c of r.candidates) w(` ${c}`);
289
+ return 0;
290
+ }
291
+ if (argv.includes("--preview")) {
292
+ const r = previewPattern(root, flag(argv, "--preview") ?? "");
293
+ if (!r.ok) {
294
+ w(`invalid: ${r.reason}`);
295
+ return 1;
296
+ }
297
+ if (r.matches.length === 0) {
298
+ w("This pattern matches no project.");
299
+ return 0;
300
+ }
301
+ w("This pattern would discover:");
302
+ for (const m of r.matches) w(` ${m.path} (${m.name})`);
303
+ return 0;
304
+ }
305
+ w("usage: manage-spec-anchors --list | --add <p> | --remove <p> | --edit <old> <new> | --induce <path> | --preview <p>");
306
+ return 0;
307
+ }
308
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
309
+ //#endregion
310
+ export { FIXED_ANCHORS, addAnchor, editAnchor, inducePatterns, isFixedConvention, isValidPattern, listAnchors, main, parseAnchorsToml, previewPattern, readCustomAnchors, removeAnchor, serializeAnchors };
@@ -8,6 +8,6 @@ cyberplace repository, not shipped in this package) carries the authoritative be
8
8
  and the frozen 36-scenario contract.
9
9
 
10
10
  - **Skill contract:** [`SKILL.md`](./SKILL.md)
11
- - **Script:** [`scripts/mission-graph.mts`](./scripts/mission-graph.mts)
12
- - **Tests:** [`scripts/mission-graph.test.mts`](./scripts/mission-graph.test.mts) (`node:test`) —
11
+ - **Script:** [`scripts/mission-graph.mjs`](./scripts/mission-graph.mjs)
12
+ - **Tests:** [`src/mission-graph/mission-graph.test.mts`](../../src/mission-graph/mission-graph.test.mts) (`node:test`) —
13
13
  one test per frozen scenario, titled `scenario: <verbatim frozen scenario name>`.
@@ -23,7 +23,7 @@ plus a zero-dependency **fold** into two read-only views and one write-time guar
23
23
  ⊆ the declared set), what's its release floor (the closure alone — support members don't gate
24
24
  it), and its completed/total progress.
25
25
 
26
- It carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention). Pure
26
+ It carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11). Pure
27
27
  derivations (`fold`, `ready`, `cycles`, `checkOperation`, `proposeEdge`) take and return plain data
28
28
  only — no fs access — kept apart from a thin store-IO seam with **two backends**: an in-tree JSONL
29
29
  file and the branch-independent orphan ref `refs/sdd/mission-graph`, resolved at run time. The swap