cyber-sdd 0.6.1 → 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 (136) 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/agents/sdd-spec-judge.md +3 -3
  5. package/agents/sdd-warden.md +8 -0
  6. package/package.json +11 -13
  7. package/plugin.json +55 -0
  8. package/skills/align-spec/README.md +3 -3
  9. package/skills/align-spec/SKILL.md +2 -2
  10. package/skills/align-spec/scripts/align-spec.mjs +6755 -0
  11. package/skills/architect-impl-governance/SKILL.md +3 -1
  12. package/skills/architect-spec-governance/SKILL.md +5 -1
  13. package/skills/blast-estimate/README.md +4 -4
  14. package/skills/blast-estimate/SKILL.md +1 -1
  15. package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -0
  16. package/skills/check-field-mandates/SKILL.md +1 -1
  17. package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -0
  18. package/skills/check-partition-quality/SKILL.md +1 -1
  19. package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -0
  20. package/skills/check-plan-safety/README.md +3 -3
  21. package/skills/check-plan-safety/SKILL.md +3 -4
  22. package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -0
  23. package/skills/check-plugin-manifests/SKILL.md +1 -1
  24. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -0
  25. package/skills/check-project-specs/README.md +3 -3
  26. package/skills/check-project-specs/SKILL.md +3 -3
  27. package/{dist/check-project-specs/scripts/check-project-specs.js → skills/check-project-specs/scripts/check-project-specs.mjs} +300 -18
  28. package/skills/check-retired-terms/README.md +2 -2
  29. package/skills/check-retired-terms/SKILL.md +4 -5
  30. package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -0
  31. package/skills/check-scenario-overlap/README.md +2 -2
  32. package/skills/check-scenario-overlap/SKILL.md +2 -3
  33. package/{dist/check-scenario-overlap/scripts/check-scenario-overlap.js → skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs} +3 -1
  34. package/skills/check-spec-references/README.md +1 -1
  35. package/skills/check-spec-references/SKILL.md +3 -3
  36. package/{dist/check-spec-references/scripts/check-spec-references.js → skills/check-spec-references/scripts/check-spec-references.mjs} +3 -1
  37. package/skills/check-spec-structure/README.md +2 -2
  38. package/skills/check-spec-structure/SKILL.md +2 -3
  39. package/{dist/check-spec-structure/scripts/check-spec-structure.js → skills/check-spec-structure/scripts/check-spec-structure.mjs} +3 -1
  40. package/skills/collision-ladder/README.md +2 -2
  41. package/skills/collision-ladder/SKILL.md +2 -2
  42. package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -0
  43. package/skills/combat-log-governance/SKILL.md +1 -1
  44. package/skills/concept-index/README.md +2 -2
  45. package/skills/concept-index/SKILL.md +2 -2
  46. package/{dist/concept-index/scripts/concept-index.js → skills/concept-index/scripts/concept-index.mjs} +3 -1
  47. package/skills/discover-plans/README.md +5 -5
  48. package/skills/discover-plans/SKILL.md +2 -2
  49. package/skills/discover-plans/scripts/discover-plans.mjs +137 -0
  50. package/skills/discover-specs/README.md +4 -4
  51. package/skills/discover-specs/SKILL.md +2 -2
  52. package/{dist/discover-specs/scripts/discover-specs.js → skills/discover-specs/scripts/discover-specs.mjs} +4 -1
  53. package/skills/doctrine-loop/SKILL.md +2 -2
  54. package/skills/formation-loop/SKILL.md +10 -0
  55. package/skills/gate-validation-governance/SKILL.md +2 -2
  56. package/skills/init/README.md +5 -5
  57. package/skills/init/SKILL.md +3 -3
  58. package/skills/init/scripts/wire-statusline.mjs +168 -0
  59. package/skills/manage-ignore/README.md +8 -8
  60. package/skills/manage-ignore/SKILL.md +2 -3
  61. package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -0
  62. package/skills/manage-scenario-bridge/README.md +6 -6
  63. package/skills/manage-scenario-bridge/SKILL.md +2 -2
  64. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +239 -0
  65. package/skills/manage-spec-anchors/README.md +7 -7
  66. package/skills/manage-spec-anchors/SKILL.md +2 -2
  67. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -0
  68. package/skills/mission-graph/README.md +2 -2
  69. package/skills/mission-graph/SKILL.md +10 -10
  70. package/skills/mission-graph/scripts/mission-graph.mjs +1040 -0
  71. package/skills/pause-mission/SKILL.md +2 -2
  72. package/skills/place-node/README.md +1 -1
  73. package/skills/place-node/SKILL.md +3 -3
  74. package/skills/place-node/scripts/place-node.mjs +117 -0
  75. package/skills/plan-retirement/README.md +2 -2
  76. package/skills/plan-retirement/SKILL.md +1 -1
  77. package/skills/plan-retirement/scripts/retire-plans.mjs +123 -0
  78. package/skills/resolve-governances/README.md +4 -4
  79. package/skills/resolve-governances/SKILL.md +2 -2
  80. package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -0
  81. package/skills/resolve-tracking/SKILL.md +2 -2
  82. package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -0
  83. package/skills/scaffold-project-spec/SKILL.md +3 -3
  84. package/skills/sdd/SKILL.md +1 -1
  85. package/skills/spec-gate/README.md +12 -9
  86. package/skills/spec-gate/SKILL.md +6 -6
  87. package/{dist/spec-gate/scripts/check-spec-state.js → skills/spec-gate/scripts/check-spec-state.mjs} +3 -1
  88. package/{dist/spec-gate/vendor/gherkin-cli.js → skills/spec-gate/scripts/check-suite.mjs} +319 -188
  89. package/skills/spec-gate/{vendor/gherkin-cli.mjs → scripts/classify-edit-class.mjs} +243 -209
  90. package/skills/spec-producer-governance/SKILL.md +4 -4
  91. package/skills/spec-structure-governance/README.md +4 -2
  92. package/skills/spec-structure-governance/SKILL.md +42 -7
  93. package/skills/start-mission/SKILL.md +2 -2
  94. package/skills/suite-format-governance/README.md +1 -1
  95. package/skills/suite-format-governance/SKILL.md +1 -1
  96. package/skills/touch-set-correction/README.md +5 -5
  97. package/skills/touch-set-correction/SKILL.md +2 -2
  98. package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -0
  99. package/skills/verify-scenarios/README.md +9 -9
  100. package/skills/verify-scenarios/SKILL.md +2 -2
  101. package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -0
  102. package/.plugin/pins.json +0 -3
  103. package/.plugin/plugin.json +0 -19
  104. package/dist/align-spec/scripts/align-spec.js +0 -102
  105. package/dist/spec-gate/scripts/check-suite.js +0 -317
  106. package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
  107. package/skills/align-spec/scripts/align-spec.mts +0 -188
  108. package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
  109. package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
  110. package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
  111. package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
  112. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
  113. package/skills/check-project-specs/scripts/check-project-specs.mts +0 -369
  114. package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
  115. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
  116. package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
  117. package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
  118. package/skills/collision-ladder/scripts/collision-ladder.mts +0 -660
  119. package/skills/concept-index/scripts/concept-index.mts +0 -246
  120. package/skills/discover-plans/scripts/discover-plans.mts +0 -215
  121. package/skills/discover-specs/scripts/discover-specs.mts +0 -420
  122. package/skills/init/scripts/wire-statusline.mts +0 -279
  123. package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
  124. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
  125. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
  126. package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
  127. package/skills/place-node/scripts/place-node.mts +0 -158
  128. package/skills/plan-retirement/scripts/retire-plans.mts +0 -199
  129. package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
  130. package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
  131. package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
  132. package/skills/spec-gate/scripts/check-suite.mts +0 -538
  133. package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
  134. package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
  135. package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -420
  136. 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
@@ -5,8 +5,8 @@ every node's `concept:` frontmatter and renders the **by-concept view** into the
5
5
  re-unifying a cross-cutting concern the capability folder tree scatters.
6
6
 
7
7
  ```bash
8
- node scripts/concept-index.mts --spec-dir <spec> --write # refresh the block
9
- node scripts/concept-index.mts --spec-dir <spec> --check # no-drift guard
8
+ node scripts/concept-index.mjs --spec-dir <spec> --write # refresh the block
9
+ node scripts/concept-index.mjs --spec-dir <spec> --check # no-drift guard
10
10
  ```
11
11
 
12
12
  Pure derivation, frontmatter only; the write touches only the delimited generated block. See
@@ -12,12 +12,12 @@ 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
 
19
19
  ```bash
20
- node "<skill>/scripts/concept-index.mts" --spec-dir <spec> [--write | --check]
20
+ node "<skill>/scripts/concept-index.mjs" --spec-dir <spec> [--write | --check]
21
21
  ```
22
22
 
23
23
  - default (no mode) — print the rendered "By concept" section to stdout (dry run).
@@ -1,8 +1,10 @@
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":{}};
2
4
  import { existsSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
3
5
  import { join, relative } from "node:path";
4
6
  import { pathToFileURL } from "node:url";
5
- //#region skills/concept-index/scripts/concept-index.mts
7
+ //#region src/concept-index/concept-index.mts
6
8
  const BEGIN_MARKER = "<!-- BEGIN generated: by-concept (project-spec/concept-index) -->";
7
9
  const END_MARKER = "<!-- END generated: by-concept -->";
8
10
  const ANCHOR_HEADING = "## Invariants";
@@ -1,16 +1,16 @@
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
- node scripts/discover-plans.mts --root . --format toon
15
- node scripts/discover-plans.mts --root . --status approved # the dispatch queue: approved briefs only
14
+ node scripts/discover-plans.mjs --root . --format toon
15
+ node scripts/discover-plans.mjs --root . --status approved # the dispatch queue: approved briefs only
16
16
  ```
@@ -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
 
@@ -42,7 +42,7 @@ the flag; it never interprets or writes it.
42
42
  ## Run the scan
43
43
 
44
44
  ```bash
45
- node "<skill>/scripts/discover-plans.mts" [--root .] [--format toon|json] [--status <value>]
45
+ node "<skill>/scripts/discover-plans.mjs" [--root .] [--format toon|json] [--status <value>]
46
46
  ```
47
47
 
48
48
  - Default `--root` is the current directory; default `--format` is **TOON** (the token-efficient
@@ -0,0 +1,137 @@
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,15 +1,15 @@
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
- node scripts/discover-specs.mts --root . --format toon
14
+ node scripts/discover-specs.mjs --root . --format toon
15
15
  ```
@@ -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
 
@@ -37,7 +37,7 @@ A `spec.md` is a spec only when **both** hold (ADR-0017, narrowed; extra anchors
37
37
  ## Run the scan
38
38
 
39
39
  ```bash
40
- node "<skill>/scripts/discover-specs.mts" [--root .] [--format toon|json] [--resolve <name>]
40
+ node "<skill>/scripts/discover-specs.mjs" [--root .] [--format toon|json] [--resolve <name>]
41
41
  ```
42
42
 
43
43
  - Default `--root` is the current directory; default `--format` is **TOON** (the token-efficient
@@ -1,7 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Compiled by `universal-plugin plugin build` from src/discover-specs/discover-specs.mts. Edit the source, not this file.
3
+ globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.1","entry":"skills/discover-specs/scripts/discover-specs.mjs","commands":{}};
1
4
  import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
2
5
  import { join } from "node:path";
3
6
  import { pathToFileURL } from "node:url";
4
- //#region skills/discover-specs/scripts/discover-specs.mts
7
+ //#region src/discover-specs/discover-specs.mts
5
8
  const LIFECYCLE_STATUSES = /* @__PURE__ */ new Set([
6
9
  "draft",
7
10
  "approved",
@@ -58,11 +58,11 @@ recorded as distilled).
58
58
  **Determine distilled-ness by a structured JSONL parse, never a substring or regex text match.**
59
59
  The ledger shards are JSONL (one JSON object per line). **Reuse the existing parse contract** rather
60
60
  than hand-rolling one: run the exported **`distilledCrRefs`** engine from
61
- `../plan-retirement/scripts/retire-plans.mts` over the project `ledger/` directory and membership-test
61
+ `../plan-retirement/scripts/retire-plans.mjs` (its `--distilled` flag) over the project `ledger/` directory and membership-test
62
62
  the mission's `<cr-ref>` against the set it returns, e.g.
63
63
 
64
64
  ```
65
- node -e 'import("../plan-retirement/scripts/retire-plans.mts").then(m => console.log([...m.distilledCrRefs(process.argv[1])].join("\n")))' <ledger-dir>
65
+ node "<plan-retirement skill>/scripts/retire-plans.mjs" --distilled <ledger-dir>
66
66
  ```
67
67
 
68
68
  `distilledCrRefs` parses **each line** with `JSON.parse` (keying only on `kind === "strategy"` with a
@@ -58,6 +58,16 @@ misplaced only when it **neither** follows the declared strategy **nor** matches
58
58
  — judging on the strategy alone raises false findings against placements a human already adjudicated. A declared layout settles *where a node goes*; it never licenses a capability to
59
59
  scatter across nodes. (Strategy is policy — read it; homes are data — derive them.)
60
60
 
61
+ **Depth on capability nodes; `design/` by its own shape** (`sdd:spec-structure-governance`). The
62
+ two-level cap covers capability nodes: one three levels deep is a depth finding naming it, its
63
+ sub-grouping owed to a `concept:` tag. `design/` is judged by its own shape instead — current design
64
+ at the top, one file per perspective, the `decisions/` ADR log, and at most one version folder
65
+ holding the next design. The placement map names that version folder and its cut trigger; the named
66
+ folder and its files raise no finding, any other `design/` sub-folder besides `decisions/` is a
67
+ perspective-folder finding, a named version with no cut trigger is a finding, and so is a named
68
+ version whose folder does not exist (a stale record the cut left behind). Tell the two
69
+ apart by the map, never by the folder's contents.
70
+
61
71
  ## The intra-spec structural acts
62
72
 
63
73
  It acts on each spec's **structure**, not its content — one project is **one spec**, so structural
@@ -13,8 +13,8 @@ conductor's autonomy bar (baked into `start-mission`), a hard floor + a three-di
13
13
 
14
14
  ## Legal-state tuples
15
15
 
16
- The mechanical authority is `spec-gate/scripts/check-spec-state.mts` — run it
17
- (`node <skill>/scripts/check-spec-state.mts`) to enforce; if `node` is unavailable, apply the same
16
+ The mechanical authority is `spec-gate/scripts/check-spec-state.mjs` — run it
17
+ (`node <skill>/scripts/check-spec-state.mjs`) to enforce; if `node` is unavailable, apply the same
18
18
  rules by reading frontmatter. The `(status, markers, .feature, approval)` tuple of the root
19
19
  `spec.md` — plus the durable gate lines globbed from its sibling `ledger/` shards (a legacy `ledger.jsonl` still counts) — is **illegal** when:
20
20
 
@@ -7,13 +7,13 @@ mission runs. A user-invocable setup skill (sibling to `start-mission` / `manage
7
7
  and invokes no gate.
8
8
 
9
9
  - **Skill contract:** [`SKILL.md`](./SKILL.md)
10
- - **Script:** [`scripts/wire-statusline.mts`](./scripts/wire-statusline.mts)
11
- - **Tests:** [`scripts/wire-statusline.test.mts`](./scripts/wire-statusline.test.mts) (`node:test`)
10
+ - **Script:** [`scripts/wire-statusline.mjs`](./scripts/wire-statusline.mjs)
11
+ - **Tests:** [`src/init/wire-statusline.test.mts`](../../src/init/wire-statusline.test.mts) (`node:test`)
12
12
 
13
13
  ```bash
14
- node scripts/wire-statusline.mts --root . --detect
15
- node scripts/wire-statusline.mts --root . --wire --mode own-line
16
- node scripts/wire-statusline.mts --root . --wire --mode same-line # --no-global-base to shadow deliberately
14
+ node scripts/wire-statusline.mjs --root . --detect
15
+ node scripts/wire-statusline.mjs --root . --wire --mode own-line
16
+ node scripts/wire-statusline.mjs --root . --wire --mode same-line # --no-global-base to shadow deliberately
17
17
  ```
18
18
 
19
19
  The engine writes only project `.claude/settings.json` (never the global settings file) and, in a
@@ -29,7 +29,7 @@ On enable, ask whether the mission status should render:
29
29
  Before wiring, run the read-only detection — Claude Code's project `statusLine` **replaces** the global one (no merge), so a fresh project wiring would silently shadow a status line living in `~/.claude/settings.json`:
30
30
 
31
31
  ```bash
32
- node "<skill>/scripts/wire-statusline.mts" --root . --detect
32
+ node "<skill>/scripts/wire-statusline.mjs" --root . --detect
33
33
  ```
34
34
 
35
35
  It reports the project statusLine (absent | wired | foreign), the global statusLine (present | absent), and the shadow risk. On a shadow risk (project absent, global present), tell the user wiring will shadow their global status line and ask:
@@ -44,8 +44,8 @@ No shadow risk → proceed straight to wiring.
44
44
  Run the engine to compose the reader into project settings and (in a git repo) gitignore the status file:
45
45
 
46
46
  ```bash
47
- node "<skill>/scripts/wire-statusline.mts" --root . --wire --mode own-line
48
- node "<skill>/scripts/wire-statusline.mts" --root . --wire --mode same-line # add --no-global-base to shadow deliberately
47
+ node "<skill>/scripts/wire-statusline.mjs" --root . --wire --mode own-line
48
+ node "<skill>/scripts/wire-statusline.mjs" --root . --wire --mode same-line # add --no-global-base to shadow deliberately
49
49
  ```
50
50
 
51
51
  The engine:
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ // Compiled by `universal-plugin plugin build` from src/init/wire-statusline.mts. Edit the source, not this file.
3
+ globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.1","entry":"skills/init/scripts/wire-statusline.mjs","commands":{}};
4
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+ //#region src/init/wire-statusline.mts
9
+ const STATUS_FILE = ".agents/sdd/statusline";
10
+ const SETTINGS_FILE = ".claude/settings.json";
11
+ const GITIGNORE_FILE = ".gitignore";
12
+ const GLOBAL_SETTINGS_FILE = join(homedir(), ".claude", "settings.json");
13
+ const MARKER_BEGIN = "# sdd-statusline:begin";
14
+ const MARKER_END = "# sdd-statusline:end";
15
+ const MODE_PREFIX = "# sdd-statusline:mode:";
16
+ const ORIG_OPEN = "__sdd_orig() {";
17
+ const ORIG_CLOSE = "}";
18
+ const NO_BASE = ":";
19
+ function buildCommand(base, mode) {
20
+ const origBody = base && base.trim() !== "" ? base : NO_BASE;
21
+ const render = mode === "own-line" ? `if [ -s "$__sdd_file" ]; then\n __sdd_val="$(cat "$__sdd_file")"\n printf '%s\\n%s\\n' "$__sdd_base" "$__sdd_val"\nelse\n printf '%s\\n' "$__sdd_base"\nfi` : `if [ -s "$__sdd_file" ]; then\n __sdd_val="$(cat "$__sdd_file")"\n printf '%s | %s\\n' "$__sdd_base" "$__sdd_val"\nelse\n printf '%s\\n' "$__sdd_base"\nfi`;
22
+ return [
23
+ MARKER_BEGIN,
24
+ `${MODE_PREFIX}${mode}`,
25
+ ORIG_OPEN,
26
+ origBody,
27
+ ORIG_CLOSE,
28
+ "__sdd_base=\"$(__sdd_orig 2>/dev/null)\"",
29
+ `__sdd_file="${STATUS_FILE}"`,
30
+ render,
31
+ MARKER_END
32
+ ].join("\n");
33
+ }
34
+ function parseCommand(command) {
35
+ if (!command?.includes(MARKER_BEGIN)) return { wired: false };
36
+ const modeMatch = new RegExp(`${MODE_PREFIX}(own-line|same-line)`).exec(command);
37
+ const origMatch = /__sdd_orig\(\) \{\n([\s\S]*?)\n\}/.exec(command);
38
+ const mode = modeMatch?.[1] ?? "own-line";
39
+ const origBody = origMatch?.[1] ?? NO_BASE;
40
+ return {
41
+ wired: true,
42
+ base: origBody === NO_BASE ? void 0 : origBody,
43
+ mode
44
+ };
45
+ }
46
+ function baseToWrap(existingCommand) {
47
+ const parsed = parseCommand(existingCommand);
48
+ return parsed.wired ? parsed.base : existingCommand;
49
+ }
50
+ function readSettingsFile(file) {
51
+ if (!existsSync(file)) return {};
52
+ try {
53
+ return JSON.parse(readFileSync(file, "utf8"));
54
+ } catch {
55
+ return {};
56
+ }
57
+ }
58
+ function readSettings(root) {
59
+ return readSettingsFile(join(root, SETTINGS_FILE));
60
+ }
61
+ function readStatusLineCommand(file) {
62
+ const command = readSettingsFile(file).statusLine?.command;
63
+ if (typeof command !== "string" || command.trim() === "") return void 0;
64
+ return command;
65
+ }
66
+ function writeSettings(root, settings) {
67
+ const file = join(root, SETTINGS_FILE);
68
+ mkdirSync(dirname(file), { recursive: true });
69
+ writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`);
70
+ }
71
+ function composeStatusLine(root, mode, globalCommand) {
72
+ const settings = readSettings(root);
73
+ const existing = settings.statusLine?.command;
74
+ const hasProjectStatusLine = typeof existing === "string" && existing.trim() !== "";
75
+ const globalBaseComposed = !hasProjectStatusLine && globalCommand !== void 0;
76
+ const command = buildCommand(hasProjectStatusLine ? baseToWrap(existing) : globalCommand, mode);
77
+ if (existing === command) return {
78
+ changed: false,
79
+ command,
80
+ globalBaseComposed: false
81
+ };
82
+ settings.statusLine = {
83
+ ...settings.statusLine,
84
+ type: "command",
85
+ command
86
+ };
87
+ writeSettings(root, settings);
88
+ return {
89
+ changed: true,
90
+ command,
91
+ globalBaseComposed
92
+ };
93
+ }
94
+ function isGitRepo(root) {
95
+ return existsSync(join(root, ".git"));
96
+ }
97
+ function splitLines(text) {
98
+ const lines = text.split("\n");
99
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
100
+ return lines;
101
+ }
102
+ function addGitignoreEntry(root) {
103
+ const file = join(root, GITIGNORE_FILE);
104
+ const lines = existsSync(file) ? splitLines(readFileSync(file, "utf8")) : [];
105
+ if (lines.some((l) => l.trim() === ".agents/sdd/statusline")) return { changed: false };
106
+ const next = [...lines, STATUS_FILE];
107
+ writeFileSync(file, `${next.join("\n")}\n`);
108
+ return { changed: true };
109
+ }
110
+ function detectStatusLines(root, globalSettingsFile) {
111
+ const projectCommand = readStatusLineCommand(join(root, SETTINGS_FILE));
112
+ const project = projectCommand === void 0 ? "absent" : parseCommand(projectCommand).wired ? "wired" : "foreign";
113
+ const global = readStatusLineCommand(globalSettingsFile ?? GLOBAL_SETTINGS_FILE) !== void 0 ? "present" : "absent";
114
+ return {
115
+ project,
116
+ global,
117
+ shadow: project === "absent" && global === "present"
118
+ };
119
+ }
120
+ function wireStatusline(root, mode, opts) {
121
+ return {
122
+ settings: composeStatusLine(root, mode, opts?.globalBase ?? true ? readStatusLineCommand(opts?.globalSettingsFile ?? GLOBAL_SETTINGS_FILE) : void 0),
123
+ gitignore: isGitRepo(root) ? {
124
+ ...addGitignoreEntry(root),
125
+ skipped: false
126
+ } : {
127
+ changed: false,
128
+ skipped: true
129
+ }
130
+ };
131
+ }
132
+ function flag(argv, name) {
133
+ const i = argv.indexOf(name);
134
+ return i === -1 ? void 0 : argv[i + 1];
135
+ }
136
+ function main(argv) {
137
+ const root = flag(argv, "--root") ?? ".";
138
+ const globalSettingsFile = flag(argv, "--global-settings");
139
+ const w = (s) => process.stdout.write(`${s}\n`);
140
+ if (argv.includes("--detect")) {
141
+ const result = detectStatusLines(root, globalSettingsFile);
142
+ w(`project statusLine: ${result.project}`);
143
+ w(`global statusLine: ${result.global}`);
144
+ w(result.shadow ? "shadow risk: yes — wiring would shadow the global statusline; compose it as the base (default) or pass --no-global-base to shadow deliberately" : "shadow risk: no");
145
+ return 0;
146
+ }
147
+ if (argv.includes("--wire")) {
148
+ const mode = flag(argv, "--mode");
149
+ if (mode !== "own-line" && mode !== "same-line") {
150
+ w("refused: --mode must be own-line or same-line");
151
+ return 1;
152
+ }
153
+ const result = wireStatusline(root, mode, {
154
+ globalSettingsFile,
155
+ globalBase: !argv.includes("--no-global-base")
156
+ });
157
+ w(result.settings.changed ? `wired statusLine (${mode})` : "statusLine already wired (unchanged)");
158
+ if (result.settings.globalBaseComposed) w("composed the global statusLine command as the wrapped base");
159
+ if (result.gitignore.skipped) w("not a git repo — gitignore skipped");
160
+ else w(result.gitignore.changed ? `added ${STATUS_FILE} to .gitignore` : ".gitignore already up to date");
161
+ return 0;
162
+ }
163
+ w("usage: wire-statusline --root <dir> --wire --mode own-line|same-line [--global-settings <file>] [--no-global-base]\n wire-statusline --root <dir> --detect [--global-settings <file>]");
164
+ return 0;
165
+ }
166
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
167
+ //#endregion
168
+ export { GITIGNORE_FILE, GLOBAL_SETTINGS_FILE, SETTINGS_FILE, STATUS_FILE, addGitignoreEntry, buildCommand, composeStatusLine, detectStatusLines, isGitRepo, main, parseCommand, readStatusLineCommand, wireStatusline };
@@ -2,18 +2,18 @@
2
2
 
3
3
  The concrete engine for `.agents/sdd/.sddignore` — the optional gitignore-syntax file
4
4
  `resolve-tracking` reads to decide whether an artifact is tracked or ignored. A non-user-invocable
5
- skill, loaded in-session by the `manage` gateway (Housekeeping), carrying a self-contained `.mts`
5
+ skill, loaded in-session by the `manage` gateway (Housekeeping), carrying a self-contained `.mjs`
6
6
  script that lists / adds / removes / induces / previews the ignore rules so users never hand-edit the
7
7
  file. Order is meaningful (last-match-wins), so rules are never re-sorted.
8
8
 
9
9
  - **Skill contract:** [`SKILL.md`](./SKILL.md)
10
- - **Script:** [`scripts/manage-ignore.mts`](./scripts/manage-ignore.mts)
11
- - **Tests:** [`scripts/manage-ignore.test.mts`](./scripts/manage-ignore.test.mts) (`node:test`)
10
+ - **Script:** [`scripts/manage-ignore.mjs`](./scripts/manage-ignore.mjs)
11
+ - **Tests:** [`src/manage-ignore/manage-ignore.test.mts`](../../src/manage-ignore/manage-ignore.test.mts) (`node:test`)
12
12
 
13
13
  ```bash
14
- node scripts/manage-ignore.mts --list
15
- node scripts/manage-ignore.mts --induce build/output/app.log
16
- node scripts/manage-ignore.mts --preview '*.log'
17
- node scripts/manage-ignore.mts --preview '!keep.log'
18
- node scripts/manage-ignore.mts --add '*.log'
14
+ node scripts/manage-ignore.mjs --list
15
+ node scripts/manage-ignore.mjs --induce build/output/app.log
16
+ node scripts/manage-ignore.mjs --preview '*.log'
17
+ node scripts/manage-ignore.mjs --preview '!keep.log'
18
+ node scripts/manage-ignore.mjs --add '*.log'
19
19
  ```
@@ -12,8 +12,7 @@ The concrete engine for `.agents/sdd/.sddignore` — the optional gitignore-synt
12
12
  `resolve-tracking` consults to decide whether an artifact is **tracked** or **ignored**. It is the
13
13
  **write** side of the ignore file (`resolve-tracking` is the read side). It exists so a user curates
14
14
  the ignore rules through a clean interface instead of hand-editing the file. Loaded **in-session** by
15
- the `manage` gateway (Housekeeping group); it carries a self-contained `.mts` script (the repo's
16
- node-≥23.6 / no-deps convention).
15
+ the `manage` gateway (Housekeeping group); it carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11).
17
16
 
18
17
  ## The file it curates
19
18
 
@@ -25,7 +24,7 @@ engine therefore **never re-sorts**: `--add` appends, `--remove` preserves the o
25
24
  ## Run an operation
26
25
 
27
26
  ```bash
28
- node "<skill>/scripts/manage-ignore.mts" [--root .] <operation>
27
+ node "<skill>/scripts/manage-ignore.mjs" [--root .] <operation>
29
28
  ```
30
29
 
31
30
  | Operation | Effect |