cyber-sdd 0.7.0 → 0.7.1

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 (149) hide show
  1. package/.claude-plugin/plugin.json +1 -2
  2. package/.codex-plugin/plugin.json +1 -2
  3. package/agents/sdd-scanner.md +1 -1
  4. package/package.json +11 -13
  5. package/plugin.json +55 -0
  6. package/skills/align-spec/SKILL.md +1 -1
  7. package/skills/align-spec/scripts/align-spec.mjs +6755 -12
  8. package/skills/blast-estimate/README.md +3 -3
  9. package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -12
  10. package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -12
  11. package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -12
  12. package/skills/check-plan-safety/SKILL.md +2 -3
  13. package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -12
  14. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -12
  15. package/skills/check-project-specs/SKILL.md +1 -1
  16. package/skills/check-project-specs/scripts/check-project-specs.mjs +593 -12
  17. package/skills/check-retired-terms/SKILL.md +2 -3
  18. package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -12
  19. package/skills/check-scenario-overlap/SKILL.md +1 -2
  20. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +177 -12
  21. package/skills/check-spec-references/SKILL.md +2 -2
  22. package/skills/check-spec-references/scripts/check-spec-references.mjs +287 -12
  23. package/skills/check-spec-structure/SKILL.md +1 -2
  24. package/skills/check-spec-structure/scripts/check-spec-structure.mjs +243 -12
  25. package/skills/collision-ladder/README.md +2 -2
  26. package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -12
  27. package/skills/combat-log-governance/SKILL.md +1 -1
  28. package/skills/concept-index/SKILL.md +1 -1
  29. package/skills/concept-index/scripts/concept-index.mjs +183 -12
  30. package/skills/discover-plans/README.md +3 -3
  31. package/skills/discover-plans/SKILL.md +1 -1
  32. package/skills/discover-plans/scripts/discover-plans.mjs +137 -12
  33. package/skills/discover-specs/README.md +3 -3
  34. package/skills/discover-specs/SKILL.md +1 -1
  35. package/skills/discover-specs/scripts/discover-specs.mjs +321 -12
  36. package/skills/doctrine-loop/SKILL.md +1 -1
  37. package/skills/gate-validation-governance/SKILL.md +1 -1
  38. package/skills/init/README.md +2 -2
  39. package/skills/init/scripts/wire-statusline.mjs +168 -12
  40. package/skills/manage-ignore/README.md +3 -3
  41. package/skills/manage-ignore/SKILL.md +1 -2
  42. package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -12
  43. package/skills/manage-scenario-bridge/README.md +3 -3
  44. package/skills/manage-scenario-bridge/SKILL.md +1 -1
  45. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +239 -12
  46. package/skills/manage-spec-anchors/README.md +3 -3
  47. package/skills/manage-spec-anchors/SKILL.md +1 -1
  48. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -12
  49. package/skills/mission-graph/README.md +2 -2
  50. package/skills/mission-graph/SKILL.md +1 -1
  51. package/skills/mission-graph/scripts/mission-graph.mjs +1040 -12
  52. package/skills/pause-mission/SKILL.md +1 -1
  53. package/skills/place-node/SKILL.md +2 -2
  54. package/skills/place-node/scripts/place-node.mjs +117 -12
  55. package/skills/plan-retirement/README.md +2 -2
  56. package/skills/plan-retirement/scripts/retire-plans.mjs +123 -12
  57. package/skills/resolve-governances/README.md +3 -3
  58. package/skills/resolve-governances/SKILL.md +1 -1
  59. package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -12
  60. package/skills/resolve-tracking/SKILL.md +1 -1
  61. package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -12
  62. package/skills/scaffold-project-spec/SKILL.md +1 -1
  63. package/skills/spec-gate/README.md +12 -9
  64. package/skills/spec-gate/SKILL.md +2 -2
  65. package/skills/spec-gate/scripts/check-spec-state.mjs +422 -12
  66. package/skills/spec-gate/scripts/check-suite.mjs +6500 -12
  67. package/skills/spec-gate/scripts/classify-edit-class.mjs +6579 -12
  68. package/skills/spec-producer-governance/SKILL.md +2 -2
  69. package/skills/start-mission/SKILL.md +1 -1
  70. package/skills/suite-format-governance/README.md +1 -1
  71. package/skills/suite-format-governance/SKILL.md +1 -1
  72. package/skills/touch-set-correction/README.md +5 -5
  73. package/skills/touch-set-correction/SKILL.md +1 -1
  74. package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -12
  75. package/skills/verify-scenarios/README.md +6 -6
  76. package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -12
  77. package/.plugin/pins.json +0 -3
  78. package/.plugin/plugin.json +0 -19
  79. package/dist/align-spec/scripts/align-spec.js +0 -102
  80. package/dist/check-project-specs/scripts/check-project-specs.js +0 -312
  81. package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +0 -175
  82. package/dist/check-spec-references/scripts/check-spec-references.js +0 -285
  83. package/dist/check-spec-structure/scripts/check-spec-structure.js +0 -241
  84. package/dist/concept-index/scripts/concept-index.js +0 -181
  85. package/dist/discover-specs/scripts/discover-specs.js +0 -318
  86. package/dist/spec-gate/scripts/check-spec-state.js +0 -420
  87. package/dist/spec-gate/scripts/check-suite.js +0 -317
  88. package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
  89. package/dist/spec-gate/vendor/gherkin-cli.js +0 -6369
  90. package/skills/align-spec/scripts/align-spec.bundle.mjs +0 -6753
  91. package/skills/align-spec/scripts/align-spec.mts +0 -188
  92. package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +0 -593
  93. package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
  94. package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +0 -251
  95. package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
  96. package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +0 -233
  97. package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
  98. package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +0 -107
  99. package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
  100. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +0 -248
  101. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
  102. package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +0 -584
  103. package/skills/check-project-specs/scripts/check-project-specs.mts +0 -370
  104. package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +0 -184
  105. package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
  106. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +0 -175
  107. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
  108. package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +0 -285
  109. package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
  110. package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +0 -241
  111. package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
  112. package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +0 -7041
  113. package/skills/collision-ladder/scripts/collision-ladder.mts +0 -662
  114. package/skills/concept-index/scripts/concept-index.bundle.mjs +0 -181
  115. package/skills/concept-index/scripts/concept-index.mts +0 -246
  116. package/skills/discover-plans/scripts/discover-plans.bundle.mjs +0 -135
  117. package/skills/discover-plans/scripts/discover-plans.mts +0 -215
  118. package/skills/discover-specs/scripts/discover-specs.bundle.mjs +0 -319
  119. package/skills/discover-specs/scripts/discover-specs.mts +0 -420
  120. package/skills/init/scripts/wire-statusline.bundle.mjs +0 -166
  121. package/skills/init/scripts/wire-statusline.mts +0 -279
  122. package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +0 -244
  123. package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
  124. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +0 -237
  125. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
  126. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +0 -308
  127. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
  128. package/skills/mission-graph/scripts/mission-graph.bundle.mjs +0 -1038
  129. package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
  130. package/skills/place-node/scripts/place-node.bundle.mjs +0 -115
  131. package/skills/place-node/scripts/place-node.mts +0 -158
  132. package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +0 -121
  133. package/skills/plan-retirement/scripts/retire-plans.mts +0 -205
  134. package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +0 -350
  135. package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
  136. package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +0 -150
  137. package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
  138. package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +0 -420
  139. package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
  140. package/skills/spec-gate/scripts/check-suite.bundle.mjs +0 -6498
  141. package/skills/spec-gate/scripts/check-suite.mts +0 -538
  142. package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +0 -6577
  143. package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
  144. package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
  145. package/skills/spec-gate/vendor/gherkin-cli.mjs +0 -6545
  146. package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +0 -6655
  147. package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -429
  148. package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +0 -6537
  149. 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.1","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.1","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