cyber-sdd 0.6.1 → 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-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 +1 -1
- 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 +2 -2
- 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/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 +10 -0
- 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/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-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/spec-gate/SKILL.md +4 -4
- 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/SKILL.md +2 -2
- package/skills/spec-structure-governance/README.md +4 -2
- package/skills/spec-structure-governance/SKILL.md +42 -7
- package/skills/start-mission/SKILL.md +1 -1
- package/skills/touch-set-correction/SKILL.md +1 -1
- 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 +2 -2
- 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
|
|
@@ -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.
|
|
@@ -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
|
|
@@ -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
|