cyber-sdd 0.6.0 → 0.7.0
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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.plugin/plugin.json +1 -1
- package/agents/sdd-automaton.md +2 -3
- package/agents/sdd-impl-judge.md +5 -4
- package/agents/sdd-scanner.md +1 -1
- package/agents/sdd-spec-judge.md +3 -3
- package/agents/sdd-warden.md +8 -0
- package/dist/check-project-specs/scripts/check-project-specs.js +1 -0
- package/package.json +2 -2
- package/skills/align-spec/README.md +3 -3
- package/skills/align-spec/SKILL.md +1 -1
- package/skills/align-spec/scripts/align-spec.bundle.mjs +6753 -0
- package/skills/align-spec/scripts/align-spec.mjs +12 -0
- package/skills/architect-impl-governance/SKILL.md +3 -1
- package/skills/architect-spec-governance/SKILL.md +5 -1
- package/skills/blast-estimate/README.md +1 -1
- package/skills/blast-estimate/SKILL.md +1 -1
- package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +593 -0
- package/skills/blast-estimate/scripts/blast-estimate.mjs +12 -0
- package/skills/check-field-mandates/SKILL.md +1 -1
- package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +251 -0
- package/skills/check-field-mandates/scripts/check-field-mandates.mjs +12 -0
- package/skills/check-partition-quality/SKILL.md +1 -1
- package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +233 -0
- package/skills/check-partition-quality/scripts/check-partition-quality.mjs +12 -0
- package/skills/check-plan-safety/README.md +3 -3
- package/skills/check-plan-safety/SKILL.md +1 -1
- package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +107 -0
- package/skills/check-plan-safety/scripts/check-plan-safety.mjs +12 -0
- package/skills/check-plugin-manifests/SKILL.md +1 -1
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +248 -0
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +12 -0
- package/skills/check-project-specs/README.md +3 -3
- package/skills/check-project-specs/SKILL.md +2 -2
- package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +584 -0
- package/skills/check-project-specs/scripts/check-project-specs.mjs +12 -0
- package/skills/check-project-specs/scripts/check-project-specs.mts +1 -0
- package/skills/check-retired-terms/README.md +2 -2
- package/skills/check-retired-terms/SKILL.md +2 -2
- package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +184 -0
- package/skills/check-retired-terms/scripts/check-retired-terms.mjs +12 -0
- package/skills/check-scenario-overlap/README.md +2 -2
- package/skills/check-scenario-overlap/SKILL.md +2 -3
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +175 -0
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +12 -0
- package/skills/check-spec-references/README.md +1 -1
- package/skills/check-spec-references/SKILL.md +1 -1
- package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +285 -0
- package/skills/check-spec-references/scripts/check-spec-references.mjs +12 -0
- package/skills/check-spec-structure/README.md +2 -2
- package/skills/check-spec-structure/SKILL.md +1 -1
- package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +241 -0
- package/skills/check-spec-structure/scripts/check-spec-structure.mjs +12 -0
- package/skills/collision-ladder/SKILL.md +3 -3
- package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +7041 -0
- package/skills/collision-ladder/scripts/collision-ladder.mjs +12 -0
- package/skills/collision-ladder/scripts/collision-ladder.mts +4 -2
- package/skills/combat-log-governance/SKILL.md +1 -1
- package/skills/concept-index/README.md +2 -2
- package/skills/concept-index/SKILL.md +1 -1
- package/skills/concept-index/scripts/concept-index.bundle.mjs +181 -0
- package/skills/concept-index/scripts/concept-index.mjs +12 -0
- package/skills/discover-plans/README.md +2 -2
- package/skills/discover-plans/SKILL.md +1 -1
- package/skills/discover-plans/scripts/discover-plans.bundle.mjs +135 -0
- package/skills/discover-plans/scripts/discover-plans.mjs +12 -0
- package/skills/discover-specs/README.md +1 -1
- package/skills/discover-specs/SKILL.md +1 -1
- package/skills/discover-specs/scripts/discover-specs.bundle.mjs +319 -0
- package/skills/discover-specs/scripts/discover-specs.mjs +12 -0
- package/skills/doctrine-loop/SKILL.md +2 -2
- package/skills/formation-loop/SKILL.md +11 -4
- package/skills/gate-validation-governance/SKILL.md +1 -1
- package/skills/init/README.md +3 -3
- package/skills/init/SKILL.md +3 -3
- package/skills/init/scripts/wire-statusline.bundle.mjs +166 -0
- package/skills/init/scripts/wire-statusline.mjs +12 -0
- package/skills/manage-ignore/README.md +5 -5
- package/skills/manage-ignore/SKILL.md +1 -1
- package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +244 -0
- package/skills/manage-ignore/scripts/manage-ignore.mjs +12 -0
- package/skills/manage-scenario-bridge/README.md +3 -3
- package/skills/manage-scenario-bridge/SKILL.md +1 -1
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +237 -0
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +12 -0
- package/skills/manage-spec-anchors/README.md +4 -4
- package/skills/manage-spec-anchors/SKILL.md +1 -1
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +308 -0
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +12 -0
- package/skills/mission-graph/SKILL.md +9 -9
- package/skills/mission-graph/scripts/mission-graph.bundle.mjs +1038 -0
- package/skills/mission-graph/scripts/mission-graph.mjs +12 -0
- package/skills/pause-mission/SKILL.md +1 -1
- package/skills/place-node/README.md +1 -1
- package/skills/place-node/SKILL.md +1 -1
- package/skills/place-node/scripts/place-node.bundle.mjs +115 -0
- package/skills/place-node/scripts/place-node.mjs +12 -0
- package/skills/plan-retirement/SKILL.md +1 -1
- package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +121 -0
- package/skills/plan-retirement/scripts/retire-plans.mjs +12 -0
- package/skills/plan-retirement/scripts/retire-plans.mts +6 -0
- package/skills/plugin-contract-governance/SKILL.md +5 -12
- package/skills/resolve-governances/README.md +1 -1
- package/skills/resolve-governances/SKILL.md +1 -1
- package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +350 -0
- package/skills/resolve-governances/scripts/resolve-governances.mjs +12 -0
- package/skills/resolve-governances/scripts/resolve-governances.mts +1 -1
- package/skills/resolve-tracking/SKILL.md +1 -1
- package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +150 -0
- package/skills/resolve-tracking/scripts/resolve-tracking.mjs +12 -0
- package/skills/scaffold-project-spec/SKILL.md +2 -2
- package/skills/sdd/SKILL.md +1 -1
- package/skills/solution-producer-governance/README.md +1 -1
- package/skills/solution-producer-governance/SKILL.md +1 -1
- package/skills/spec-gate/SKILL.md +5 -5
- package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +420 -0
- package/skills/spec-gate/scripts/check-spec-state.mjs +12 -0
- package/skills/spec-gate/scripts/check-suite.bundle.mjs +6498 -0
- package/skills/spec-gate/scripts/check-suite.mjs +12 -0
- package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +6577 -0
- package/skills/spec-gate/scripts/classify-edit-class.mjs +12 -0
- package/skills/spec-producer-governance/README.md +1 -1
- package/skills/spec-producer-governance/SKILL.md +3 -3
- package/skills/spec-structure-governance/README.md +4 -2
- package/skills/spec-structure-governance/SKILL.md +42 -7
- package/skills/start-mission/README.md +1 -1
- package/skills/start-mission/SKILL.md +6 -6
- package/skills/suite-format-governance/references/rubric.md +1 -1
- package/skills/touch-set-correction/SKILL.md +2 -2
- package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +6655 -0
- package/skills/touch-set-correction/scripts/touch-set-correction.mjs +12 -0
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +12 -3
- package/skills/verify-scenarios/README.md +3 -3
- package/skills/verify-scenarios/SKILL.md +3 -3
- package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +6537 -0
- package/skills/verify-scenarios/scripts/verify-scenarios.mjs +12 -0
|
@@ -0,0 +1,12 @@
|
|
|
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
|
|
@@ -572,8 +572,10 @@ function readStdin(): string {
|
|
|
572
572
|
}
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
const HERE =
|
|
576
|
-
|
|
575
|
+
const HERE = fileURLToPath(import.meta.url)
|
|
576
|
+
// A bundle (built for npm, where Node will not strip types) runs its sibling engines as bundles too.
|
|
577
|
+
const ENGINE_EXT = HERE.endsWith('.mts') ? '.mts' : '.bundle.mjs'
|
|
578
|
+
const DISCOVER_SPECS_PATH = join(dirname(HERE), '..', '..', 'discover-specs', 'scripts', `discover-specs${ENGINE_EXT}`)
|
|
577
579
|
|
|
578
580
|
interface DiscoveredSpec {
|
|
579
581
|
path: string
|
|
@@ -92,7 +92,7 @@ field.
|
|
|
92
92
|
### `report` — per-subagent dispatch (combat log)
|
|
93
93
|
|
|
94
94
|
```jsonl
|
|
95
|
-
{"seq": 3, "ts": "2026-06-28T18:30:11Z", "handle": "unional", "kind": "report", "role": "spec-producer", "agent": "sdd:automaton", "outcome": "pass", "summary": "wrote 14 scenarios covering the ledger expansion"}
|
|
95
|
+
{"seq": 3, "ts": "2026-06-28T18:30:11Z", "handle": "unional", "kind": "report", "role": "spec-producer", "agent": "sdd:sdd-automaton", "outcome": "pass", "summary": "wrote 14 scenarios covering the ledger expansion"}
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
`role` is the production role dispatched; `agent` is the plugin-qualified agent name; `outcome` is
|
|
@@ -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.
|
|
9
|
-
node scripts/concept-index.
|
|
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
|
|
@@ -17,7 +17,7 @@ carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps conven
|
|
|
17
17
|
## Run it
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
node "<skill>/scripts/concept-index.
|
|
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).
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
//#region skills/concept-index/scripts/concept-index.mts
|
|
6
|
+
const BEGIN_MARKER = "<!-- BEGIN generated: by-concept (project-spec/concept-index) -->";
|
|
7
|
+
const END_MARKER = "<!-- END generated: by-concept -->";
|
|
8
|
+
const ANCHOR_HEADING = "## Invariants";
|
|
9
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10
|
+
"node_modules",
|
|
11
|
+
".git",
|
|
12
|
+
"dist",
|
|
13
|
+
".turbo",
|
|
14
|
+
".next",
|
|
15
|
+
"coverage"
|
|
16
|
+
]);
|
|
17
|
+
function parseFrontmatter(text) {
|
|
18
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text);
|
|
19
|
+
if (!m) return null;
|
|
20
|
+
const fm = {
|
|
21
|
+
concepts: [],
|
|
22
|
+
model: false
|
|
23
|
+
};
|
|
24
|
+
const lines = m[1].split("\n").map((l) => l.replace(/\r$/, ""));
|
|
25
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26
|
+
const line = lines[i];
|
|
27
|
+
if (line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
28
|
+
if (line.length - line.trimStart().length !== 0) continue;
|
|
29
|
+
const [key, ...rest] = line.trim().split(":");
|
|
30
|
+
const value = rest.join(":").trim();
|
|
31
|
+
if (key === "spec-type") fm.specType = unquote(value);
|
|
32
|
+
else if (key === "model") fm.model = unquote(value) === "true";
|
|
33
|
+
else if (key === "concept") {
|
|
34
|
+
if (value === "" || value === "|" || value === ">") for (let j = i + 1; j < lines.length; j++) {
|
|
35
|
+
const item = lines[j];
|
|
36
|
+
if (item.trim() === "") continue;
|
|
37
|
+
if (item.length - item.trimStart().length === 0) break;
|
|
38
|
+
const dash = /^\s*-\s+(.*)$/.exec(item);
|
|
39
|
+
if (dash) fm.concepts.push(unquote(dash[1].trim()));
|
|
40
|
+
}
|
|
41
|
+
else fm.concepts.push(...parseScalarOrFlow(value));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return fm;
|
|
45
|
+
}
|
|
46
|
+
function parseScalarOrFlow(value) {
|
|
47
|
+
const v = value.trim();
|
|
48
|
+
if (v.startsWith("[") && v.endsWith("]")) return v.slice(1, -1).split(",").map((s) => unquote(s.trim())).filter((s) => s.length > 0);
|
|
49
|
+
const single = unquote(v);
|
|
50
|
+
return single.length > 0 ? [single] : [];
|
|
51
|
+
}
|
|
52
|
+
function unquote(v) {
|
|
53
|
+
return v.replace(/^["']|["']$/g, "");
|
|
54
|
+
}
|
|
55
|
+
function facetKind(relPath, specType) {
|
|
56
|
+
const p = relPath.replace(/\\/g, "/");
|
|
57
|
+
if (p.startsWith("design/")) return "rule";
|
|
58
|
+
if (p.startsWith("workflows/")) return "workflow";
|
|
59
|
+
if (specType === "reference") return "reference";
|
|
60
|
+
if (specType === "behavioral") return "behavior";
|
|
61
|
+
return "index";
|
|
62
|
+
}
|
|
63
|
+
function displayPath(relPath) {
|
|
64
|
+
const p = relPath.replace(/\\/g, "/");
|
|
65
|
+
return p.endsWith("/README.md") ? p.slice(0, -9) : p;
|
|
66
|
+
}
|
|
67
|
+
function scanProjectSpec(specDir) {
|
|
68
|
+
const records = [];
|
|
69
|
+
walk(specDir, specDir, records);
|
|
70
|
+
return records;
|
|
71
|
+
}
|
|
72
|
+
function walk(dir, specDir, out) {
|
|
73
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
74
|
+
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
75
|
+
const full = join(dir, entry.name);
|
|
76
|
+
if (entry.isDirectory()) walk(full, specDir, out);
|
|
77
|
+
else if (entry.name.endsWith(".md")) {
|
|
78
|
+
const fm = parseFrontmatter(readFileSync(full, "utf8"));
|
|
79
|
+
if (!fm || fm.concepts.length === 0) continue;
|
|
80
|
+
const relPath = relative(specDir, full).replace(/\\/g, "/");
|
|
81
|
+
out.push({
|
|
82
|
+
relPath,
|
|
83
|
+
display: displayPath(relPath),
|
|
84
|
+
concepts: fm.concepts,
|
|
85
|
+
facet: facetKind(relPath, fm.specType)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function groupByConcept(records) {
|
|
91
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const rec of records) for (const concept of rec.concepts) {
|
|
93
|
+
const list = grouped.get(concept) ?? [];
|
|
94
|
+
list.push(rec);
|
|
95
|
+
grouped.set(concept, list);
|
|
96
|
+
}
|
|
97
|
+
return new Map([...grouped.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, [...v].sort((x, y) => x.display.localeCompare(y.display))]));
|
|
98
|
+
}
|
|
99
|
+
function renderTable(grouped) {
|
|
100
|
+
return [
|
|
101
|
+
"| Concept | Facets |",
|
|
102
|
+
"|---|---|",
|
|
103
|
+
...[...grouped.entries()].map(([concept, nodes]) => {
|
|
104
|
+
return `| \`${concept}\` | ${nodes.map((n) => `\`${n.display}\` (${n.facet})`).join(" · ")} |`;
|
|
105
|
+
})
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
function renderSection(grouped) {
|
|
109
|
+
return [
|
|
110
|
+
BEGIN_MARKER,
|
|
111
|
+
"",
|
|
112
|
+
"## By concept",
|
|
113
|
+
"",
|
|
114
|
+
"> Generated from `concept:` frontmatter by `project-spec/concept-index` — do not edit by hand.",
|
|
115
|
+
"",
|
|
116
|
+
renderTable(grouped),
|
|
117
|
+
"",
|
|
118
|
+
END_MARKER
|
|
119
|
+
].join("\n");
|
|
120
|
+
}
|
|
121
|
+
function applySection(specText, section) {
|
|
122
|
+
const begin = specText.indexOf(BEGIN_MARKER);
|
|
123
|
+
const end = specText.indexOf(END_MARKER);
|
|
124
|
+
if (begin !== -1 && end !== -1 && end > begin) {
|
|
125
|
+
const before = specText.slice(0, begin);
|
|
126
|
+
const after = specText.slice(end + 34);
|
|
127
|
+
return before + section + after;
|
|
128
|
+
}
|
|
129
|
+
const anchor = specText.indexOf(ANCHOR_HEADING);
|
|
130
|
+
if (anchor !== -1) return specText.slice(0, anchor) + section + "\n\n" + specText.slice(anchor);
|
|
131
|
+
return `${specText.replace(/\s*$/, "")}\n\n${section}\n`;
|
|
132
|
+
}
|
|
133
|
+
function extractSection(specText) {
|
|
134
|
+
const begin = specText.indexOf(BEGIN_MARKER);
|
|
135
|
+
const end = specText.indexOf(END_MARKER);
|
|
136
|
+
if (begin === -1 || end === -1 || end < begin) return null;
|
|
137
|
+
return specText.slice(begin, end + 34);
|
|
138
|
+
}
|
|
139
|
+
function parseArgs(argv) {
|
|
140
|
+
let specDir = ".";
|
|
141
|
+
let mode = "print";
|
|
142
|
+
for (let i = 0; i < argv.length; i++) {
|
|
143
|
+
const a = argv[i];
|
|
144
|
+
if (a === "--spec-dir") specDir = argv[++i] ?? ".";
|
|
145
|
+
else if (a === "--write") mode = "write";
|
|
146
|
+
else if (a === "--check") mode = "check";
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
specDir,
|
|
150
|
+
mode
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function main(argv) {
|
|
154
|
+
const { specDir, mode } = parseArgs(argv);
|
|
155
|
+
const section = renderSection(groupByConcept(scanProjectSpec(specDir)));
|
|
156
|
+
const specPath = join(specDir, "spec.md");
|
|
157
|
+
if (mode === "print") {
|
|
158
|
+
process.stdout.write(`${section}\n`);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
if (!existsSync(specPath)) {
|
|
162
|
+
process.stderr.write(`concept-index: no spec.md at ${specPath}\n`);
|
|
163
|
+
return 2;
|
|
164
|
+
}
|
|
165
|
+
const specText = readFileSync(specPath, "utf8");
|
|
166
|
+
if (mode === "check") {
|
|
167
|
+
if (extractSection(specText) === section) {
|
|
168
|
+
process.stdout.write("concept-index: no drift\n");
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
process.stderr.write("concept-index: drift — spec.md by-concept block is stale; run --write\n");
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
const next = applySection(specText, section);
|
|
175
|
+
if (next !== specText) writeFileSync(specPath, next);
|
|
176
|
+
process.stdout.write(`concept-index: ${next === specText ? "unchanged" : "updated"} ${specPath}\n`);
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
180
|
+
//#endregion
|
|
181
|
+
export { BEGIN_MARKER, END_MARKER, applySection, extractSection, facetKind, groupByConcept, main, parseFrontmatter, parseScalarOrFlow, renderSection, renderTable, scanProjectSpec };
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
|
@@ -11,6 +11,6 @@ an unretired/resumable mission, tallies its todos, reads its top-level `status`
|
|
|
11
11
|
- **Tests:** [`scripts/discover-plans.test.mts`](./scripts/discover-plans.test.mts) (`node:test`)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
node scripts/discover-plans.
|
|
15
|
-
node scripts/discover-plans.
|
|
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
|
```
|
|
@@ -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.
|
|
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,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
//#region skills/discover-plans/scripts/discover-plans.mts
|
|
6
|
+
const TODO_STATUSES = /* @__PURE__ */ new Set([
|
|
7
|
+
"pending",
|
|
8
|
+
"in_progress",
|
|
9
|
+
"completed"
|
|
10
|
+
]);
|
|
11
|
+
/** The default mission dispatch status — the meaning of an absent top-level `status`. */
|
|
12
|
+
const DEFAULT_PLAN_STATUS = "active";
|
|
13
|
+
function parsePlanFrontmatter(text) {
|
|
14
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text);
|
|
15
|
+
if (!m) return null;
|
|
16
|
+
const fm = {
|
|
17
|
+
name: "",
|
|
18
|
+
status: DEFAULT_PLAN_STATUS,
|
|
19
|
+
total: 0,
|
|
20
|
+
completed: 0,
|
|
21
|
+
inProgress: 0,
|
|
22
|
+
pending: 0
|
|
23
|
+
};
|
|
24
|
+
let inTodos = false;
|
|
25
|
+
for (const raw of m[1].split("\n")) {
|
|
26
|
+
const line = raw.replace(/\r$/, "");
|
|
27
|
+
if (line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
28
|
+
const indent = line.length - line.trimStart().length;
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (indent === 0) {
|
|
31
|
+
inTodos = false;
|
|
32
|
+
const [key, ...rest] = trimmed.split(":");
|
|
33
|
+
const value = rest.join(":").trim();
|
|
34
|
+
if (key === "name") fm.name = unquote(value);
|
|
35
|
+
else if (key === "status") {
|
|
36
|
+
const v = unquote(value);
|
|
37
|
+
if (v !== "") fm.status = v;
|
|
38
|
+
} else if (key === "todos") inTodos = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (inTodos) {
|
|
42
|
+
const sm = /^(?:-\s+)?status:\s*(.+)$/.exec(trimmed);
|
|
43
|
+
if (sm) {
|
|
44
|
+
const status = unquote(sm[1].trim());
|
|
45
|
+
fm.total++;
|
|
46
|
+
if (status === "completed") fm.completed++;
|
|
47
|
+
else if (status === "in_progress") fm.inProgress++;
|
|
48
|
+
else if (status === "pending") fm.pending++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return fm;
|
|
53
|
+
}
|
|
54
|
+
function unquote(v) {
|
|
55
|
+
return v.replace(/^["']|["']$/g, "");
|
|
56
|
+
}
|
|
57
|
+
function nextLead(text) {
|
|
58
|
+
const lines = text.split("\n");
|
|
59
|
+
let i = lines.findIndex((l) => /^##\s+NEXT\b/i.test(l.trim()));
|
|
60
|
+
if (i === -1) return "";
|
|
61
|
+
for (i += 1; i < lines.length; i++) {
|
|
62
|
+
const l = lines[i].replace(/\r$/, "").trim();
|
|
63
|
+
if (l === "") continue;
|
|
64
|
+
if (l.startsWith("#")) return "";
|
|
65
|
+
const cleaned = l.replace(/^[-*]\s+/, "").replace(/\*\*/g, "").trim();
|
|
66
|
+
return cleaned.length > 200 ? `${cleaned.slice(0, 197)}...` : cleaned;
|
|
67
|
+
}
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
function discoverPlanFiles(root) {
|
|
71
|
+
const dir = join(root, ".agents", "plans");
|
|
72
|
+
if (!existsSync(dir)) return [];
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
return entries.filter((e) => e.isFile() && e.name.endsWith(".plan.md")).map((e) => e.name).sort();
|
|
80
|
+
}
|
|
81
|
+
function collectPlans(root) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const name of discoverPlanFiles(root)) {
|
|
84
|
+
let text;
|
|
85
|
+
try {
|
|
86
|
+
text = readFileSync(join(root, ".agents", "plans", name), "utf8");
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const fm = parsePlanFrontmatter(text);
|
|
91
|
+
if (!fm) continue;
|
|
92
|
+
out.push({
|
|
93
|
+
cr: name.replace(/\.plan\.md$/, ""),
|
|
94
|
+
name: fm.name,
|
|
95
|
+
total: fm.total,
|
|
96
|
+
completed: fm.completed,
|
|
97
|
+
inProgress: fm.inProgress,
|
|
98
|
+
status: fm.status,
|
|
99
|
+
next: nextLead(text)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return out.sort((a, b) => a.cr < b.cr ? -1 : a.cr > b.cr ? 1 : 0);
|
|
103
|
+
}
|
|
104
|
+
function filterByStatus(plans, status) {
|
|
105
|
+
return plans.filter((p) => p.status === status);
|
|
106
|
+
}
|
|
107
|
+
const COLUMNS = [
|
|
108
|
+
"cr",
|
|
109
|
+
"name",
|
|
110
|
+
"total",
|
|
111
|
+
"completed",
|
|
112
|
+
"inProgress",
|
|
113
|
+
"status",
|
|
114
|
+
"next"
|
|
115
|
+
];
|
|
116
|
+
function toonField(v) {
|
|
117
|
+
if (v === "" || /[",]/.test(v) || v !== v.trim()) return `"${v.replace(/"/g, "\"\"")}"`;
|
|
118
|
+
return v;
|
|
119
|
+
}
|
|
120
|
+
function toToon(plans) {
|
|
121
|
+
return [`plans[${plans.length}]{${COLUMNS.join(",")}}:`, ...plans.map((p) => ` ${COLUMNS.map((c) => toonField(String(p[c]))).join(",")}`)].join("\n");
|
|
122
|
+
}
|
|
123
|
+
function main(argv) {
|
|
124
|
+
const root = argv.includes("--root") ? argv[argv.indexOf("--root") + 1] ?? "." : ".";
|
|
125
|
+
const format = argv.includes("--format") ? argv[argv.indexOf("--format") + 1] : "toon";
|
|
126
|
+
const statusFilter = argv.includes("--status") ? argv[argv.indexOf("--status") + 1] : void 0;
|
|
127
|
+
let plans = collectPlans(root);
|
|
128
|
+
if (statusFilter !== void 0) plans = filterByStatus(plans, statusFilter);
|
|
129
|
+
const out = format === "json" ? JSON.stringify(plans, null, 2) : toToon(plans);
|
|
130
|
+
process.stdout.write(`${out}\n`);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
134
|
+
//#endregion
|
|
135
|
+
export { DEFAULT_PLAN_STATUS, TODO_STATUSES, collectPlans, filterByStatus, main, nextLead, parsePlanFrontmatter, toToon };
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
|
@@ -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.
|
|
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
|