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,350 @@
|
|
|
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/resolve-governances/scripts/resolve-governances.mts
|
|
6
|
+
const ROLE_KEYS = [
|
|
7
|
+
"spec-producer",
|
|
8
|
+
"solution-producer",
|
|
9
|
+
"spec-judge",
|
|
10
|
+
"impl-producer",
|
|
11
|
+
"impl-judge"
|
|
12
|
+
];
|
|
13
|
+
const BAR_KEYS = [
|
|
14
|
+
"oracle-spec",
|
|
15
|
+
"builder-spec",
|
|
16
|
+
"builder-impl",
|
|
17
|
+
"architect-spec",
|
|
18
|
+
"architect-impl"
|
|
19
|
+
];
|
|
20
|
+
const ACTORS = [
|
|
21
|
+
"oracle",
|
|
22
|
+
"builder",
|
|
23
|
+
"architect"
|
|
24
|
+
];
|
|
25
|
+
const GATES = ["spec", "impl"];
|
|
26
|
+
const ROLE_LOADOUT = {
|
|
27
|
+
"spec-producer": { bars: ["oracle-spec", "builder-spec"] },
|
|
28
|
+
"solution-producer": { bars: ["architect-spec"] },
|
|
29
|
+
"spec-judge": { bars: [
|
|
30
|
+
"oracle-spec",
|
|
31
|
+
"builder-spec",
|
|
32
|
+
"architect-spec"
|
|
33
|
+
] },
|
|
34
|
+
"impl-producer": { bars: ["builder-impl", "architect-impl"] },
|
|
35
|
+
"impl-judge": { bars: ["builder-impl", "architect-impl"] }
|
|
36
|
+
};
|
|
37
|
+
const SDD_DEFAULT_AGENT = {
|
|
38
|
+
"spec-producer": null,
|
|
39
|
+
"solution-producer": null,
|
|
40
|
+
"spec-judge": "sdd-spec-judge",
|
|
41
|
+
"impl-producer": null,
|
|
42
|
+
"impl-judge": "sdd-impl-judge"
|
|
43
|
+
};
|
|
44
|
+
function migrateRoles(rolesIn) {
|
|
45
|
+
const roles = { ...rolesIn ?? {} };
|
|
46
|
+
if ("plan-producer" in roles) {
|
|
47
|
+
const legacy = roles["plan-producer"];
|
|
48
|
+
if (!("solution-producer" in roles)) roles["solution-producer"] = legacy;
|
|
49
|
+
delete roles["plan-producer"];
|
|
50
|
+
}
|
|
51
|
+
return roles;
|
|
52
|
+
}
|
|
53
|
+
function migrateGovernances(gIn) {
|
|
54
|
+
const g = gIn ?? {};
|
|
55
|
+
return "oracle" in g || "builder" in g || "architect" in g ? {
|
|
56
|
+
"oracle-spec": g.oracle ?? null,
|
|
57
|
+
"builder-spec": g.builder ?? null,
|
|
58
|
+
"builder-impl": g.builder ?? null,
|
|
59
|
+
"architect-spec": g.architect ?? null,
|
|
60
|
+
"architect-impl": g.architect ?? null
|
|
61
|
+
} : { ...g };
|
|
62
|
+
}
|
|
63
|
+
function migrateSquad(sq) {
|
|
64
|
+
const types = sq["artifact-types"] ?? sq.domains ?? [];
|
|
65
|
+
return {
|
|
66
|
+
"artifact-types": Array.isArray(types) ? types : [],
|
|
67
|
+
roles: migrateRoles(sq.roles),
|
|
68
|
+
governances: migrateGovernances(sq.governances)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function migrateEntry(entry) {
|
|
72
|
+
const squads = Array.isArray(entry.squads) ? entry.squads.map(migrateSquad) : [migrateSquad({
|
|
73
|
+
"artifact-types": entry.domains,
|
|
74
|
+
roles: entry.roles,
|
|
75
|
+
governances: entry.governances
|
|
76
|
+
})];
|
|
77
|
+
return {
|
|
78
|
+
name: entry.name,
|
|
79
|
+
version: entry.version,
|
|
80
|
+
squads
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parseRegistry(text) {
|
|
84
|
+
let raw;
|
|
85
|
+
try {
|
|
86
|
+
raw = JSON.parse(text);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
throw new Error(`registry is not valid JSON: ${e.message}`);
|
|
89
|
+
}
|
|
90
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw["sdd-plugins"])) throw new Error("registry has no sdd-plugins array");
|
|
91
|
+
return { "sdd-plugins": raw["sdd-plugins"].map(migrateEntry) };
|
|
92
|
+
}
|
|
93
|
+
function loadRegistry(root) {
|
|
94
|
+
const path = join(root, ".agents", "universal-plugin.json");
|
|
95
|
+
if (!existsSync(path)) return { "sdd-plugins": [] };
|
|
96
|
+
return parseRegistry(readFileSync(path, "utf8"));
|
|
97
|
+
}
|
|
98
|
+
function parseArtifactTypeMap(text) {
|
|
99
|
+
const out = [];
|
|
100
|
+
for (const raw of text.split("\n")) {
|
|
101
|
+
const line = raw.trim();
|
|
102
|
+
if (!line || line.startsWith("#") || line.startsWith("[")) continue;
|
|
103
|
+
const m = /^(?:"([^"]+)"|'([^']+)'|(\S+))\s*=\s*(?:"([^"]+)"|'([^']+)')\s*(?:#.*)?$/.exec(line);
|
|
104
|
+
if (!m) continue;
|
|
105
|
+
const glob = m[1] ?? m[2] ?? m[3];
|
|
106
|
+
const type = m[4] ?? m[5];
|
|
107
|
+
if (glob && type) out.push({
|
|
108
|
+
glob,
|
|
109
|
+
type
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
function globToRegExp(glob) {
|
|
115
|
+
const re = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, (m) => m.length > 1 ? ".*" : "[^/]*").replace(/\?/g, ".");
|
|
116
|
+
return new RegExp(`^${re}$`);
|
|
117
|
+
}
|
|
118
|
+
function resolveArtifactTypeFromMap(map, path) {
|
|
119
|
+
const matches = map.filter((b) => globToRegExp(b.glob).test(path));
|
|
120
|
+
if (matches.length === 0) return null;
|
|
121
|
+
const literal = (g) => g.replace(/[*?]/g, "").length;
|
|
122
|
+
matches.sort((a, b) => literal(b.glob) - literal(a.glob) || b.glob.length - a.glob.length);
|
|
123
|
+
return matches[0].type;
|
|
124
|
+
}
|
|
125
|
+
function loadArtifactTypeMap(root) {
|
|
126
|
+
const path = join(root, ".agents", "sdd", "artifact-types.toml");
|
|
127
|
+
if (!existsSync(path)) return [];
|
|
128
|
+
return parseArtifactTypeMap(readFileSync(path, "utf8"));
|
|
129
|
+
}
|
|
130
|
+
function frontmatter(text) {
|
|
131
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
132
|
+
return m ? m[1].split("\n") : [];
|
|
133
|
+
}
|
|
134
|
+
const unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
135
|
+
function parseGovernanceFrontmatter(text) {
|
|
136
|
+
const lines = frontmatter(text);
|
|
137
|
+
let inMeta = false;
|
|
138
|
+
let artifactType = null;
|
|
139
|
+
let actor = null;
|
|
140
|
+
let gate = null;
|
|
141
|
+
let compose = "union";
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
if (/^metadata:\s*$/.test(line)) {
|
|
144
|
+
inMeta = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (inMeta && /^\S/.test(line)) inMeta = false;
|
|
148
|
+
if (!inMeta) continue;
|
|
149
|
+
const at = /^\s+artifact-type:\s*(.+)$/.exec(line);
|
|
150
|
+
if (at) artifactType = unquote(at[1]);
|
|
151
|
+
const ac = /^\s+actor:\s*(.+)$/.exec(line);
|
|
152
|
+
if (ac) actor = unquote(ac[1]);
|
|
153
|
+
const ga = /^\s+gate:\s*(.+)$/.exec(line);
|
|
154
|
+
if (ga) gate = unquote(ga[1]);
|
|
155
|
+
const co = /^\s+compose:\s*(.+)$/.exec(line);
|
|
156
|
+
if (co) compose = unquote(co[1]) === "replace" ? "replace" : "union";
|
|
157
|
+
}
|
|
158
|
+
if (!actor || !gate) return null;
|
|
159
|
+
return {
|
|
160
|
+
artifactType,
|
|
161
|
+
actor,
|
|
162
|
+
gate,
|
|
163
|
+
compose
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function collectAnchorGovernances(anchors) {
|
|
167
|
+
const out = [];
|
|
168
|
+
for (const { tier, root } of anchors) {
|
|
169
|
+
const dir = join(root, ".agents", "governances");
|
|
170
|
+
let entries;
|
|
171
|
+
try {
|
|
172
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
173
|
+
} catch {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
for (const e of entries) {
|
|
177
|
+
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
178
|
+
const meta = parseGovernanceFrontmatter(readFileSync(join(dir, e.name), "utf8"));
|
|
179
|
+
if (meta) out.push({
|
|
180
|
+
...meta,
|
|
181
|
+
tier,
|
|
182
|
+
path: join(root, ".agents", "governances", e.name)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
function matchSquad(registry, artifactType) {
|
|
189
|
+
if (!artifactType) return {
|
|
190
|
+
match: null,
|
|
191
|
+
ambiguous: []
|
|
192
|
+
};
|
|
193
|
+
const matches = [];
|
|
194
|
+
for (const p of registry["sdd-plugins"]) for (const sq of p.squads ?? []) if (sq["artifact-types"]?.includes(artifactType)) matches.push({
|
|
195
|
+
plugin: p.name,
|
|
196
|
+
squad: sq
|
|
197
|
+
});
|
|
198
|
+
if (matches.length === 0) return {
|
|
199
|
+
match: null,
|
|
200
|
+
ambiguous: []
|
|
201
|
+
};
|
|
202
|
+
if (matches.length === 1) return {
|
|
203
|
+
match: matches[0],
|
|
204
|
+
ambiguous: []
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
match: null,
|
|
208
|
+
ambiguous: [...new Set(matches.map((m) => m.plugin))]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function matchBar(artifactType, actor, gate, ctx) {
|
|
212
|
+
const key = `${actor}-${gate}`;
|
|
213
|
+
const bucket = (tier) => ctx.projectGovs.filter((c) => c.tier === tier && c.actor === actor && c.gate === gate).filter((c) => c.artifactType === artifactType || c.artifactType === null).map((c) => c.path);
|
|
214
|
+
const pluginBar = ctx.match?.squad.governances?.[key];
|
|
215
|
+
return {
|
|
216
|
+
key,
|
|
217
|
+
candidates: {
|
|
218
|
+
project: bucket("project"),
|
|
219
|
+
"project-root": bucket("project-root"),
|
|
220
|
+
plugin: ctx.match && pluginBar ? `${ctx.match.plugin}:${pluginBar}` : null,
|
|
221
|
+
sdd: `sdd:${key}-governance`
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function resolveAgent(role, match) {
|
|
226
|
+
if (!match) return {
|
|
227
|
+
source: "sdd",
|
|
228
|
+
ref: SDD_DEFAULT_AGENT[role]
|
|
229
|
+
};
|
|
230
|
+
if (role in match.squad.roles) {
|
|
231
|
+
const named = match.squad.roles[role];
|
|
232
|
+
if (named) return {
|
|
233
|
+
source: "plugin",
|
|
234
|
+
ref: named
|
|
235
|
+
};
|
|
236
|
+
return {
|
|
237
|
+
source: "sdd",
|
|
238
|
+
ref: SDD_DEFAULT_AGENT[role]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
source: "plugin",
|
|
243
|
+
ref: `${match.plugin}-${role}`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function resolveRole(role, artifactType, ctx) {
|
|
247
|
+
const bars = ROLE_LOADOUT[role].bars.map((b) => {
|
|
248
|
+
const [actor, gate] = b.split("-");
|
|
249
|
+
return matchBar(artifactType, actor, gate, ctx);
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
role,
|
|
253
|
+
agent: resolveAgent(role, ctx.match),
|
|
254
|
+
bars
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function buildLoadPlan(artifactType, registry, projectGovs) {
|
|
258
|
+
const { match, ambiguous } = matchSquad(registry, artifactType);
|
|
259
|
+
if (ambiguous.length > 0) return {
|
|
260
|
+
artifactType,
|
|
261
|
+
status: "needs-input",
|
|
262
|
+
plugin: null,
|
|
263
|
+
ambiguous,
|
|
264
|
+
roles: []
|
|
265
|
+
};
|
|
266
|
+
const ctx = {
|
|
267
|
+
match,
|
|
268
|
+
projectGovs
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
artifactType,
|
|
272
|
+
status: "complete",
|
|
273
|
+
plugin: match?.plugin ?? null,
|
|
274
|
+
ambiguous: [],
|
|
275
|
+
roles: ROLE_KEYS.map((r) => resolveRole(r, artifactType, ctx))
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function validateRegistry(registry) {
|
|
279
|
+
const v = [];
|
|
280
|
+
const typeOwners = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const entry of registry["sdd-plugins"]) {
|
|
282
|
+
const where = entry.name || "<unnamed plugin>";
|
|
283
|
+
if (!entry.name) v.push("an sdd-plugins entry has no name");
|
|
284
|
+
if (!Array.isArray(entry.squads)) {
|
|
285
|
+
v.push(`${where}: squads is not an array`);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
289
|
+
for (const sq of entry.squads) {
|
|
290
|
+
if (!Array.isArray(sq["artifact-types"])) v.push(`${where}: a squad's artifact-types is not an array`);
|
|
291
|
+
for (const r of Object.keys(sq.roles ?? {})) if (!ROLE_KEYS.includes(r)) v.push(`${where}: unknown role key "${r}"`);
|
|
292
|
+
for (const k of Object.keys(sq.governances ?? {})) if (!BAR_KEYS.includes(k)) v.push(`${where}: unknown governance key "${k}"`);
|
|
293
|
+
for (const t of sq["artifact-types"] ?? []) {
|
|
294
|
+
if (seenTypes.has(t)) v.push(`${where}: artifact-type "${t}" appears in more than one squad`);
|
|
295
|
+
seenTypes.add(t);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const t of seenTypes) typeOwners.set(t, [...typeOwners.get(t) ?? [], where]);
|
|
299
|
+
}
|
|
300
|
+
for (const [t, owners] of typeOwners) if (owners.length > 1) v.push(`artifact-type "${t}" is claimed by ${owners.length} plugins (${owners.join(", ")}) — ambiguous`);
|
|
301
|
+
return v;
|
|
302
|
+
}
|
|
303
|
+
function main(argv) {
|
|
304
|
+
const root = argv.includes("--root") ? argv[argv.indexOf("--root") + 1] : ".";
|
|
305
|
+
const explicitType = argv.includes("--artifact-type") ? argv[argv.indexOf("--artifact-type") + 1] : null;
|
|
306
|
+
const pathArg = argv.includes("--path") ? argv[argv.indexOf("--path") + 1] : null;
|
|
307
|
+
const projectArg = argv.includes("--project") ? argv[argv.indexOf("--project") + 1] : root;
|
|
308
|
+
const projectRootArg = argv.includes("--project-root") ? argv[argv.indexOf("--project-root") + 1] : null;
|
|
309
|
+
const anchors = [{
|
|
310
|
+
tier: "project",
|
|
311
|
+
root: projectArg
|
|
312
|
+
}];
|
|
313
|
+
if (projectRootArg) anchors.push({
|
|
314
|
+
tier: "project-root",
|
|
315
|
+
root: projectRootArg
|
|
316
|
+
});
|
|
317
|
+
let artifactType = explicitType;
|
|
318
|
+
if (!artifactType && pathArg) artifactType = resolveArtifactTypeFromMap(loadArtifactTypeMap(root), pathArg);
|
|
319
|
+
let registry;
|
|
320
|
+
try {
|
|
321
|
+
registry = loadRegistry(root);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error(`✗ ${e.message}`);
|
|
324
|
+
return 1;
|
|
325
|
+
}
|
|
326
|
+
if (!artifactType && pathArg) {
|
|
327
|
+
process.stdout.write(`${JSON.stringify({
|
|
328
|
+
path: pathArg,
|
|
329
|
+
artifactType: null,
|
|
330
|
+
note: "no tiebreaker match — classify by convention"
|
|
331
|
+
}, null, 2)}\n`);
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
if (!artifactType) {
|
|
335
|
+
const violations = validateRegistry(registry);
|
|
336
|
+
if (violations.length) {
|
|
337
|
+
for (const line of violations) console.error(`✗ ${line}`);
|
|
338
|
+
return 1;
|
|
339
|
+
}
|
|
340
|
+
process.stdout.write("governance registry OK\n");
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
const plan = buildLoadPlan(artifactType, registry, collectAnchorGovernances(anchors));
|
|
344
|
+
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
345
|
+
if (plan.status === "needs-input") console.error(`needs-input: artifact-type "${artifactType}" is claimed by ${plan.ambiguous.join(", ")}`);
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
349
|
+
//#endregion
|
|
350
|
+
export { ACTORS, BAR_KEYS, GATES, ROLE_KEYS, ROLE_LOADOUT, SDD_DEFAULT_AGENT, buildLoadPlan, collectAnchorGovernances, loadArtifactTypeMap, loadRegistry, main, matchBar, matchSquad, migrateEntry, parseArtifactTypeMap, parseGovernanceFrontmatter, parseRegistry, resolveAgent, resolveArtifactTypeFromMap, resolveRole, validateRegistry };
|
|
@@ -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
|
|
@@ -49,7 +49,7 @@ export const ROLE_LOADOUT: Record<RoleKey, { bars: BarKey[] }> = {
|
|
|
49
49
|
|
|
50
50
|
// The SDD-default agent per role. A null ref means the conductor runs the role
|
|
51
51
|
// INLINE in the main session (spec/solution-producer) or via a generic spawned
|
|
52
|
-
// builder (impl-producer); both are recorded produced-by sdd:automaton. The two
|
|
52
|
+
// builder (impl-producer); both are recorded produced-by sdd:sdd-automaton. The two
|
|
53
53
|
// judges are spawned cold by name.
|
|
54
54
|
export const SDD_DEFAULT_AGENT: Record<RoleKey, string | null> = {
|
|
55
55
|
'spec-producer': null,
|
|
@@ -18,7 +18,7 @@ It carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps con
|
|
|
18
18
|
## Run the resolution
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
node "<skill>/scripts/resolve-tracking.
|
|
21
|
+
node "<skill>/scripts/resolve-tracking.mjs" --root . --path <repo-relative-path> [--artifact-type <type>] [--explicit tracked|ignored]
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
- `--root` is the repo root (default `.`) — where `.agents/sdd/.sddignore` is read from.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
//#region skills/resolve-tracking/scripts/resolve-tracking.mts
|
|
5
|
+
function globToRegExpBody(glob) {
|
|
6
|
+
return glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, (m) => m.length > 1 ? ".*" : "[^/]*").replace(/\?/g, "[^/]");
|
|
7
|
+
}
|
|
8
|
+
function parseIgnoreLine(raw) {
|
|
9
|
+
const trimmed = raw.replace(/\r$/, "").trim();
|
|
10
|
+
if (!trimmed || trimmed.startsWith("#")) return "skip";
|
|
11
|
+
let body = trimmed;
|
|
12
|
+
const negated = body.startsWith("!");
|
|
13
|
+
if (negated) body = body.slice(1);
|
|
14
|
+
if (body.endsWith("/")) body = body.slice(0, -1);
|
|
15
|
+
let anchored = false;
|
|
16
|
+
if (body.startsWith("/")) {
|
|
17
|
+
anchored = true;
|
|
18
|
+
body = body.slice(1);
|
|
19
|
+
} else if (body.includes("/")) anchored = true;
|
|
20
|
+
if (body === "") return null;
|
|
21
|
+
const prefix = anchored ? "^" : "^(?:.*/)?";
|
|
22
|
+
const core = globToRegExpBody(body);
|
|
23
|
+
const suffix = "(?:/.*)?$";
|
|
24
|
+
try {
|
|
25
|
+
return {
|
|
26
|
+
negated,
|
|
27
|
+
regex: new RegExp(prefix + core + suffix),
|
|
28
|
+
source: trimmed
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function parseIgnoreFile(text) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const raw of text.split("\n")) {
|
|
37
|
+
const rule = parseIgnoreLine(raw);
|
|
38
|
+
if (rule && rule !== "skip") out.push(rule);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function loadIgnoreRules(root) {
|
|
43
|
+
const path = join(root, ".agents", "sdd", ".sddignore");
|
|
44
|
+
if (!existsSync(path)) return [];
|
|
45
|
+
return parseIgnoreFile(readFileSync(path, "utf8"));
|
|
46
|
+
}
|
|
47
|
+
function resolveFromIgnore(rules, path) {
|
|
48
|
+
let value = null;
|
|
49
|
+
for (const rule of rules) if (rule.regex.test(path)) value = rule.negated ? "tracked" : "ignored";
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
const KIND_DEFAULT_GLOBS = {
|
|
53
|
+
skill: {
|
|
54
|
+
ignored: [".agents/skills/**"],
|
|
55
|
+
tracked: [
|
|
56
|
+
"skills/**",
|
|
57
|
+
"plugins/*/skills/**",
|
|
58
|
+
"packages/*/skills/**"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
subagent: {
|
|
62
|
+
ignored: [".agents/agents/**"],
|
|
63
|
+
tracked: ["plugins/*/agents/**", "packages/*/agents/**"]
|
|
64
|
+
},
|
|
65
|
+
command: {
|
|
66
|
+
ignored: [".agents/commands/**"],
|
|
67
|
+
tracked: ["plugins/*/commands/**", "packages/*/commands/**"]
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function globMatch(glob, path) {
|
|
71
|
+
return new RegExp(`^${globToRegExpBody(glob)}$`).test(path);
|
|
72
|
+
}
|
|
73
|
+
function resolveKindDefault(artifactType, path) {
|
|
74
|
+
if (!artifactType) return null;
|
|
75
|
+
const kind = KIND_DEFAULT_GLOBS[artifactType];
|
|
76
|
+
if (!kind) return null;
|
|
77
|
+
if (kind.ignored.some((g) => globMatch(g, path))) return "ignored";
|
|
78
|
+
if (kind.tracked.some((g) => globMatch(g, path))) return "tracked";
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function resolveTracking(input) {
|
|
82
|
+
if (input.explicit) return {
|
|
83
|
+
value: input.explicit,
|
|
84
|
+
reason: "explicit override"
|
|
85
|
+
};
|
|
86
|
+
const ignoreMatch = resolveFromIgnore(loadIgnoreRules(input.root), input.path);
|
|
87
|
+
if (ignoreMatch) return {
|
|
88
|
+
value: ignoreMatch,
|
|
89
|
+
reason: ".sddignore"
|
|
90
|
+
};
|
|
91
|
+
const kindDefault = resolveKindDefault(input.artifactType, input.path);
|
|
92
|
+
if (kindDefault) return {
|
|
93
|
+
value: kindDefault,
|
|
94
|
+
reason: `kind-default (${input.artifactType})`
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
value: "tracked",
|
|
98
|
+
reason: "fail-closed (no signal)"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function validateIgnoreFile(root) {
|
|
102
|
+
const path = join(root, ".agents", "sdd", ".sddignore");
|
|
103
|
+
if (!existsSync(path)) return {
|
|
104
|
+
ok: true,
|
|
105
|
+
message: ".sddignore OK (absent, fine)"
|
|
106
|
+
};
|
|
107
|
+
const text = readFileSync(path, "utf8");
|
|
108
|
+
const notes = [];
|
|
109
|
+
text.split("\n").forEach((raw, i) => {
|
|
110
|
+
if (parseIgnoreLine(raw) === null) notes.push(`line ${i + 1}: not a valid gitignore pattern: ${raw.trim()}`);
|
|
111
|
+
});
|
|
112
|
+
if (notes.length > 0) return {
|
|
113
|
+
ok: false,
|
|
114
|
+
message: notes.join("\n")
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
message: `.sddignore OK (${parseIgnoreFile(text).length} rule(s))`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function parseArgs(argv) {
|
|
122
|
+
const out = {};
|
|
123
|
+
for (let i = 0; i < argv.length; i++) if (argv[i].startsWith("--")) {
|
|
124
|
+
out[argv[i].slice(2)] = argv[i + 1];
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
function main(argv) {
|
|
130
|
+
const args = parseArgs(argv);
|
|
131
|
+
const root = args.root ?? ".";
|
|
132
|
+
if (!args.path) {
|
|
133
|
+
const result = validateIgnoreFile(root);
|
|
134
|
+
process.stdout.write(`${result.message}\n`);
|
|
135
|
+
if (!result.ok) process.exitCode = 1;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const explicit = args.explicit === "tracked" || args.explicit === "ignored" ? args.explicit : void 0;
|
|
139
|
+
const result = resolveTracking({
|
|
140
|
+
root,
|
|
141
|
+
path: args.path,
|
|
142
|
+
artifactType: args["artifact-type"],
|
|
143
|
+
explicit
|
|
144
|
+
});
|
|
145
|
+
process.stdout.write(`${result.value}\n`);
|
|
146
|
+
process.stdout.write(`reason: ${result.reason}\n`);
|
|
147
|
+
}
|
|
148
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) main(process.argv.slice(2));
|
|
149
|
+
//#endregion
|
|
150
|
+
export { loadIgnoreRules, main, parseIgnoreFile, parseIgnoreLine, resolveFromIgnore, resolveKindDefault, resolveTracking, validateIgnoreFile };
|
|
@@ -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
|
|
@@ -13,7 +13,7 @@ homes. It is **internal** — reached through `start-mission`, never a user entr
|
|
|
13
13
|
`status: draft`; it authors no node's `## Use Cases`/`.feature`, renders no gate verdict, and freezes nothing.
|
|
14
14
|
|
|
15
15
|
**Load `sdd:spec-structure-governance`** for the layout law it applies: the node taxonomy (three
|
|
16
|
-
spec-types, the concept axis), the two-level depth cap, screaming architecture and the
|
|
16
|
+
spec-types, the concept axis), the two-level depth cap on capability nodes, screaming architecture and the
|
|
17
17
|
non-capability folders, root-files-not-folders, and the colocate-or-hoist test. This skill does not
|
|
18
18
|
restate that law — it **runs** it, and owns only what is genuinely its own: the **strategy menu**,
|
|
19
19
|
the **shared envelope**, and the seven-step procedure below.
|
|
@@ -138,7 +138,7 @@ Then the chosen strategy's **top-level skeleton** of **stub node READMEs**, each
|
|
|
138
138
|
`.feature`);
|
|
139
139
|
- an **index / rule / structural grouping** → **descriptive** (no marker).
|
|
140
140
|
|
|
141
|
-
The skeleton obeys the **two-level depth cap** under **every** strategy: a node is
|
|
141
|
+
The skeleton obeys the **two-level depth cap** under **every** strategy: a capability node is
|
|
142
142
|
`<capability>/<unit>` and **never three deep** — a sub-grouping inside a capability is a `concept:` tag
|
|
143
143
|
recovered by the by-concept index, not a third folder level. Under **mirror-source**, mirror **only to the
|
|
144
144
|
unit boundary**: a folder with a testable surface becomes one behavioral leaf that owns its subtree; create
|
package/skills/sdd/SKILL.md
CHANGED
|
@@ -85,7 +85,7 @@ The **fresh spawn per mission is deliberate** — each automaton's context dies
|
|
|
85
85
|
- **Escape.** A task that is **not a CR** escapes: create no draft, invoke no gate, and **write no record** (a non-CR is not SDD's to track; a spec-prose-only change is already in git). Two independent triggers: **no suite-relevant behavior**, or an **ignored** touched artifact. Escaping does not mean stopping — if the artifact-type has a producer with an escaped-request entry point (e.g. `define-skill` for `skill`), invoke it directly to do the actual work; only state "leaving the lifecycle" and stop when no such producer exists. For tracking, run the `resolve-tracking` skill (`intake/resolve-tracking`'s concrete engine) per touched artifact:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
node "<resolve-tracking skill>/scripts/resolve-tracking.
|
|
88
|
+
node "<resolve-tracking skill>/scripts/resolve-tracking.mjs" --root . --path <repo-relative-path> [--artifact-type <type>] [--explicit tracked|ignored]
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
`--explicit` carries a tracking statement the requester made directly in the prompt/CR, when present. Pass `--artifact-type` when convention already makes it obvious (`skill` under `skills/`, `subagent` under `agents/`, …) — a full `resolve-governances` classification is not needed just to gate the escape check. An `ignored` result escapes the same as no-suite-relevant-behavior; a `tracked` result (including the fail-closed default) proceeds toward CR classification. This is a **read**, the same category as counting strategy lines or scanning specs — the thin-classifier rule still holds. Recognition is the **grill + impact analysis**, not a gateway classifier — the grill may carve a CR out of the tracked parts of a mixed request and escape the ignored ones. Ambiguity defaults *into* the lifecycle and is decided during explore.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Non-user-invocable SDD skill holding the **default solution-producer procedure**: how to record a unit's `<unit>.solution.md` (the chosen approach + rejected alternatives) for a domain no plugin covers — **only** when the unit carries durable design rationale.
|
|
4
4
|
|
|
5
|
-
Loaded via the harness (`Skill`) by the **conductor** (the main session) when it runs the solution-producer role from the SDD default — the conductor authors **inline** in its own warm context (recorded `produced-by.solution-producer: sdd:automaton`) rather than spawning a producer agent. Relocates the *functional-spec* half of the former `plan-producer` role to a per-unit, optional, ungated facet; the task-DAG half is now the conductor's transient execution `.plan.md` `todos`, not this role's output.
|
|
5
|
+
Loaded via the harness (`Skill`) by the **conductor** (the main session) when it runs the solution-producer role from the SDD default — the conductor authors **inline** in its own warm context (recorded `produced-by.solution-producer: sdd:sdd-automaton`) rather than spawning a producer agent. Relocates the *functional-spec* half of the former `plan-producer` role to a per-unit, optional, ungated facet; the task-DAG half is now the conductor's transient execution `.plan.md` `todos`, not this role's output.
|
|
6
6
|
|
|
7
7
|
The solution is the unit's **third facet** (spec / suite / solution). It is **optional** (most units have none), **boundary-aligned** (maps to the design decision, not one entry per scenario), and **ungated / unfrozen** — no judge grades it and the spec gate does not see it; the implementation's frozen-scenario result validates it transitively.
|
|
8
8
|
|
|
@@ -6,7 +6,7 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
# Solution-Producer Governance — the default solution-recording procedure
|
|
8
8
|
|
|
9
|
-
The procedure the **conductor** follows when it runs the **solution-producer** role from the SDD default — no plugin covers the domain and no model-tuned producer agent is named for the slot, so the conductor **loads this governance and authors inline** in its own warm context (recorded `produced-by.solution-producer: sdd:automaton`). This is the relocation of the former `plan-producer` role's *functional-spec* half: the solution is the chosen approach + rejected alternatives, now recorded **per unit** as `<unit>.solution.md` rather than as a `plan.md`. The task DAG that `plan-producer` also wrote is **not** this role's output — it is the conductor's transient execution `.plan.md` `todos`.
|
|
9
|
+
The procedure the **conductor** follows when it runs the **solution-producer** role from the SDD default — no plugin covers the domain and no model-tuned producer agent is named for the slot, so the conductor **loads this governance and authors inline** in its own warm context (recorded `produced-by.solution-producer: sdd:sdd-automaton`). This is the relocation of the former `plan-producer` role's *functional-spec* half: the solution is the chosen approach + rejected alternatives, now recorded **per unit** as `<unit>.solution.md` rather than as a `plan.md`. The task DAG that `plan-producer` also wrote is **not** this role's output — it is the conductor's transient execution `.plan.md` `todos`.
|
|
10
10
|
|
|
11
11
|
The solution is the unit's **third facet** (spec = *what*, suite = *proof*, solution = *why this shape*). It is **optional** and **ungated**: it gets no judge of its own, stays out of the spec-judge's view, and is never frozen. The implementation's frozen-scenario result validates it transitively.
|
|
12
12
|
|
|
@@ -26,7 +26,7 @@ self-clear-vs-escalate bar and the four-C floor are the conductor's autonomy bar
|
|
|
26
26
|
Run before any verdict work; structural validity **fails closed**, availability only **flags**:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
node "<skill>/scripts/check-spec-state.
|
|
29
|
+
node "<skill>/scripts/check-spec-state.mjs" [--root <specs-dir>]
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Exit `0` = legal; exit `1` prints each violation as `✗ <slug>: <reason>` — fix before continuing.
|
|
@@ -41,7 +41,7 @@ the CR touches any `.feature`, run it here **fail-closed, before spawning the co
|
|
|
41
41
|
the CR's touched files:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
node "<skill>/scripts/check-suite.
|
|
44
|
+
node "<skill>/scripts/check-suite.mjs" --files <the CR's touched .feature files>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Exit `0` = form clean; exit `1` prints each `✗ <file>: <reason>` — **advance nothing and do not
|
|
@@ -67,7 +67,7 @@ or a nested `<project-path>/.agents/spec/` — so a touched `.md` outside the sp
|
|
|
67
67
|
swept):
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
node "<skill>/scripts/check-spec-state.
|
|
70
|
+
node "<skill>/scripts/check-spec-state.mjs" --files <the CR's touched .md files> [--base <baseref>]
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Exit `0` prints each `⚠ <file>: introduces unresolved reference ...` finding (if any) plus the
|
|
@@ -158,7 +158,7 @@ change is purely additive with no judge round; a **narrowing/rewriting** edit (a
|
|
|
158
158
|
scenario) unfreezes its file and fires **Clearance** once the narrowing is confirmed semantically.
|
|
159
159
|
The edit-class classification itself — additive / no-content-change / narrowing / mixed — comes from
|
|
160
160
|
`scripts/classify-edit-class.mts` (`--files <paths> [--base <ref>]`): a **structural** per-named-`Scenario`
|
|
161
|
-
diff via the pinned `gherkin-cli@0.
|
|
161
|
+
diff via the pinned `gherkin-cli@0.2.1 diff`, plus git rename detection for a pure `git mv`, **never a raw
|
|
162
162
|
line diff** (a step orphaned off a frozen scenario onto a new adjacent scenario shows no `-` line and
|
|
163
163
|
would read as additive to a line-diff; the structural diff correctly reports the losing scenario as
|
|
164
164
|
`modified`). It only classifies — additive / no-content-change self-clear, narrowing / mixed take the
|
|
@@ -166,7 +166,7 @@ Clearance path above; it fires no verdict itself. Run it over the CR's touched f
|
|
|
166
166
|
to read the edit class before applying the verb:
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
|
-
node "<skill>/scripts/classify-edit-class.
|
|
169
|
+
node "<skill>/scripts/classify-edit-class.mjs" --files <the CR's touched .feature files> [--base <baseref>]
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
A `narrowing`/`mixed` result on a still-`@frozen` file routes to **Clearance** (escalated unless the CR
|