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,352 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compiled by `universal-plugin plugin build` from src/resolve-governances/resolve-governances.mts. Edit the source, not this file.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/resolve-governances/scripts/resolve-governances.mjs","commands":{}};
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
//#region src/resolve-governances/resolve-governances.mts
|
|
8
|
+
const ROLE_KEYS = [
|
|
9
|
+
"spec-producer",
|
|
10
|
+
"solution-producer",
|
|
11
|
+
"spec-judge",
|
|
12
|
+
"impl-producer",
|
|
13
|
+
"impl-judge"
|
|
14
|
+
];
|
|
15
|
+
const BAR_KEYS = [
|
|
16
|
+
"oracle-spec",
|
|
17
|
+
"builder-spec",
|
|
18
|
+
"builder-impl",
|
|
19
|
+
"architect-spec",
|
|
20
|
+
"architect-impl"
|
|
21
|
+
];
|
|
22
|
+
const ACTORS = [
|
|
23
|
+
"oracle",
|
|
24
|
+
"builder",
|
|
25
|
+
"architect"
|
|
26
|
+
];
|
|
27
|
+
const GATES = ["spec", "impl"];
|
|
28
|
+
const ROLE_LOADOUT = {
|
|
29
|
+
"spec-producer": { bars: ["oracle-spec", "builder-spec"] },
|
|
30
|
+
"solution-producer": { bars: ["architect-spec"] },
|
|
31
|
+
"spec-judge": { bars: [
|
|
32
|
+
"oracle-spec",
|
|
33
|
+
"builder-spec",
|
|
34
|
+
"architect-spec"
|
|
35
|
+
] },
|
|
36
|
+
"impl-producer": { bars: ["builder-impl", "architect-impl"] },
|
|
37
|
+
"impl-judge": { bars: ["builder-impl", "architect-impl"] }
|
|
38
|
+
};
|
|
39
|
+
const SDD_DEFAULT_AGENT = {
|
|
40
|
+
"spec-producer": null,
|
|
41
|
+
"solution-producer": null,
|
|
42
|
+
"spec-judge": "sdd-spec-judge",
|
|
43
|
+
"impl-producer": null,
|
|
44
|
+
"impl-judge": "sdd-impl-judge"
|
|
45
|
+
};
|
|
46
|
+
function migrateRoles(rolesIn) {
|
|
47
|
+
const roles = { ...rolesIn ?? {} };
|
|
48
|
+
if ("plan-producer" in roles) {
|
|
49
|
+
const legacy = roles["plan-producer"];
|
|
50
|
+
if (!("solution-producer" in roles)) roles["solution-producer"] = legacy;
|
|
51
|
+
delete roles["plan-producer"];
|
|
52
|
+
}
|
|
53
|
+
return roles;
|
|
54
|
+
}
|
|
55
|
+
function migrateGovernances(gIn) {
|
|
56
|
+
const g = gIn ?? {};
|
|
57
|
+
return "oracle" in g || "builder" in g || "architect" in g ? {
|
|
58
|
+
"oracle-spec": g.oracle ?? null,
|
|
59
|
+
"builder-spec": g.builder ?? null,
|
|
60
|
+
"builder-impl": g.builder ?? null,
|
|
61
|
+
"architect-spec": g.architect ?? null,
|
|
62
|
+
"architect-impl": g.architect ?? null
|
|
63
|
+
} : { ...g };
|
|
64
|
+
}
|
|
65
|
+
function migrateSquad(sq) {
|
|
66
|
+
const types = sq["artifact-types"] ?? sq.domains ?? [];
|
|
67
|
+
return {
|
|
68
|
+
"artifact-types": Array.isArray(types) ? types : [],
|
|
69
|
+
roles: migrateRoles(sq.roles),
|
|
70
|
+
governances: migrateGovernances(sq.governances)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function migrateEntry(entry) {
|
|
74
|
+
const squads = Array.isArray(entry.squads) ? entry.squads.map(migrateSquad) : [migrateSquad({
|
|
75
|
+
"artifact-types": entry.domains,
|
|
76
|
+
roles: entry.roles,
|
|
77
|
+
governances: entry.governances
|
|
78
|
+
})];
|
|
79
|
+
return {
|
|
80
|
+
name: entry.name,
|
|
81
|
+
version: entry.version,
|
|
82
|
+
squads
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function parseRegistry(text) {
|
|
86
|
+
let raw;
|
|
87
|
+
try {
|
|
88
|
+
raw = JSON.parse(text);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
throw new Error(`registry is not valid JSON: ${e.message}`);
|
|
91
|
+
}
|
|
92
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw["sdd-plugins"])) throw new Error("registry has no sdd-plugins array");
|
|
93
|
+
return { "sdd-plugins": raw["sdd-plugins"].map(migrateEntry) };
|
|
94
|
+
}
|
|
95
|
+
function loadRegistry(root) {
|
|
96
|
+
const path = join(root, ".agents", "universal-plugin.json");
|
|
97
|
+
if (!existsSync(path)) return { "sdd-plugins": [] };
|
|
98
|
+
return parseRegistry(readFileSync(path, "utf8"));
|
|
99
|
+
}
|
|
100
|
+
function parseArtifactTypeMap(text) {
|
|
101
|
+
const out = [];
|
|
102
|
+
for (const raw of text.split("\n")) {
|
|
103
|
+
const line = raw.trim();
|
|
104
|
+
if (!line || line.startsWith("#") || line.startsWith("[")) continue;
|
|
105
|
+
const m = /^(?:"([^"]+)"|'([^']+)'|(\S+))\s*=\s*(?:"([^"]+)"|'([^']+)')\s*(?:#.*)?$/.exec(line);
|
|
106
|
+
if (!m) continue;
|
|
107
|
+
const glob = m[1] ?? m[2] ?? m[3];
|
|
108
|
+
const type = m[4] ?? m[5];
|
|
109
|
+
if (glob && type) out.push({
|
|
110
|
+
glob,
|
|
111
|
+
type
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function globToRegExp(glob) {
|
|
117
|
+
const re = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, (m) => m.length > 1 ? ".*" : "[^/]*").replace(/\?/g, ".");
|
|
118
|
+
return new RegExp(`^${re}$`);
|
|
119
|
+
}
|
|
120
|
+
function resolveArtifactTypeFromMap(map, path) {
|
|
121
|
+
const matches = map.filter((b) => globToRegExp(b.glob).test(path));
|
|
122
|
+
if (matches.length === 0) return null;
|
|
123
|
+
const literal = (g) => g.replace(/[*?]/g, "").length;
|
|
124
|
+
matches.sort((a, b) => literal(b.glob) - literal(a.glob) || b.glob.length - a.glob.length);
|
|
125
|
+
return matches[0].type;
|
|
126
|
+
}
|
|
127
|
+
function loadArtifactTypeMap(root) {
|
|
128
|
+
const path = join(root, ".agents", "sdd", "artifact-types.toml");
|
|
129
|
+
if (!existsSync(path)) return [];
|
|
130
|
+
return parseArtifactTypeMap(readFileSync(path, "utf8"));
|
|
131
|
+
}
|
|
132
|
+
function frontmatter(text) {
|
|
133
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
134
|
+
return m ? m[1].split("\n") : [];
|
|
135
|
+
}
|
|
136
|
+
const unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
137
|
+
function parseGovernanceFrontmatter(text) {
|
|
138
|
+
const lines = frontmatter(text);
|
|
139
|
+
let inMeta = false;
|
|
140
|
+
let artifactType = null;
|
|
141
|
+
let actor = null;
|
|
142
|
+
let gate = null;
|
|
143
|
+
let compose = "union";
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
if (/^metadata:\s*$/.test(line)) {
|
|
146
|
+
inMeta = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (inMeta && /^\S/.test(line)) inMeta = false;
|
|
150
|
+
if (!inMeta) continue;
|
|
151
|
+
const at = /^\s+artifact-type:\s*(.+)$/.exec(line);
|
|
152
|
+
if (at) artifactType = unquote(at[1]);
|
|
153
|
+
const ac = /^\s+actor:\s*(.+)$/.exec(line);
|
|
154
|
+
if (ac) actor = unquote(ac[1]);
|
|
155
|
+
const ga = /^\s+gate:\s*(.+)$/.exec(line);
|
|
156
|
+
if (ga) gate = unquote(ga[1]);
|
|
157
|
+
const co = /^\s+compose:\s*(.+)$/.exec(line);
|
|
158
|
+
if (co) compose = unquote(co[1]) === "replace" ? "replace" : "union";
|
|
159
|
+
}
|
|
160
|
+
if (!actor || !gate) return null;
|
|
161
|
+
return {
|
|
162
|
+
artifactType,
|
|
163
|
+
actor,
|
|
164
|
+
gate,
|
|
165
|
+
compose
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function collectAnchorGovernances(anchors) {
|
|
169
|
+
const out = [];
|
|
170
|
+
for (const { tier, root } of anchors) {
|
|
171
|
+
const dir = join(root, ".agents", "governances");
|
|
172
|
+
let entries;
|
|
173
|
+
try {
|
|
174
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
175
|
+
} catch {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
for (const e of entries) {
|
|
179
|
+
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
180
|
+
const meta = parseGovernanceFrontmatter(readFileSync(join(dir, e.name), "utf8"));
|
|
181
|
+
if (meta) out.push({
|
|
182
|
+
...meta,
|
|
183
|
+
tier,
|
|
184
|
+
path: join(root, ".agents", "governances", e.name)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
function matchSquad(registry, artifactType) {
|
|
191
|
+
if (!artifactType) return {
|
|
192
|
+
match: null,
|
|
193
|
+
ambiguous: []
|
|
194
|
+
};
|
|
195
|
+
const matches = [];
|
|
196
|
+
for (const p of registry["sdd-plugins"]) for (const sq of p.squads ?? []) if (sq["artifact-types"]?.includes(artifactType)) matches.push({
|
|
197
|
+
plugin: p.name,
|
|
198
|
+
squad: sq
|
|
199
|
+
});
|
|
200
|
+
if (matches.length === 0) return {
|
|
201
|
+
match: null,
|
|
202
|
+
ambiguous: []
|
|
203
|
+
};
|
|
204
|
+
if (matches.length === 1) return {
|
|
205
|
+
match: matches[0],
|
|
206
|
+
ambiguous: []
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
match: null,
|
|
210
|
+
ambiguous: [...new Set(matches.map((m) => m.plugin))]
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function matchBar(artifactType, actor, gate, ctx) {
|
|
214
|
+
const key = `${actor}-${gate}`;
|
|
215
|
+
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);
|
|
216
|
+
const pluginBar = ctx.match?.squad.governances?.[key];
|
|
217
|
+
return {
|
|
218
|
+
key,
|
|
219
|
+
candidates: {
|
|
220
|
+
project: bucket("project"),
|
|
221
|
+
"project-root": bucket("project-root"),
|
|
222
|
+
plugin: ctx.match && pluginBar ? `${ctx.match.plugin}:${pluginBar}` : null,
|
|
223
|
+
sdd: `sdd:${key}-governance`
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function resolveAgent(role, match) {
|
|
228
|
+
if (!match) return {
|
|
229
|
+
source: "sdd",
|
|
230
|
+
ref: SDD_DEFAULT_AGENT[role]
|
|
231
|
+
};
|
|
232
|
+
if (role in match.squad.roles) {
|
|
233
|
+
const named = match.squad.roles[role];
|
|
234
|
+
if (named) return {
|
|
235
|
+
source: "plugin",
|
|
236
|
+
ref: named
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
source: "sdd",
|
|
240
|
+
ref: SDD_DEFAULT_AGENT[role]
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
source: "plugin",
|
|
245
|
+
ref: `${match.plugin}-${role}`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function resolveRole(role, artifactType, ctx) {
|
|
249
|
+
const bars = ROLE_LOADOUT[role].bars.map((b) => {
|
|
250
|
+
const [actor, gate] = b.split("-");
|
|
251
|
+
return matchBar(artifactType, actor, gate, ctx);
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
role,
|
|
255
|
+
agent: resolveAgent(role, ctx.match),
|
|
256
|
+
bars
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function buildLoadPlan(artifactType, registry, projectGovs) {
|
|
260
|
+
const { match, ambiguous } = matchSquad(registry, artifactType);
|
|
261
|
+
if (ambiguous.length > 0) return {
|
|
262
|
+
artifactType,
|
|
263
|
+
status: "needs-input",
|
|
264
|
+
plugin: null,
|
|
265
|
+
ambiguous,
|
|
266
|
+
roles: []
|
|
267
|
+
};
|
|
268
|
+
const ctx = {
|
|
269
|
+
match,
|
|
270
|
+
projectGovs
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
artifactType,
|
|
274
|
+
status: "complete",
|
|
275
|
+
plugin: match?.plugin ?? null,
|
|
276
|
+
ambiguous: [],
|
|
277
|
+
roles: ROLE_KEYS.map((r) => resolveRole(r, artifactType, ctx))
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function validateRegistry(registry) {
|
|
281
|
+
const v = [];
|
|
282
|
+
const typeOwners = /* @__PURE__ */ new Map();
|
|
283
|
+
for (const entry of registry["sdd-plugins"]) {
|
|
284
|
+
const where = entry.name || "<unnamed plugin>";
|
|
285
|
+
if (!entry.name) v.push("an sdd-plugins entry has no name");
|
|
286
|
+
if (!Array.isArray(entry.squads)) {
|
|
287
|
+
v.push(`${where}: squads is not an array`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
291
|
+
for (const sq of entry.squads) {
|
|
292
|
+
if (!Array.isArray(sq["artifact-types"])) v.push(`${where}: a squad's artifact-types is not an array`);
|
|
293
|
+
for (const r of Object.keys(sq.roles ?? {})) if (!ROLE_KEYS.includes(r)) v.push(`${where}: unknown role key "${r}"`);
|
|
294
|
+
for (const k of Object.keys(sq.governances ?? {})) if (!BAR_KEYS.includes(k)) v.push(`${where}: unknown governance key "${k}"`);
|
|
295
|
+
for (const t of sq["artifact-types"] ?? []) {
|
|
296
|
+
if (seenTypes.has(t)) v.push(`${where}: artifact-type "${t}" appears in more than one squad`);
|
|
297
|
+
seenTypes.add(t);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (const t of seenTypes) typeOwners.set(t, [...typeOwners.get(t) ?? [], where]);
|
|
301
|
+
}
|
|
302
|
+
for (const [t, owners] of typeOwners) if (owners.length > 1) v.push(`artifact-type "${t}" is claimed by ${owners.length} plugins (${owners.join(", ")}) — ambiguous`);
|
|
303
|
+
return v;
|
|
304
|
+
}
|
|
305
|
+
function main(argv) {
|
|
306
|
+
const root = argv.includes("--root") ? argv[argv.indexOf("--root") + 1] : ".";
|
|
307
|
+
const explicitType = argv.includes("--artifact-type") ? argv[argv.indexOf("--artifact-type") + 1] : null;
|
|
308
|
+
const pathArg = argv.includes("--path") ? argv[argv.indexOf("--path") + 1] : null;
|
|
309
|
+
const projectArg = argv.includes("--project") ? argv[argv.indexOf("--project") + 1] : root;
|
|
310
|
+
const projectRootArg = argv.includes("--project-root") ? argv[argv.indexOf("--project-root") + 1] : null;
|
|
311
|
+
const anchors = [{
|
|
312
|
+
tier: "project",
|
|
313
|
+
root: projectArg
|
|
314
|
+
}];
|
|
315
|
+
if (projectRootArg) anchors.push({
|
|
316
|
+
tier: "project-root",
|
|
317
|
+
root: projectRootArg
|
|
318
|
+
});
|
|
319
|
+
let artifactType = explicitType;
|
|
320
|
+
if (!artifactType && pathArg) artifactType = resolveArtifactTypeFromMap(loadArtifactTypeMap(root), pathArg);
|
|
321
|
+
let registry;
|
|
322
|
+
try {
|
|
323
|
+
registry = loadRegistry(root);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
console.error(`✗ ${e.message}`);
|
|
326
|
+
return 1;
|
|
327
|
+
}
|
|
328
|
+
if (!artifactType && pathArg) {
|
|
329
|
+
process.stdout.write(`${JSON.stringify({
|
|
330
|
+
path: pathArg,
|
|
331
|
+
artifactType: null,
|
|
332
|
+
note: "no tiebreaker match — classify by convention"
|
|
333
|
+
}, null, 2)}\n`);
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
if (!artifactType) {
|
|
337
|
+
const violations = validateRegistry(registry);
|
|
338
|
+
if (violations.length) {
|
|
339
|
+
for (const line of violations) console.error(`✗ ${line}`);
|
|
340
|
+
return 1;
|
|
341
|
+
}
|
|
342
|
+
process.stdout.write("governance registry OK\n");
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
const plan = buildLoadPlan(artifactType, registry, collectAnchorGovernances(anchors));
|
|
346
|
+
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
347
|
+
if (plan.status === "needs-input") console.error(`needs-input: artifact-type "${artifactType}" is claimed by ${plan.ambiguous.join(", ")}`);
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
351
|
+
//#endregion
|
|
352
|
+
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 };
|
|
@@ -13,7 +13,7 @@ project spec's `intake` node (repo-only). For one touched artifact it decides **
|
|
|
13
13
|
governs it — spec + gates) or **ignored** (SDD does not govern it; it still gets built) and
|
|
14
14
|
reports which step decided it, so the conductor can skip a task outright (no CR, no draft, no
|
|
15
15
|
gate, no record) when it resolves ignored. The split mirrors git's **tracked vs ignored** files.
|
|
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
|
## Run the resolution
|
|
19
19
|
|
|
@@ -1,12 +1,152 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
// Compiled by `universal-plugin plugin build` from src/resolve-tracking/resolve-tracking.mts. Edit the source, not this file.
|
|
2
|
+
globalThis.__SKILL_SCRIPT__ = {"package":null,"bin":null,"version":"0.7.2","entry":"skills/resolve-tracking/scripts/resolve-tracking.mjs","commands":{}};
|
|
3
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
//#region src/resolve-tracking/resolve-tracking.mts
|
|
7
|
+
function globToRegExpBody(glob) {
|
|
8
|
+
return glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, (m) => m.length > 1 ? ".*" : "[^/]*").replace(/\?/g, "[^/]");
|
|
9
|
+
}
|
|
10
|
+
function parseIgnoreLine(raw) {
|
|
11
|
+
const trimmed = raw.replace(/\r$/, "").trim();
|
|
12
|
+
if (!trimmed || trimmed.startsWith("#")) return "skip";
|
|
13
|
+
let body = trimmed;
|
|
14
|
+
const negated = body.startsWith("!");
|
|
15
|
+
if (negated) body = body.slice(1);
|
|
16
|
+
if (body.endsWith("/")) body = body.slice(0, -1);
|
|
17
|
+
let anchored = false;
|
|
18
|
+
if (body.startsWith("/")) {
|
|
19
|
+
anchored = true;
|
|
20
|
+
body = body.slice(1);
|
|
21
|
+
} else if (body.includes("/")) anchored = true;
|
|
22
|
+
if (body === "") return null;
|
|
23
|
+
const prefix = anchored ? "^" : "^(?:.*/)?";
|
|
24
|
+
const core = globToRegExpBody(body);
|
|
25
|
+
const suffix = "(?:/.*)?$";
|
|
26
|
+
try {
|
|
27
|
+
return {
|
|
28
|
+
negated,
|
|
29
|
+
regex: new RegExp(prefix + core + suffix),
|
|
30
|
+
source: trimmed
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseIgnoreFile(text) {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const raw of text.split("\n")) {
|
|
39
|
+
const rule = parseIgnoreLine(raw);
|
|
40
|
+
if (rule && rule !== "skip") out.push(rule);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function loadIgnoreRules(root) {
|
|
45
|
+
const path = join(root, ".agents", "sdd", ".sddignore");
|
|
46
|
+
if (!existsSync(path)) return [];
|
|
47
|
+
return parseIgnoreFile(readFileSync(path, "utf8"));
|
|
48
|
+
}
|
|
49
|
+
function resolveFromIgnore(rules, path) {
|
|
50
|
+
let value = null;
|
|
51
|
+
for (const rule of rules) if (rule.regex.test(path)) value = rule.negated ? "tracked" : "ignored";
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
const KIND_DEFAULT_GLOBS = {
|
|
55
|
+
skill: {
|
|
56
|
+
ignored: [".agents/skills/**"],
|
|
57
|
+
tracked: [
|
|
58
|
+
"skills/**",
|
|
59
|
+
"plugins/*/skills/**",
|
|
60
|
+
"packages/*/skills/**"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
subagent: {
|
|
64
|
+
ignored: [".agents/agents/**"],
|
|
65
|
+
tracked: ["plugins/*/agents/**", "packages/*/agents/**"]
|
|
66
|
+
},
|
|
67
|
+
command: {
|
|
68
|
+
ignored: [".agents/commands/**"],
|
|
69
|
+
tracked: ["plugins/*/commands/**", "packages/*/commands/**"]
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function globMatch(glob, path) {
|
|
73
|
+
return new RegExp(`^${globToRegExpBody(glob)}$`).test(path);
|
|
74
|
+
}
|
|
75
|
+
function resolveKindDefault(artifactType, path) {
|
|
76
|
+
if (!artifactType) return null;
|
|
77
|
+
const kind = KIND_DEFAULT_GLOBS[artifactType];
|
|
78
|
+
if (!kind) return null;
|
|
79
|
+
if (kind.ignored.some((g) => globMatch(g, path))) return "ignored";
|
|
80
|
+
if (kind.tracked.some((g) => globMatch(g, path))) return "tracked";
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function resolveTracking(input) {
|
|
84
|
+
if (input.explicit) return {
|
|
85
|
+
value: input.explicit,
|
|
86
|
+
reason: "explicit override"
|
|
87
|
+
};
|
|
88
|
+
const ignoreMatch = resolveFromIgnore(loadIgnoreRules(input.root), input.path);
|
|
89
|
+
if (ignoreMatch) return {
|
|
90
|
+
value: ignoreMatch,
|
|
91
|
+
reason: ".sddignore"
|
|
92
|
+
};
|
|
93
|
+
const kindDefault = resolveKindDefault(input.artifactType, input.path);
|
|
94
|
+
if (kindDefault) return {
|
|
95
|
+
value: kindDefault,
|
|
96
|
+
reason: `kind-default (${input.artifactType})`
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
value: "tracked",
|
|
100
|
+
reason: "fail-closed (no signal)"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function validateIgnoreFile(root) {
|
|
104
|
+
const path = join(root, ".agents", "sdd", ".sddignore");
|
|
105
|
+
if (!existsSync(path)) return {
|
|
106
|
+
ok: true,
|
|
107
|
+
message: ".sddignore OK (absent, fine)"
|
|
108
|
+
};
|
|
109
|
+
const text = readFileSync(path, "utf8");
|
|
110
|
+
const notes = [];
|
|
111
|
+
text.split("\n").forEach((raw, i) => {
|
|
112
|
+
if (parseIgnoreLine(raw) === null) notes.push(`line ${i + 1}: not a valid gitignore pattern: ${raw.trim()}`);
|
|
113
|
+
});
|
|
114
|
+
if (notes.length > 0) return {
|
|
115
|
+
ok: false,
|
|
116
|
+
message: notes.join("\n")
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
ok: true,
|
|
120
|
+
message: `.sddignore OK (${parseIgnoreFile(text).length} rule(s))`
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parseArgs(argv) {
|
|
124
|
+
const out = {};
|
|
125
|
+
for (let i = 0; i < argv.length; i++) if (argv[i].startsWith("--")) {
|
|
126
|
+
out[argv[i].slice(2)] = argv[i + 1];
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function main(argv) {
|
|
132
|
+
const args = parseArgs(argv);
|
|
133
|
+
const root = args.root ?? ".";
|
|
134
|
+
if (!args.path) {
|
|
135
|
+
const result = validateIgnoreFile(root);
|
|
136
|
+
process.stdout.write(`${result.message}\n`);
|
|
137
|
+
if (!result.ok) process.exitCode = 1;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const explicit = args.explicit === "tracked" || args.explicit === "ignored" ? args.explicit : void 0;
|
|
141
|
+
const result = resolveTracking({
|
|
142
|
+
root,
|
|
143
|
+
path: args.path,
|
|
144
|
+
artifactType: args["artifact-type"],
|
|
145
|
+
explicit
|
|
146
|
+
});
|
|
147
|
+
process.stdout.write(`${result.value}\n`);
|
|
148
|
+
process.stdout.write(`reason: ${result.reason}\n`);
|
|
149
|
+
}
|
|
150
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) main(process.argv.slice(2));
|
|
151
|
+
//#endregion
|
|
152
|
+
export { loadIgnoreRules, main, parseIgnoreFile, parseIgnoreLine, resolveFromIgnore, resolveKindDefault, resolveTracking, validateIgnoreFile };
|
|
@@ -168,7 +168,7 @@ In the same act that writes root `spec.md`, record both so a later edit reads, n
|
|
|
168
168
|
cross-cutting `concept → its nodes` view. Reserve the block; **leave it for `concept-index` to
|
|
169
169
|
generate** from `concept:` frontmatter — pure derivation, never hand-maintained.
|
|
170
170
|
|
|
171
|
-
Validate the result with the `spec-gate` skill's `check-spec-state` script (`check-spec-state.
|
|
171
|
+
Validate the result with the `spec-gate` skill's `check-spec-state` script (`check-spec-state.mjs --root
|
|
172
172
|
<specs-dir>`): the root lifecycle tuple must be legal.
|
|
173
173
|
|
|
174
174
|
## 6 — Hand back
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# spec-gate
|
|
2
2
|
|
|
3
3
|
Internal SDD skill that runs the **spec gate** (Draft → Approved) over a CR's spec + suite
|
|
4
|
-
**diff**. It runs the deterministic structural checks (`scripts/check-spec-state.
|
|
4
|
+
**diff**. It runs the deterministic structural checks (`scripts/check-spec-state.mjs` — the root
|
|
5
5
|
lifecycle tuple + the per-node `spec-type` reconcile) and the provenance structural checks first
|
|
6
6
|
(fail-closed on malformed `produced-by` / no resolvable producer; flag-only on uninstalled
|
|
7
7
|
producers), then — running **in-session** as the conductor at the gate — **spawns a distinct cold
|
|
@@ -18,18 +18,21 @@ frontmatter and never fixes issues automatically.
|
|
|
18
18
|
References `sdd:lifecycle-governance`, `sdd:ownership-governance`, `sdd:gate-validation-governance`,
|
|
19
19
|
`sdd:combat-log-governance` (provenance/freeze shapes), and the conductor's autonomy bar.
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Engines
|
|
22
|
+
|
|
23
|
+
Source and tests are in `plugins/sdd/src/spec-gate/`; `universal-plugin plugin build` compiles each
|
|
24
|
+
engine into `scripts/<engine>.mjs`, which is what the skill runs.
|
|
22
25
|
|
|
23
26
|
- `check-spec-state.mts` — deterministic state validator (root tuple + per-node spec-type
|
|
24
27
|
reconcile); run via `pnpm verify:specs-new`. Tested by `check-spec-state.test.mts`.
|
|
25
28
|
- `check-suite.mts` — deterministic `.feature`-form validator (Gherkin validity, boolean-`Then`
|
|
26
29
|
form, scenario ordering); run via `pnpm verify:specs-new`. Tested by `check-suite.test.mts`.
|
|
27
|
-
|
|
28
|
-
runs from an installed plugin, which has no `node_modules`.
|
|
29
|
-
bundle; `check-suite-installed.test.mts`
|
|
30
|
-
fails while the bundle is stale.
|
|
30
|
+
The build inlines gherkin-cli from `src/vendor/gherkin-cli.mjs`, a committed self-contained
|
|
31
|
+
bundle, so the compiled script runs from an installed plugin, which has no `node_modules`.
|
|
32
|
+
`pnpm --filter cyber-sdd build:vendor` rebuilds the bundle; `check-suite-installed.test.mts`
|
|
33
|
+
runs the engine from a copy of the plugin's files and fails while the bundle is stale.
|
|
31
34
|
- `classify-edit-class.mts` — the structural edit-class classifier for touched frozen `.feature`
|
|
32
|
-
files.
|
|
35
|
+
files. The build inlines the same `src/vendor/gherkin-cli.mjs` bundle; `classify-edit-class-installed.test.mts`
|
|
33
36
|
runs it from a copy of the plugin's files.
|
|
34
|
-
- `vendor/gherkin-cli.mjs` is shared: `touch-set-correction` and `verify-scenarios`
|
|
35
|
-
|
|
37
|
+
- `src/vendor/gherkin-cli.mjs` is shared: `touch-set-correction` and `verify-scenarios` inline it
|
|
38
|
+
too, so every compiled script stays self-contained.
|
|
@@ -35,7 +35,7 @@ carrying a `.feature` or missing its `## Subject`, and a `behavioral` node missi
|
|
|
35
35
|
**fail closed**; a descriptive node (no marker) raises no violation. If `node` is unavailable,
|
|
36
36
|
perform the same checks by reading each README's frontmatter yourself.
|
|
37
37
|
|
|
38
|
-
The sibling `scripts/check-suite.
|
|
38
|
+
The sibling `scripts/check-suite.mjs` is the **`.feature`-form** authority — Gherkin validity,
|
|
39
39
|
boolean-`Then` form (hedge-word + leaked-rubric detection), and scenario ordering/sectioning. When
|
|
40
40
|
the CR touches any `.feature`, run it here **fail-closed, before spawning the cold judge**, scoped to
|
|
41
41
|
the CR's touched files:
|
|
@@ -157,7 +157,7 @@ files keep their state. An **additive** scenario folds into a frozen file withou
|
|
|
157
157
|
change is purely additive with no judge round; a **narrowing/rewriting** edit (a `modified`/`removed`
|
|
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
|
-
`scripts/classify-edit-class.
|
|
160
|
+
`scripts/classify-edit-class.mjs` (`--files <paths> [--base <ref>]`): a **structural** per-named-`Scenario`
|
|
161
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
|