cyber-sdd 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -2
- package/.codex-plugin/plugin.json +1 -2
- package/CHANGELOG.md +320 -0
- package/agents/sdd-scanner.md +1 -1
- package/package.json +11 -12
- package/plugin.json +55 -0
- package/skills/align-spec/SKILL.md +1 -1
- package/skills/align-spec/scripts/align-spec.mjs +6755 -12
- package/skills/blast-estimate/README.md +3 -3
- package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -12
- package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -12
- package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -12
- package/skills/check-plan-safety/SKILL.md +2 -3
- package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -12
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -12
- package/skills/check-project-specs/SKILL.md +1 -1
- package/skills/check-project-specs/scripts/check-project-specs.mjs +593 -12
- package/skills/check-retired-terms/SKILL.md +2 -3
- package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -12
- package/skills/check-scenario-overlap/SKILL.md +1 -2
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +177 -12
- package/skills/check-spec-references/SKILL.md +2 -2
- package/skills/check-spec-references/scripts/check-spec-references.mjs +287 -12
- package/skills/check-spec-structure/SKILL.md +1 -2
- package/skills/check-spec-structure/scripts/check-spec-structure.mjs +243 -12
- package/skills/collision-ladder/README.md +2 -2
- package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -12
- package/skills/combat-log-governance/SKILL.md +1 -1
- package/skills/concept-index/SKILL.md +1 -1
- package/skills/concept-index/scripts/concept-index.mjs +183 -12
- package/skills/discover-plans/README.md +3 -3
- package/skills/discover-plans/SKILL.md +1 -1
- package/skills/discover-plans/scripts/discover-plans.mjs +137 -12
- package/skills/discover-specs/README.md +3 -3
- package/skills/discover-specs/SKILL.md +1 -1
- package/skills/discover-specs/scripts/discover-specs.mjs +321 -12
- package/skills/doctrine-loop/SKILL.md +1 -1
- package/skills/gate-validation-governance/SKILL.md +1 -1
- package/skills/init/README.md +2 -2
- package/skills/init/scripts/wire-statusline.mjs +168 -12
- package/skills/manage-ignore/README.md +3 -3
- package/skills/manage-ignore/SKILL.md +1 -2
- package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -12
- 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.mjs +239 -12
- package/skills/manage-spec-anchors/README.md +3 -3
- package/skills/manage-spec-anchors/SKILL.md +1 -1
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -12
- package/skills/mission-graph/README.md +2 -2
- package/skills/mission-graph/SKILL.md +1 -1
- package/skills/mission-graph/scripts/mission-graph.mjs +1040 -12
- package/skills/pause-mission/SKILL.md +1 -1
- package/skills/place-node/SKILL.md +2 -2
- package/skills/place-node/scripts/place-node.mjs +117 -12
- package/skills/plan-retirement/README.md +2 -2
- package/skills/plan-retirement/scripts/retire-plans.mjs +123 -12
- package/skills/resolve-governances/README.md +3 -3
- package/skills/resolve-governances/SKILL.md +1 -1
- package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -12
- package/skills/resolve-tracking/SKILL.md +1 -1
- package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -12
- package/skills/scaffold-project-spec/SKILL.md +1 -1
- package/skills/spec-gate/README.md +12 -9
- package/skills/spec-gate/SKILL.md +2 -2
- package/skills/spec-gate/scripts/check-spec-state.mjs +422 -12
- package/skills/spec-gate/scripts/check-suite.mjs +6500 -12
- package/skills/spec-gate/scripts/classify-edit-class.mjs +6579 -12
- package/skills/spec-producer-governance/SKILL.md +2 -2
- package/skills/start-mission/SKILL.md +1 -1
- package/skills/suite-format-governance/README.md +1 -1
- package/skills/suite-format-governance/SKILL.md +1 -1
- package/skills/touch-set-correction/README.md +5 -5
- package/skills/touch-set-correction/SKILL.md +1 -1
- package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -12
- package/skills/verify-scenarios/README.md +6 -6
- package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -12
- package/.plugin/pins.json +0 -3
- package/.plugin/plugin.json +0 -19
- package/dist/align-spec/scripts/align-spec.js +0 -102
- package/dist/check-project-specs/scripts/check-project-specs.js +0 -312
- package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +0 -175
- package/dist/check-spec-references/scripts/check-spec-references.js +0 -285
- package/dist/check-spec-structure/scripts/check-spec-structure.js +0 -241
- package/dist/concept-index/scripts/concept-index.js +0 -181
- package/dist/discover-specs/scripts/discover-specs.js +0 -318
- package/dist/spec-gate/scripts/check-spec-state.js +0 -420
- package/dist/spec-gate/scripts/check-suite.js +0 -317
- package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
- package/dist/spec-gate/vendor/gherkin-cli.js +0 -6369
- package/skills/align-spec/scripts/align-spec.bundle.mjs +0 -6753
- package/skills/align-spec/scripts/align-spec.mts +0 -188
- package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +0 -593
- package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
- package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +0 -251
- package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
- package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +0 -233
- package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
- package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +0 -107
- package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +0 -248
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
- package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +0 -584
- package/skills/check-project-specs/scripts/check-project-specs.mts +0 -370
- package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +0 -184
- package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +0 -175
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
- package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +0 -285
- package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
- package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +0 -241
- package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
- package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +0 -7041
- package/skills/collision-ladder/scripts/collision-ladder.mts +0 -662
- package/skills/concept-index/scripts/concept-index.bundle.mjs +0 -181
- package/skills/concept-index/scripts/concept-index.mts +0 -246
- package/skills/discover-plans/scripts/discover-plans.bundle.mjs +0 -135
- package/skills/discover-plans/scripts/discover-plans.mts +0 -215
- package/skills/discover-specs/scripts/discover-specs.bundle.mjs +0 -319
- package/skills/discover-specs/scripts/discover-specs.mts +0 -420
- package/skills/init/scripts/wire-statusline.bundle.mjs +0 -166
- package/skills/init/scripts/wire-statusline.mts +0 -279
- package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +0 -244
- package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +0 -237
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +0 -308
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
- package/skills/mission-graph/scripts/mission-graph.bundle.mjs +0 -1038
- package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
- package/skills/place-node/scripts/place-node.bundle.mjs +0 -115
- package/skills/place-node/scripts/place-node.mts +0 -158
- package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +0 -121
- package/skills/plan-retirement/scripts/retire-plans.mts +0 -205
- package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +0 -350
- package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
- package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +0 -150
- package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
- package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +0 -420
- package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
- package/skills/spec-gate/scripts/check-suite.bundle.mjs +0 -6498
- package/skills/spec-gate/scripts/check-suite.mts +0 -538
- package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +0 -6577
- package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
- package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
- package/skills/spec-gate/vendor/gherkin-cli.mjs +0 -6545
- package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +0 -6655
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -429
- package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +0 -6537
- package/skills/verify-scenarios/scripts/verify-scenarios.mts +0 -470
|
@@ -1,12 +1,285 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compiled by `universal-plugin plugin build` from src/manage-ignore/manage-ignore.mts. Edit the source, not this file.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/manage-ignore/scripts/manage-ignore.mjs","commands":{}};
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
//#region src/manage-ignore/manage-ignore.mts
|
|
8
|
+
const IGNORE_FILE = ".agents/sdd/.sddignore";
|
|
9
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10
|
+
"node_modules",
|
|
11
|
+
".git",
|
|
12
|
+
"dist",
|
|
13
|
+
".turbo",
|
|
14
|
+
".next",
|
|
15
|
+
"coverage"
|
|
16
|
+
]);
|
|
17
|
+
function splitLines(text) {
|
|
18
|
+
const lines = text.split("\n");
|
|
19
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
20
|
+
return lines;
|
|
21
|
+
}
|
|
22
|
+
function readIgnoreLines(root) {
|
|
23
|
+
const file = join(root, IGNORE_FILE);
|
|
24
|
+
if (!existsSync(file)) return [];
|
|
25
|
+
try {
|
|
26
|
+
return splitLines(readFileSync(file, "utf8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function writeIgnoreLines(root, lines) {
|
|
32
|
+
const file = join(root, IGNORE_FILE);
|
|
33
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
34
|
+
writeFileSync(file, lines.length === 0 ? "" : `${lines.join("\n")}\n`);
|
|
35
|
+
}
|
|
36
|
+
function isRuleLine(line) {
|
|
37
|
+
const t = line.trim();
|
|
38
|
+
return t !== "" && !t.startsWith("#");
|
|
39
|
+
}
|
|
40
|
+
function listRules(root) {
|
|
41
|
+
return readIgnoreLines(root).filter(isRuleLine).map((l) => l.trim());
|
|
42
|
+
}
|
|
43
|
+
function hasUnclosedBracket(s) {
|
|
44
|
+
let i = 0;
|
|
45
|
+
while (i < s.length) {
|
|
46
|
+
if (s[i] === "\\") {
|
|
47
|
+
i += 2;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (s[i] === "[") {
|
|
51
|
+
const close = s.indexOf("]", i + 1);
|
|
52
|
+
if (close === -1) return true;
|
|
53
|
+
i = close + 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function isValidPattern(pattern) {
|
|
61
|
+
let s = pattern;
|
|
62
|
+
if (s.startsWith("!")) s = s.slice(1);
|
|
63
|
+
s = s.replace(/^\s+|\s+$/g, "");
|
|
64
|
+
if (s === "" || s.startsWith("#")) return false;
|
|
65
|
+
if (hasUnclosedBracket(s)) return false;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function addRule(root, pattern) {
|
|
69
|
+
const p = pattern.trim();
|
|
70
|
+
if (!isValidPattern(p)) return {
|
|
71
|
+
ok: false,
|
|
72
|
+
reason: "malformed gitignore pattern"
|
|
73
|
+
};
|
|
74
|
+
const lines = readIgnoreLines(root);
|
|
75
|
+
if (lines.some((l) => l.trim() === p)) return {
|
|
76
|
+
ok: true,
|
|
77
|
+
changed: false,
|
|
78
|
+
rules: listRules(root),
|
|
79
|
+
message: "already present"
|
|
80
|
+
};
|
|
81
|
+
const next = [...lines, p];
|
|
82
|
+
writeIgnoreLines(root, next);
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
changed: true,
|
|
86
|
+
rules: next.filter(isRuleLine).map((l) => l.trim()),
|
|
87
|
+
message: `added ${p}`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function removeRule(root, pattern) {
|
|
91
|
+
const p = pattern.trim();
|
|
92
|
+
const lines = readIgnoreLines(root);
|
|
93
|
+
const next = lines.filter((l) => l.trim() !== p);
|
|
94
|
+
if (next.length === lines.length) return {
|
|
95
|
+
ok: true,
|
|
96
|
+
changed: false,
|
|
97
|
+
rules: listRules(root),
|
|
98
|
+
message: "nothing was removed"
|
|
99
|
+
};
|
|
100
|
+
writeIgnoreLines(root, next);
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
changed: true,
|
|
104
|
+
rules: next.filter(isRuleLine).map((l) => l.trim()),
|
|
105
|
+
message: `removed ${p}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function inducePatterns(_root, samplePath) {
|
|
109
|
+
const normalized = samplePath.trim().replace(/\\/g, "/");
|
|
110
|
+
if (normalized.startsWith("/")) return {
|
|
111
|
+
ok: false,
|
|
112
|
+
reason: "path is not repo-relative (absolute)"
|
|
113
|
+
};
|
|
114
|
+
const rel = normalized.replace(/\/+$/g, "");
|
|
115
|
+
if (rel === "" || /(^|\/)\.\.(\/|$)/.test(rel)) return {
|
|
116
|
+
ok: false,
|
|
117
|
+
reason: "path is not repo-relative"
|
|
118
|
+
};
|
|
119
|
+
const generalized = `**/${rel.split("/").pop() ?? rel}`;
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
candidates: generalized === rel ? [rel] : [rel, generalized]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function tokenize(pat) {
|
|
126
|
+
const tokens = [];
|
|
127
|
+
for (let i = 0; i < pat.length; i++) {
|
|
128
|
+
const c = pat[i];
|
|
129
|
+
if (c === "*") {
|
|
130
|
+
if (pat[i + 1] === "*") {
|
|
131
|
+
i++;
|
|
132
|
+
if (pat[i + 1] === "/") {
|
|
133
|
+
i++;
|
|
134
|
+
tokens.push({ kind: "deepdir" });
|
|
135
|
+
} else tokens.push({ kind: "deep" });
|
|
136
|
+
} else tokens.push({ kind: "star" });
|
|
137
|
+
} else if (c === "?") tokens.push({ kind: "one" });
|
|
138
|
+
else if (c === "[") {
|
|
139
|
+
let j = i + 1;
|
|
140
|
+
const negated = pat[j] === "!";
|
|
141
|
+
if (negated) j++;
|
|
142
|
+
let chars = "";
|
|
143
|
+
while (j < pat.length && pat[j] !== "]") chars += pat[j++];
|
|
144
|
+
tokens.push({
|
|
145
|
+
kind: "class",
|
|
146
|
+
negated,
|
|
147
|
+
chars
|
|
148
|
+
});
|
|
149
|
+
i = j;
|
|
150
|
+
} else tokens.push({
|
|
151
|
+
kind: "lit",
|
|
152
|
+
ch: c
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return tokens;
|
|
156
|
+
}
|
|
157
|
+
function classHas(chars, ch) {
|
|
158
|
+
for (let k = 0; k < chars.length; k++) if (chars[k + 1] === "-" && k + 2 < chars.length) {
|
|
159
|
+
if (ch >= chars[k] && ch <= chars[k + 2]) return true;
|
|
160
|
+
k += 2;
|
|
161
|
+
} else if (chars[k] === ch) return true;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
function matchTokens(tokens, path, start) {
|
|
165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
166
|
+
const walk = (ti, pi) => {
|
|
167
|
+
const key = ti * (path.length + 1) + pi;
|
|
168
|
+
if (seen.has(key)) return false;
|
|
169
|
+
seen.add(key);
|
|
170
|
+
if (ti === tokens.length) return pi === path.length || path[pi] === "/";
|
|
171
|
+
const t = tokens[ti];
|
|
172
|
+
switch (t.kind) {
|
|
173
|
+
case "lit": return path[pi] === t.ch && walk(ti + 1, pi + 1);
|
|
174
|
+
case "one": return pi < path.length && path[pi] !== "/" && walk(ti + 1, pi + 1);
|
|
175
|
+
case "class": return pi < path.length && classHas(t.chars, path[pi]) !== t.negated && walk(ti + 1, pi + 1);
|
|
176
|
+
case "star": for (let k = pi;; k++) {
|
|
177
|
+
if (walk(ti + 1, k)) return true;
|
|
178
|
+
if (k >= path.length || path[k] === "/") return false;
|
|
179
|
+
}
|
|
180
|
+
case "deep":
|
|
181
|
+
for (let k = pi; k <= path.length; k++) if (walk(ti + 1, k)) return true;
|
|
182
|
+
return false;
|
|
183
|
+
case "deepdir":
|
|
184
|
+
if (walk(ti + 1, pi)) return true;
|
|
185
|
+
for (let k = pi; k < path.length; k++) if (path[k] === "/" && walk(ti + 1, k + 1)) return true;
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return walk(0, start);
|
|
190
|
+
}
|
|
191
|
+
function matchesPattern(pattern, path) {
|
|
192
|
+
let pat = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
193
|
+
if (pat.endsWith("/")) pat = pat.slice(0, -1);
|
|
194
|
+
let anchored = pat.startsWith("/");
|
|
195
|
+
if (anchored) pat = pat.slice(1);
|
|
196
|
+
if (pat.includes("/")) anchored = true;
|
|
197
|
+
const tokens = tokenize(pat);
|
|
198
|
+
if (matchTokens(tokens, path, 0)) return true;
|
|
199
|
+
if (anchored) return false;
|
|
200
|
+
for (let k = 0; k < path.length; k++) if (path[k] === "/" && matchTokens(tokens, path, k + 1)) return true;
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function walkTree(root) {
|
|
204
|
+
const out = [];
|
|
205
|
+
const walk = (rel) => {
|
|
206
|
+
let entries;
|
|
207
|
+
try {
|
|
208
|
+
entries = readdirSync(join(root, rel), { withFileTypes: true });
|
|
209
|
+
} catch {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const e of entries) {
|
|
213
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
214
|
+
const child = rel ? `${rel}/${e.name}` : e.name;
|
|
215
|
+
out.push(child);
|
|
216
|
+
if (e.isDirectory()) walk(child);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
walk("");
|
|
220
|
+
return out.sort();
|
|
221
|
+
}
|
|
222
|
+
function previewPattern(root, pattern) {
|
|
223
|
+
const p = pattern.trim();
|
|
224
|
+
if (!isValidPattern(p)) return {
|
|
225
|
+
ok: false,
|
|
226
|
+
reason: "malformed gitignore pattern"
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
negated: p.startsWith("!"),
|
|
231
|
+
matches: walkTree(root).filter((path) => matchesPattern(p, path))
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function flag(argv, name) {
|
|
235
|
+
const i = argv.indexOf(name);
|
|
236
|
+
return i === -1 ? void 0 : argv[i + 1];
|
|
237
|
+
}
|
|
238
|
+
function main(argv) {
|
|
239
|
+
const root = flag(argv, "--root") ?? ".";
|
|
240
|
+
const w = (s) => process.stdout.write(`${s}\n`);
|
|
241
|
+
if (argv.includes("--list")) {
|
|
242
|
+
for (const rule of listRules(root)) w(rule);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
if (argv.includes("--add")) {
|
|
246
|
+
const r = addRule(root, flag(argv, "--add") ?? "");
|
|
247
|
+
w(r.ok ? r.message : `refused: ${r.reason}`);
|
|
248
|
+
return r.ok ? 0 : 1;
|
|
249
|
+
}
|
|
250
|
+
if (argv.includes("--remove")) {
|
|
251
|
+
const r = removeRule(root, flag(argv, "--remove") ?? "");
|
|
252
|
+
w(r.ok ? r.message : `refused: ${r.reason}`);
|
|
253
|
+
return r.ok ? 0 : 1;
|
|
254
|
+
}
|
|
255
|
+
if (argv.includes("--induce")) {
|
|
256
|
+
const r = inducePatterns(root, flag(argv, "--induce") ?? "");
|
|
257
|
+
if (!r.ok) {
|
|
258
|
+
w(`unusable: ${r.reason}`);
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
w("Candidate ignore patterns:");
|
|
262
|
+
for (const c of r.candidates) w(` ${c}`);
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
if (argv.includes("--preview")) {
|
|
266
|
+
const r = previewPattern(root, flag(argv, "--preview") ?? "");
|
|
267
|
+
if (!r.ok) {
|
|
268
|
+
w(`invalid: ${r.reason}`);
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
const verb = r.negated ? "re-track" : "ignore";
|
|
272
|
+
if (r.matches.length === 0) {
|
|
273
|
+
w(`This pattern would ${verb} no path.`);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
w(`This pattern would ${verb}:`);
|
|
277
|
+
for (const m of r.matches) w(` ${m}`);
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
w("usage: manage-ignore --list | --add <p> | --remove <p> | --induce <path> | --preview <p>");
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
284
|
+
//#endregion
|
|
285
|
+
export { addRule, inducePatterns, isRuleLine, isValidPattern, listRules, main, matchesPattern, previewPattern, readIgnoreLines, removeRule, splitLines };
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
The concrete engine for `<project-path>/.agents/sdd/scenario-bridge.toml` — the one-time
|
|
4
4
|
per-project wiring the `verify-scenarios` bridge and the impl-judge step-0 consumption both read. A
|
|
5
5
|
non-user-invocable skill, loaded in-session by the `manage` gateway (Setup & discovery), carrying a
|
|
6
|
-
self-contained `.
|
|
6
|
+
self-contained `.mjs` script that lists / scaffolds / adds sources so users never hand-author the
|
|
7
7
|
config.
|
|
8
8
|
|
|
9
9
|
- **Skill contract:** [`SKILL.md`](./SKILL.md)
|
|
10
|
-
- **Script:** [`scripts/manage-scenario-bridge.
|
|
11
|
-
- **Tests:** [`
|
|
10
|
+
- **Script:** [`scripts/manage-scenario-bridge.mjs`](./scripts/manage-scenario-bridge.mjs)
|
|
11
|
+
- **Tests:** [`src/manage-scenario-bridge/manage-scenario-bridge.test.mts`](../../src/manage-scenario-bridge/manage-scenario-bridge.test.mts) (`node:test`)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
node scripts/manage-scenario-bridge.mjs --project-path packages/cyberlegion --list
|
|
@@ -13,7 +13,7 @@ per-project wiring the [`verify-scenarios`](../verify-scenarios/) bridge and the
|
|
|
13
13
|
step-0 consumption both read. It is the **write** side of that config (`verify-scenarios` is the
|
|
14
14
|
read side). It exists so a user wires a project's scenario bridge through a clean interface instead
|
|
15
15
|
of hand-authoring the TOML. Loaded **in-session** by the `manage` gateway (Setup & discovery group);
|
|
16
|
-
it carries a self-contained `.
|
|
16
|
+
it carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11).
|
|
17
17
|
|
|
18
18
|
## The file it curates
|
|
19
19
|
|
|
@@ -1,12 +1,239 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compiled by `universal-plugin plugin build` from src/manage-scenario-bridge/manage-scenario-bridge.mts. Edit the source, not this file.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs","commands":{}};
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
//#region src/vendor/gherkin-cli.mjs
|
|
8
|
+
var TokenType;
|
|
9
|
+
(function(TokenType) {
|
|
10
|
+
TokenType[TokenType["None"] = 0] = "None";
|
|
11
|
+
TokenType[TokenType["EOF"] = 1] = "EOF";
|
|
12
|
+
TokenType[TokenType["Empty"] = 2] = "Empty";
|
|
13
|
+
TokenType[TokenType["Comment"] = 3] = "Comment";
|
|
14
|
+
TokenType[TokenType["TagLine"] = 4] = "TagLine";
|
|
15
|
+
TokenType[TokenType["FeatureLine"] = 5] = "FeatureLine";
|
|
16
|
+
TokenType[TokenType["RuleLine"] = 6] = "RuleLine";
|
|
17
|
+
TokenType[TokenType["BackgroundLine"] = 7] = "BackgroundLine";
|
|
18
|
+
TokenType[TokenType["ScenarioLine"] = 8] = "ScenarioLine";
|
|
19
|
+
TokenType[TokenType["ExamplesLine"] = 9] = "ExamplesLine";
|
|
20
|
+
TokenType[TokenType["StepLine"] = 10] = "StepLine";
|
|
21
|
+
TokenType[TokenType["DocStringSeparator"] = 11] = "DocStringSeparator";
|
|
22
|
+
TokenType[TokenType["TableRow"] = 12] = "TableRow";
|
|
23
|
+
TokenType[TokenType["Language"] = 13] = "Language";
|
|
24
|
+
TokenType[TokenType["Other"] = 14] = "Other";
|
|
25
|
+
})(TokenType || (TokenType = {}));
|
|
26
|
+
var RuleType;
|
|
27
|
+
(function(RuleType) {
|
|
28
|
+
RuleType[RuleType["None"] = 0] = "None";
|
|
29
|
+
RuleType[RuleType["_EOF"] = 1] = "_EOF";
|
|
30
|
+
RuleType[RuleType["_Empty"] = 2] = "_Empty";
|
|
31
|
+
RuleType[RuleType["_Comment"] = 3] = "_Comment";
|
|
32
|
+
RuleType[RuleType["_TagLine"] = 4] = "_TagLine";
|
|
33
|
+
RuleType[RuleType["_FeatureLine"] = 5] = "_FeatureLine";
|
|
34
|
+
RuleType[RuleType["_RuleLine"] = 6] = "_RuleLine";
|
|
35
|
+
RuleType[RuleType["_BackgroundLine"] = 7] = "_BackgroundLine";
|
|
36
|
+
RuleType[RuleType["_ScenarioLine"] = 8] = "_ScenarioLine";
|
|
37
|
+
RuleType[RuleType["_ExamplesLine"] = 9] = "_ExamplesLine";
|
|
38
|
+
RuleType[RuleType["_StepLine"] = 10] = "_StepLine";
|
|
39
|
+
RuleType[RuleType["_DocStringSeparator"] = 11] = "_DocStringSeparator";
|
|
40
|
+
RuleType[RuleType["_TableRow"] = 12] = "_TableRow";
|
|
41
|
+
RuleType[RuleType["_Language"] = 13] = "_Language";
|
|
42
|
+
RuleType[RuleType["_Other"] = 14] = "_Other";
|
|
43
|
+
RuleType[RuleType["GherkinDocument"] = 15] = "GherkinDocument";
|
|
44
|
+
RuleType[RuleType["Feature"] = 16] = "Feature";
|
|
45
|
+
RuleType[RuleType["FeatureHeader"] = 17] = "FeatureHeader";
|
|
46
|
+
RuleType[RuleType["Rule"] = 18] = "Rule";
|
|
47
|
+
RuleType[RuleType["RuleHeader"] = 19] = "RuleHeader";
|
|
48
|
+
RuleType[RuleType["Background"] = 20] = "Background";
|
|
49
|
+
RuleType[RuleType["ScenarioDefinition"] = 21] = "ScenarioDefinition";
|
|
50
|
+
RuleType[RuleType["Scenario"] = 22] = "Scenario";
|
|
51
|
+
RuleType[RuleType["ExamplesDefinition"] = 23] = "ExamplesDefinition";
|
|
52
|
+
RuleType[RuleType["Examples"] = 24] = "Examples";
|
|
53
|
+
RuleType[RuleType["ExamplesTable"] = 25] = "ExamplesTable";
|
|
54
|
+
RuleType[RuleType["Step"] = 26] = "Step";
|
|
55
|
+
RuleType[RuleType["StepArg"] = 27] = "StepArg";
|
|
56
|
+
RuleType[RuleType["DataTable"] = 28] = "DataTable";
|
|
57
|
+
RuleType[RuleType["DocString"] = 29] = "DocString";
|
|
58
|
+
RuleType[RuleType["Tags"] = 30] = "Tags";
|
|
59
|
+
RuleType[RuleType["DescriptionHelper"] = 31] = "DescriptionHelper";
|
|
60
|
+
RuleType[RuleType["Description"] = 32] = "Description";
|
|
61
|
+
})(RuleType || (RuleType = {}));
|
|
62
|
+
var AttachmentContentEncoding;
|
|
63
|
+
(function(AttachmentContentEncoding) {
|
|
64
|
+
AttachmentContentEncoding["IDENTITY"] = "IDENTITY";
|
|
65
|
+
AttachmentContentEncoding["BASE64"] = "BASE64";
|
|
66
|
+
})(AttachmentContentEncoding || (AttachmentContentEncoding = {}));
|
|
67
|
+
var HookType;
|
|
68
|
+
(function(HookType) {
|
|
69
|
+
HookType["BEFORE_TEST_RUN"] = "BEFORE_TEST_RUN";
|
|
70
|
+
HookType["AFTER_TEST_RUN"] = "AFTER_TEST_RUN";
|
|
71
|
+
HookType["BEFORE_TEST_CASE"] = "BEFORE_TEST_CASE";
|
|
72
|
+
HookType["AFTER_TEST_CASE"] = "AFTER_TEST_CASE";
|
|
73
|
+
HookType["BEFORE_TEST_STEP"] = "BEFORE_TEST_STEP";
|
|
74
|
+
HookType["AFTER_TEST_STEP"] = "AFTER_TEST_STEP";
|
|
75
|
+
})(HookType || (HookType = {}));
|
|
76
|
+
var PickleStepType;
|
|
77
|
+
(function(PickleStepType) {
|
|
78
|
+
PickleStepType["UNKNOWN"] = "Unknown";
|
|
79
|
+
PickleStepType["CONTEXT"] = "Context";
|
|
80
|
+
PickleStepType["ACTION"] = "Action";
|
|
81
|
+
PickleStepType["OUTCOME"] = "Outcome";
|
|
82
|
+
})(PickleStepType || (PickleStepType = {}));
|
|
83
|
+
var SourceMediaType;
|
|
84
|
+
(function(SourceMediaType) {
|
|
85
|
+
SourceMediaType["TEXT_X_CUCUMBER_GHERKIN_PLAIN"] = "text/x.cucumber.gherkin+plain";
|
|
86
|
+
SourceMediaType["TEXT_X_CUCUMBER_GHERKIN_MARKDOWN"] = "text/x.cucumber.gherkin+markdown";
|
|
87
|
+
})(SourceMediaType || (SourceMediaType = {}));
|
|
88
|
+
var StepDefinitionPatternType;
|
|
89
|
+
(function(StepDefinitionPatternType) {
|
|
90
|
+
StepDefinitionPatternType["CUCUMBER_EXPRESSION"] = "CUCUMBER_EXPRESSION";
|
|
91
|
+
StepDefinitionPatternType["REGULAR_EXPRESSION"] = "REGULAR_EXPRESSION";
|
|
92
|
+
})(StepDefinitionPatternType || (StepDefinitionPatternType = {}));
|
|
93
|
+
var StepKeywordType;
|
|
94
|
+
(function(StepKeywordType) {
|
|
95
|
+
StepKeywordType["UNKNOWN"] = "Unknown";
|
|
96
|
+
StepKeywordType["CONTEXT"] = "Context";
|
|
97
|
+
StepKeywordType["ACTION"] = "Action";
|
|
98
|
+
StepKeywordType["OUTCOME"] = "Outcome";
|
|
99
|
+
StepKeywordType["CONJUNCTION"] = "Conjunction";
|
|
100
|
+
})(StepKeywordType || (StepKeywordType = {}));
|
|
101
|
+
var TestStepResultStatus;
|
|
102
|
+
(function(TestStepResultStatus) {
|
|
103
|
+
TestStepResultStatus["UNKNOWN"] = "UNKNOWN";
|
|
104
|
+
TestStepResultStatus["PASSED"] = "PASSED";
|
|
105
|
+
TestStepResultStatus["SKIPPED"] = "SKIPPED";
|
|
106
|
+
TestStepResultStatus["PENDING"] = "PENDING";
|
|
107
|
+
TestStepResultStatus["UNDEFINED"] = "UNDEFINED";
|
|
108
|
+
TestStepResultStatus["AMBIGUOUS"] = "AMBIGUOUS";
|
|
109
|
+
TestStepResultStatus["FAILED"] = "FAILED";
|
|
110
|
+
})(TestStepResultStatus || (TestStepResultStatus = {}));
|
|
111
|
+
var KeywordPrefix;
|
|
112
|
+
(function(KeywordPrefix) {
|
|
113
|
+
KeywordPrefix["BULLET"] = "^(\\s*[*+-]\\s*)";
|
|
114
|
+
KeywordPrefix["HEADER"] = "^(#{1,6}\\s)";
|
|
115
|
+
})(KeywordPrefix || (KeywordPrefix = {}));
|
|
116
|
+
StepKeywordType.UNKNOWN, PickleStepType.UNKNOWN, StepKeywordType.CONTEXT, PickleStepType.CONTEXT, StepKeywordType.ACTION, PickleStepType.ACTION, StepKeywordType.OUTCOME, PickleStepType.OUTCOME, StepKeywordType.CONJUNCTION;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/verify-scenarios/verify-scenarios.mts
|
|
119
|
+
function parseSourcesToml(text) {
|
|
120
|
+
const out = [];
|
|
121
|
+
const blocks = text.split(/(?=^\s*\[\[source\]\]\s*$)/m).filter((b) => /^\s*\[\[source\]\]\s*$/m.test(b));
|
|
122
|
+
for (const block of blocks) {
|
|
123
|
+
const body = block.replace(/^\s*\[\[source\]\]\s*$/m, "");
|
|
124
|
+
const fields = {};
|
|
125
|
+
for (const m of body.matchAll(/^\s*([A-Za-z_][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')\s*$/gm)) fields[m[1]] = m[2] ?? m[3] ?? "";
|
|
126
|
+
if (!fields.adapter || !fields.reportPath) continue;
|
|
127
|
+
out.push({
|
|
128
|
+
adapter: fields.adapter,
|
|
129
|
+
command: fields.command,
|
|
130
|
+
reportPath: fields.reportPath
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
if (process.argv[1] && false);
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/manage-scenario-bridge/manage-scenario-bridge.mts
|
|
138
|
+
const CONFIG_REL_PATH = ".agents/sdd/scenario-bridge.toml";
|
|
139
|
+
function configPath(projectPath) {
|
|
140
|
+
return join(projectPath, CONFIG_REL_PATH);
|
|
141
|
+
}
|
|
142
|
+
function listSources(projectPath) {
|
|
143
|
+
const file = configPath(projectPath);
|
|
144
|
+
if (!existsSync(file)) return [];
|
|
145
|
+
try {
|
|
146
|
+
return parseSourcesToml(readFileSync(file, "utf8"));
|
|
147
|
+
} catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function renderBlock(source) {
|
|
152
|
+
return [
|
|
153
|
+
"[[source]]",
|
|
154
|
+
`adapter = "${source.adapter}"`,
|
|
155
|
+
...source.command !== void 0 ? [`command = "${source.command}"`] : [],
|
|
156
|
+
`reportPath = "${source.reportPath}"`
|
|
157
|
+
].join("\n");
|
|
158
|
+
}
|
|
159
|
+
function validateInput(input) {
|
|
160
|
+
if (!input.adapter || !input.reportPath) return {
|
|
161
|
+
ok: false,
|
|
162
|
+
reason: "a source needs both adapter and reportPath"
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
adapter: input.adapter,
|
|
167
|
+
command: input.command,
|
|
168
|
+
reportPath: input.reportPath
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function scaffoldSource(projectPath, input) {
|
|
172
|
+
const valid = validateInput(input);
|
|
173
|
+
if (!valid.ok) return valid;
|
|
174
|
+
const file = configPath(projectPath);
|
|
175
|
+
if (existsSync(file)) return {
|
|
176
|
+
ok: false,
|
|
177
|
+
reason: "scenario-bridge.toml already exists; use add to append a source"
|
|
178
|
+
};
|
|
179
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
180
|
+
writeFileSync(file, `${renderBlock(valid)}\n`);
|
|
181
|
+
const sources = listSources(projectPath);
|
|
182
|
+
return {
|
|
183
|
+
ok: true,
|
|
184
|
+
message: `scaffolded ${file}`,
|
|
185
|
+
sources
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function addSource(projectPath, input) {
|
|
189
|
+
const valid = validateInput(input);
|
|
190
|
+
if (!valid.ok) return valid;
|
|
191
|
+
const file = configPath(projectPath);
|
|
192
|
+
if (!existsSync(file)) return {
|
|
193
|
+
ok: false,
|
|
194
|
+
reason: "scenario-bridge.toml does not exist yet; use scaffold to create it first"
|
|
195
|
+
};
|
|
196
|
+
const existing = readFileSync(file, "utf8");
|
|
197
|
+
const sep = existing.endsWith("\n") ? "" : "\n";
|
|
198
|
+
writeFileSync(file, `${existing}${sep}\n${renderBlock(valid)}\n`);
|
|
199
|
+
const sources = listSources(projectPath);
|
|
200
|
+
return {
|
|
201
|
+
ok: true,
|
|
202
|
+
message: `added a source to ${file}`,
|
|
203
|
+
sources
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function flag(argv, name) {
|
|
207
|
+
const i = argv.indexOf(name);
|
|
208
|
+
return i === -1 ? void 0 : argv[i + 1];
|
|
209
|
+
}
|
|
210
|
+
function inputFromArgv(argv) {
|
|
211
|
+
return {
|
|
212
|
+
adapter: flag(argv, "--adapter"),
|
|
213
|
+
command: flag(argv, "--command"),
|
|
214
|
+
reportPath: flag(argv, "--report-path")
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function main(argv) {
|
|
218
|
+
const projectPath = flag(argv, "--project-path") ?? ".";
|
|
219
|
+
const w = (s) => process.stdout.write(`${s}\n`);
|
|
220
|
+
if (argv.includes("--list")) {
|
|
221
|
+
for (const s of listSources(projectPath)) w(`${s.adapter}${s.command ? ` (${s.command})` : ""} -> ${s.reportPath}`);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
if (argv.includes("--scaffold")) {
|
|
225
|
+
const r = scaffoldSource(projectPath, inputFromArgv(argv));
|
|
226
|
+
w(r.ok ? r.message : `refused: ${r.reason}`);
|
|
227
|
+
return r.ok ? 0 : 1;
|
|
228
|
+
}
|
|
229
|
+
if (argv.includes("--add")) {
|
|
230
|
+
const r = addSource(projectPath, inputFromArgv(argv));
|
|
231
|
+
w(r.ok ? r.message : `refused: ${r.reason}`);
|
|
232
|
+
return r.ok ? 0 : 1;
|
|
233
|
+
}
|
|
234
|
+
w("usage: manage-scenario-bridge --project-path <dir> --list | --scaffold --adapter <a> [--command <c>] --report-path <p> | --add --adapter <a> [--command <c>] --report-path <p>");
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
238
|
+
//#endregion
|
|
239
|
+
export { addSource, configPath, listSources, main, scaffoldSource };
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
The concrete engine for the SDD **spec-anchors** config — the opt-in registry of extra spec anchors
|
|
4
4
|
`discover-specs` scans on top of the three fixed conventions (ADR-0019). A non-user-invocable skill,
|
|
5
|
-
loaded in-session by the `manage` gateway (Housekeeping), carrying a self-contained `.
|
|
5
|
+
loaded in-session by the `manage` gateway (Housekeeping), carrying a self-contained `.mjs` script
|
|
6
6
|
that lists / CRUDs / induces / previews the anchors so users never hand-edit
|
|
7
7
|
`.agents/sdd/spec-anchors.toml`.
|
|
8
8
|
|
|
9
9
|
- **Skill contract:** [`SKILL.md`](./SKILL.md)
|
|
10
|
-
- **Script:** [`scripts/manage-spec-anchors.
|
|
11
|
-
- **Tests:** [`
|
|
10
|
+
- **Script:** [`scripts/manage-spec-anchors.mjs`](./scripts/manage-spec-anchors.mjs)
|
|
11
|
+
- **Tests:** [`src/manage-spec-anchors/manage-spec-anchors.test.mts`](../../src/manage-spec-anchors/manage-spec-anchors.test.mts) (`node:test`)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
node scripts/manage-spec-anchors.mjs --list
|
|
@@ -12,7 +12,7 @@ The concrete engine for the SDD **spec-anchors** config — the opt-in registry
|
|
|
12
12
|
anchors that `discover-specs` scans on top of the three fixed conventions (ADR-0019). It exists so a
|
|
13
13
|
user curates those anchors through a clean interface instead of hand-editing
|
|
14
14
|
`.agents/sdd/spec-anchors.toml`. Loaded **in-session** by the `manage` gateway (Housekeeping group);
|
|
15
|
-
it carries a self-contained `.
|
|
15
|
+
it carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11).
|
|
16
16
|
|
|
17
17
|
## The config it curates
|
|
18
18
|
|