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
@@ -242,7 +242,7 @@ plan/log-surfaced candidate against **current code** (a persisted plan or log is
242
242
  closed candidate.
243
243
 
244
244
  ```jsonl
245
- {"seq": 3, "handle": "sdd-scanner", "kind": "strategy", "disposition": "resolved", "recommendation": "no action — coverage-gap mechanism already shipped", "evidence": ["current-code: checkReferencedArtifacts + checkUseCaseCoverage live in spec-gate/scripts/check-spec-state.mts"], "ratified": false}
245
+ {"seq": 3, "handle": "sdd-scanner", "kind": "strategy", "disposition": "resolved", "recommendation": "no action — coverage-gap mechanism already shipped", "evidence": ["current-code: checkReferencedArtifacts + checkUseCaseCoverage live in spec-gate/scripts/check-spec-state.mjs"], "ratified": false}
246
246
  ```
247
247
 
248
248
  `disposition` is set **once at write** and never flipped (the append-only invariant) — the Scanner's
@@ -12,7 +12,7 @@ The concrete engine for the **concept-index** step. It scans
12
12
  one project-spec for every node's `concept:` frontmatter and renders the **by-concept view** —
13
13
  `concept → {its nodes across every folder}` — that re-unifies a cross-cutting concern the capability
14
14
  folder tree scatters (the concept axis: one concern enacted across several capability folders). It
15
- carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention).
15
+ carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11).
16
16
 
17
17
  ## Run it
18
18
 
@@ -1,12 +1,183 @@
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/concept-index/concept-index.mts. Edit the source, not this file.
3
+ globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/concept-index/scripts/concept-index.mjs","commands":{}};
4
+ import { existsSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
5
+ import { join, relative } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ //#region src/concept-index/concept-index.mts
8
+ const BEGIN_MARKER = "<!-- BEGIN generated: by-concept (project-spec/concept-index) -->";
9
+ const END_MARKER = "<!-- END generated: by-concept -->";
10
+ const ANCHOR_HEADING = "## Invariants";
11
+ const SKIP_DIRS = /* @__PURE__ */ new Set([
12
+ "node_modules",
13
+ ".git",
14
+ "dist",
15
+ ".turbo",
16
+ ".next",
17
+ "coverage"
18
+ ]);
19
+ function parseFrontmatter(text) {
20
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text);
21
+ if (!m) return null;
22
+ const fm = {
23
+ concepts: [],
24
+ model: false
25
+ };
26
+ const lines = m[1].split("\n").map((l) => l.replace(/\r$/, ""));
27
+ for (let i = 0; i < lines.length; i++) {
28
+ const line = lines[i];
29
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
30
+ if (line.length - line.trimStart().length !== 0) continue;
31
+ const [key, ...rest] = line.trim().split(":");
32
+ const value = rest.join(":").trim();
33
+ if (key === "spec-type") fm.specType = unquote(value);
34
+ else if (key === "model") fm.model = unquote(value) === "true";
35
+ else if (key === "concept") {
36
+ if (value === "" || value === "|" || value === ">") for (let j = i + 1; j < lines.length; j++) {
37
+ const item = lines[j];
38
+ if (item.trim() === "") continue;
39
+ if (item.length - item.trimStart().length === 0) break;
40
+ const dash = /^\s*-\s+(.*)$/.exec(item);
41
+ if (dash) fm.concepts.push(unquote(dash[1].trim()));
42
+ }
43
+ else fm.concepts.push(...parseScalarOrFlow(value));
44
+ }
45
+ }
46
+ return fm;
47
+ }
48
+ function parseScalarOrFlow(value) {
49
+ const v = value.trim();
50
+ if (v.startsWith("[") && v.endsWith("]")) return v.slice(1, -1).split(",").map((s) => unquote(s.trim())).filter((s) => s.length > 0);
51
+ const single = unquote(v);
52
+ return single.length > 0 ? [single] : [];
53
+ }
54
+ function unquote(v) {
55
+ return v.replace(/^["']|["']$/g, "");
56
+ }
57
+ function facetKind(relPath, specType) {
58
+ const p = relPath.replace(/\\/g, "/");
59
+ if (p.startsWith("design/")) return "rule";
60
+ if (p.startsWith("workflows/")) return "workflow";
61
+ if (specType === "reference") return "reference";
62
+ if (specType === "behavioral") return "behavior";
63
+ return "index";
64
+ }
65
+ function displayPath(relPath) {
66
+ const p = relPath.replace(/\\/g, "/");
67
+ return p.endsWith("/README.md") ? p.slice(0, -9) : p;
68
+ }
69
+ function scanProjectSpec(specDir) {
70
+ const records = [];
71
+ walk(specDir, specDir, records);
72
+ return records;
73
+ }
74
+ function walk(dir, specDir, out) {
75
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
76
+ if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
77
+ const full = join(dir, entry.name);
78
+ if (entry.isDirectory()) walk(full, specDir, out);
79
+ else if (entry.name.endsWith(".md")) {
80
+ const fm = parseFrontmatter(readFileSync(full, "utf8"));
81
+ if (!fm || fm.concepts.length === 0) continue;
82
+ const relPath = relative(specDir, full).replace(/\\/g, "/");
83
+ out.push({
84
+ relPath,
85
+ display: displayPath(relPath),
86
+ concepts: fm.concepts,
87
+ facet: facetKind(relPath, fm.specType)
88
+ });
89
+ }
90
+ }
91
+ }
92
+ function groupByConcept(records) {
93
+ const grouped = /* @__PURE__ */ new Map();
94
+ for (const rec of records) for (const concept of rec.concepts) {
95
+ const list = grouped.get(concept) ?? [];
96
+ list.push(rec);
97
+ grouped.set(concept, list);
98
+ }
99
+ return new Map([...grouped.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, [...v].sort((x, y) => x.display.localeCompare(y.display))]));
100
+ }
101
+ function renderTable(grouped) {
102
+ return [
103
+ "| Concept | Facets |",
104
+ "|---|---|",
105
+ ...[...grouped.entries()].map(([concept, nodes]) => {
106
+ return `| \`${concept}\` | ${nodes.map((n) => `\`${n.display}\` (${n.facet})`).join(" · ")} |`;
107
+ })
108
+ ].join("\n");
109
+ }
110
+ function renderSection(grouped) {
111
+ return [
112
+ BEGIN_MARKER,
113
+ "",
114
+ "## By concept",
115
+ "",
116
+ "> Generated from `concept:` frontmatter by `project-spec/concept-index` — do not edit by hand.",
117
+ "",
118
+ renderTable(grouped),
119
+ "",
120
+ END_MARKER
121
+ ].join("\n");
122
+ }
123
+ function applySection(specText, section) {
124
+ const begin = specText.indexOf(BEGIN_MARKER);
125
+ const end = specText.indexOf(END_MARKER);
126
+ if (begin !== -1 && end !== -1 && end > begin) {
127
+ const before = specText.slice(0, begin);
128
+ const after = specText.slice(end + 34);
129
+ return before + section + after;
130
+ }
131
+ const anchor = specText.indexOf(ANCHOR_HEADING);
132
+ if (anchor !== -1) return specText.slice(0, anchor) + section + "\n\n" + specText.slice(anchor);
133
+ return `${specText.replace(/\s*$/, "")}\n\n${section}\n`;
134
+ }
135
+ function extractSection(specText) {
136
+ const begin = specText.indexOf(BEGIN_MARKER);
137
+ const end = specText.indexOf(END_MARKER);
138
+ if (begin === -1 || end === -1 || end < begin) return null;
139
+ return specText.slice(begin, end + 34);
140
+ }
141
+ function parseArgs(argv) {
142
+ let specDir = ".";
143
+ let mode = "print";
144
+ for (let i = 0; i < argv.length; i++) {
145
+ const a = argv[i];
146
+ if (a === "--spec-dir") specDir = argv[++i] ?? ".";
147
+ else if (a === "--write") mode = "write";
148
+ else if (a === "--check") mode = "check";
149
+ }
150
+ return {
151
+ specDir,
152
+ mode
153
+ };
154
+ }
155
+ function main(argv) {
156
+ const { specDir, mode } = parseArgs(argv);
157
+ const section = renderSection(groupByConcept(scanProjectSpec(specDir)));
158
+ const specPath = join(specDir, "spec.md");
159
+ if (mode === "print") {
160
+ process.stdout.write(`${section}\n`);
161
+ return 0;
162
+ }
163
+ if (!existsSync(specPath)) {
164
+ process.stderr.write(`concept-index: no spec.md at ${specPath}\n`);
165
+ return 2;
166
+ }
167
+ const specText = readFileSync(specPath, "utf8");
168
+ if (mode === "check") {
169
+ if (extractSection(specText) === section) {
170
+ process.stdout.write("concept-index: no drift\n");
171
+ return 0;
172
+ }
173
+ process.stderr.write("concept-index: drift — spec.md by-concept block is stale; run --write\n");
174
+ return 1;
175
+ }
176
+ const next = applySection(specText, section);
177
+ if (next !== specText) writeFileSync(specPath, next);
178
+ process.stdout.write(`concept-index: ${next === specText ? "unchanged" : "updated"} ${specPath}\n`);
179
+ return 0;
180
+ }
181
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
182
+ //#endregion
183
+ export { BEGIN_MARKER, END_MARKER, applySection, extractSection, facetKind, groupByConcept, main, parseFrontmatter, parseScalarOrFlow, renderSection, renderTable, scanProjectSpec };
@@ -1,14 +1,14 @@
1
1
  # discover-plans
2
2
 
3
3
  The concrete engine for SDD **plan discovery**. A non-user-invocable skill carrying a self-contained
4
- `.mts` script that scans `.agents/plans` for `*.plan.md` mission briefs, treats each present brief as
4
+ `.mjs` script that scans `.agents/plans` for `*.plan.md` mission briefs, treats each present brief as
5
5
  an unretired/resumable mission, tallies its todos, reads its top-level `status` dispatch flag
6
6
  (`active` when unset) and its `## NEXT` resume lead, and emits a TOON list. The plan sibling of
7
7
  [`discover-specs`](../discover-specs/README.md).
8
8
 
9
9
  - **Skill contract:** [`SKILL.md`](./SKILL.md)
10
- - **Script:** [`scripts/discover-plans.mts`](./scripts/discover-plans.mts)
11
- - **Tests:** [`scripts/discover-plans.test.mts`](./scripts/discover-plans.test.mts) (`node:test`)
10
+ - **Script:** [`scripts/discover-plans.mjs`](./scripts/discover-plans.mjs)
11
+ - **Tests:** [`src/discover-plans/discover-plans.test.mts`](../../src/discover-plans/discover-plans.test.mts) (`node:test`)
12
12
 
13
13
  ```bash
14
14
  node scripts/discover-plans.mjs --root . --format toon
@@ -12,7 +12,7 @@ The concrete engine for SDD **plan discovery**. It
12
12
  locates the **resumable missions** in a repo — the mission **plan briefs** under `.agents/plans` —
13
13
  and returns each one's CR ref, name, todo tally, and `## NEXT` resume lead, **without reading the
14
14
  rest of the body**, so a consumer (the **gateway**, on entry) can offer resume cheaply. It carries a
15
- self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention). It is the **plan** sibling
15
+ self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11). It is the **plan** sibling
16
16
  of `discover-specs`: that engine finds **specs** by status shape; this one finds **missions** by
17
17
  their briefs.
18
18
 
@@ -1,12 +1,137 @@
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/discover-plans/discover-plans.mts. Edit the source, not this file.
3
+ globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/discover-plans/scripts/discover-plans.mjs","commands":{}};
4
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ //#region src/discover-plans/discover-plans.mts
8
+ const TODO_STATUSES = /* @__PURE__ */ new Set([
9
+ "pending",
10
+ "in_progress",
11
+ "completed"
12
+ ]);
13
+ /** The default mission dispatch status — the meaning of an absent top-level `status`. */
14
+ const DEFAULT_PLAN_STATUS = "active";
15
+ function parsePlanFrontmatter(text) {
16
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text);
17
+ if (!m) return null;
18
+ const fm = {
19
+ name: "",
20
+ status: DEFAULT_PLAN_STATUS,
21
+ total: 0,
22
+ completed: 0,
23
+ inProgress: 0,
24
+ pending: 0
25
+ };
26
+ let inTodos = false;
27
+ for (const raw of m[1].split("\n")) {
28
+ const line = raw.replace(/\r$/, "");
29
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
30
+ const indent = line.length - line.trimStart().length;
31
+ const trimmed = line.trim();
32
+ if (indent === 0) {
33
+ inTodos = false;
34
+ const [key, ...rest] = trimmed.split(":");
35
+ const value = rest.join(":").trim();
36
+ if (key === "name") fm.name = unquote(value);
37
+ else if (key === "status") {
38
+ const v = unquote(value);
39
+ if (v !== "") fm.status = v;
40
+ } else if (key === "todos") inTodos = true;
41
+ continue;
42
+ }
43
+ if (inTodos) {
44
+ const sm = /^(?:-\s+)?status:\s*(.+)$/.exec(trimmed);
45
+ if (sm) {
46
+ const status = unquote(sm[1].trim());
47
+ fm.total++;
48
+ if (status === "completed") fm.completed++;
49
+ else if (status === "in_progress") fm.inProgress++;
50
+ else if (status === "pending") fm.pending++;
51
+ }
52
+ }
53
+ }
54
+ return fm;
55
+ }
56
+ function unquote(v) {
57
+ return v.replace(/^["']|["']$/g, "");
58
+ }
59
+ function nextLead(text) {
60
+ const lines = text.split("\n");
61
+ let i = lines.findIndex((l) => /^##\s+NEXT\b/i.test(l.trim()));
62
+ if (i === -1) return "";
63
+ for (i += 1; i < lines.length; i++) {
64
+ const l = lines[i].replace(/\r$/, "").trim();
65
+ if (l === "") continue;
66
+ if (l.startsWith("#")) return "";
67
+ const cleaned = l.replace(/^[-*]\s+/, "").replace(/\*\*/g, "").trim();
68
+ return cleaned.length > 200 ? `${cleaned.slice(0, 197)}...` : cleaned;
69
+ }
70
+ return "";
71
+ }
72
+ function discoverPlanFiles(root) {
73
+ const dir = join(root, ".agents", "plans");
74
+ if (!existsSync(dir)) return [];
75
+ let entries;
76
+ try {
77
+ entries = readdirSync(dir, { withFileTypes: true });
78
+ } catch {
79
+ return [];
80
+ }
81
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".plan.md")).map((e) => e.name).sort();
82
+ }
83
+ function collectPlans(root) {
84
+ const out = [];
85
+ for (const name of discoverPlanFiles(root)) {
86
+ let text;
87
+ try {
88
+ text = readFileSync(join(root, ".agents", "plans", name), "utf8");
89
+ } catch {
90
+ continue;
91
+ }
92
+ const fm = parsePlanFrontmatter(text);
93
+ if (!fm) continue;
94
+ out.push({
95
+ cr: name.replace(/\.plan\.md$/, ""),
96
+ name: fm.name,
97
+ total: fm.total,
98
+ completed: fm.completed,
99
+ inProgress: fm.inProgress,
100
+ status: fm.status,
101
+ next: nextLead(text)
102
+ });
103
+ }
104
+ return out.sort((a, b) => a.cr < b.cr ? -1 : a.cr > b.cr ? 1 : 0);
105
+ }
106
+ function filterByStatus(plans, status) {
107
+ return plans.filter((p) => p.status === status);
108
+ }
109
+ const COLUMNS = [
110
+ "cr",
111
+ "name",
112
+ "total",
113
+ "completed",
114
+ "inProgress",
115
+ "status",
116
+ "next"
117
+ ];
118
+ function toonField(v) {
119
+ if (v === "" || /[",]/.test(v) || v !== v.trim()) return `"${v.replace(/"/g, "\"\"")}"`;
120
+ return v;
121
+ }
122
+ function toToon(plans) {
123
+ return [`plans[${plans.length}]{${COLUMNS.join(",")}}:`, ...plans.map((p) => ` ${COLUMNS.map((c) => toonField(String(p[c]))).join(",")}`)].join("\n");
124
+ }
125
+ function main(argv) {
126
+ const root = argv.includes("--root") ? argv[argv.indexOf("--root") + 1] ?? "." : ".";
127
+ const format = argv.includes("--format") ? argv[argv.indexOf("--format") + 1] : "toon";
128
+ const statusFilter = argv.includes("--status") ? argv[argv.indexOf("--status") + 1] : void 0;
129
+ let plans = collectPlans(root);
130
+ if (statusFilter !== void 0) plans = filterByStatus(plans, statusFilter);
131
+ const out = format === "json" ? JSON.stringify(plans, null, 2) : toToon(plans);
132
+ process.stdout.write(`${out}\n`);
133
+ return 0;
134
+ }
135
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
136
+ //#endregion
137
+ export { DEFAULT_PLAN_STATUS, TODO_STATUSES, collectPlans, filterByStatus, main, nextLead, parsePlanFrontmatter, toToon };
@@ -1,14 +1,14 @@
1
1
  # discover-specs
2
2
 
3
- The concrete engine for SDD **spec discovery**. A non-user-invocable skill carrying a self-contained `.mts`
3
+ The concrete engine for SDD **spec discovery**. A non-user-invocable skill carrying a self-contained `.mjs`
4
4
  script that scans the three fixed SDD spec locations — plus any opt-in extra anchors declared in
5
5
  `.agents/sdd/spec-anchors.toml` (ADR-0019, curated via `manage-spec-anchors`) — filters candidates by
6
6
  the lifecycle `status` shape, parses each `spec.md`'s frontmatter only, and emits a TOON list of the
7
7
  specs found.
8
8
 
9
9
  - **Skill contract:** [`SKILL.md`](./SKILL.md)
10
- - **Script:** [`scripts/discover-specs.mts`](./scripts/discover-specs.mts)
11
- - **Tests:** [`scripts/discover-specs.test.mts`](./scripts/discover-specs.test.mts) (`node:test`)
10
+ - **Script:** [`scripts/discover-specs.mjs`](./scripts/discover-specs.mjs)
11
+ - **Tests:** [`src/discover-specs/discover-specs.test.mts`](../../src/discover-specs/discover-specs.test.mts) (`node:test`)
12
12
 
13
13
  ```bash
14
14
  node scripts/discover-specs.mjs --root . --format toon
@@ -11,7 +11,7 @@ metadata:
11
11
  The concrete engine for SDD **spec discovery**. It locates the
12
12
  project specs in a repo and returns each one's frontmatter — **without reading any spec body** — so a
13
13
  consumer (the **gateway**, corpus tooling) can route on `status` / `project-path` / `approval`
14
- cheaply. It carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention).
14
+ cheaply. It carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11).
15
15
 
16
16
  ## Recognition — location-bounded and shape-confirmed
17
17