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.
Files changed (137) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +2 -2
  3. package/.plugin/plugin.json +1 -1
  4. package/agents/sdd-automaton.md +2 -3
  5. package/agents/sdd-impl-judge.md +5 -4
  6. package/agents/sdd-scanner.md +1 -1
  7. package/agents/sdd-spec-judge.md +3 -3
  8. package/agents/sdd-warden.md +8 -0
  9. package/dist/check-project-specs/scripts/check-project-specs.js +1 -0
  10. package/package.json +2 -2
  11. package/skills/align-spec/README.md +3 -3
  12. package/skills/align-spec/SKILL.md +1 -1
  13. package/skills/align-spec/scripts/align-spec.bundle.mjs +6753 -0
  14. package/skills/align-spec/scripts/align-spec.mjs +12 -0
  15. package/skills/architect-impl-governance/SKILL.md +3 -1
  16. package/skills/architect-spec-governance/SKILL.md +5 -1
  17. package/skills/blast-estimate/README.md +1 -1
  18. package/skills/blast-estimate/SKILL.md +1 -1
  19. package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +593 -0
  20. package/skills/blast-estimate/scripts/blast-estimate.mjs +12 -0
  21. package/skills/check-field-mandates/SKILL.md +1 -1
  22. package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +251 -0
  23. package/skills/check-field-mandates/scripts/check-field-mandates.mjs +12 -0
  24. package/skills/check-partition-quality/SKILL.md +1 -1
  25. package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +233 -0
  26. package/skills/check-partition-quality/scripts/check-partition-quality.mjs +12 -0
  27. package/skills/check-plan-safety/README.md +3 -3
  28. package/skills/check-plan-safety/SKILL.md +1 -1
  29. package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +107 -0
  30. package/skills/check-plan-safety/scripts/check-plan-safety.mjs +12 -0
  31. package/skills/check-plugin-manifests/SKILL.md +1 -1
  32. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +248 -0
  33. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +12 -0
  34. package/skills/check-project-specs/README.md +3 -3
  35. package/skills/check-project-specs/SKILL.md +2 -2
  36. package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +584 -0
  37. package/skills/check-project-specs/scripts/check-project-specs.mjs +12 -0
  38. package/skills/check-project-specs/scripts/check-project-specs.mts +1 -0
  39. package/skills/check-retired-terms/README.md +2 -2
  40. package/skills/check-retired-terms/SKILL.md +2 -2
  41. package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +184 -0
  42. package/skills/check-retired-terms/scripts/check-retired-terms.mjs +12 -0
  43. package/skills/check-scenario-overlap/README.md +2 -2
  44. package/skills/check-scenario-overlap/SKILL.md +2 -3
  45. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +175 -0
  46. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +12 -0
  47. package/skills/check-spec-references/README.md +1 -1
  48. package/skills/check-spec-references/SKILL.md +1 -1
  49. package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +285 -0
  50. package/skills/check-spec-references/scripts/check-spec-references.mjs +12 -0
  51. package/skills/check-spec-structure/README.md +2 -2
  52. package/skills/check-spec-structure/SKILL.md +1 -1
  53. package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +241 -0
  54. package/skills/check-spec-structure/scripts/check-spec-structure.mjs +12 -0
  55. package/skills/collision-ladder/SKILL.md +3 -3
  56. package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +7041 -0
  57. package/skills/collision-ladder/scripts/collision-ladder.mjs +12 -0
  58. package/skills/collision-ladder/scripts/collision-ladder.mts +4 -2
  59. package/skills/combat-log-governance/SKILL.md +1 -1
  60. package/skills/concept-index/README.md +2 -2
  61. package/skills/concept-index/SKILL.md +1 -1
  62. package/skills/concept-index/scripts/concept-index.bundle.mjs +181 -0
  63. package/skills/concept-index/scripts/concept-index.mjs +12 -0
  64. package/skills/discover-plans/README.md +2 -2
  65. package/skills/discover-plans/SKILL.md +1 -1
  66. package/skills/discover-plans/scripts/discover-plans.bundle.mjs +135 -0
  67. package/skills/discover-plans/scripts/discover-plans.mjs +12 -0
  68. package/skills/discover-specs/README.md +1 -1
  69. package/skills/discover-specs/SKILL.md +1 -1
  70. package/skills/discover-specs/scripts/discover-specs.bundle.mjs +319 -0
  71. package/skills/discover-specs/scripts/discover-specs.mjs +12 -0
  72. package/skills/doctrine-loop/SKILL.md +2 -2
  73. package/skills/formation-loop/SKILL.md +11 -4
  74. package/skills/gate-validation-governance/SKILL.md +1 -1
  75. package/skills/init/README.md +3 -3
  76. package/skills/init/SKILL.md +3 -3
  77. package/skills/init/scripts/wire-statusline.bundle.mjs +166 -0
  78. package/skills/init/scripts/wire-statusline.mjs +12 -0
  79. package/skills/manage-ignore/README.md +5 -5
  80. package/skills/manage-ignore/SKILL.md +1 -1
  81. package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +244 -0
  82. package/skills/manage-ignore/scripts/manage-ignore.mjs +12 -0
  83. package/skills/manage-scenario-bridge/README.md +3 -3
  84. package/skills/manage-scenario-bridge/SKILL.md +1 -1
  85. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +237 -0
  86. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +12 -0
  87. package/skills/manage-spec-anchors/README.md +4 -4
  88. package/skills/manage-spec-anchors/SKILL.md +1 -1
  89. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +308 -0
  90. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +12 -0
  91. package/skills/mission-graph/SKILL.md +9 -9
  92. package/skills/mission-graph/scripts/mission-graph.bundle.mjs +1038 -0
  93. package/skills/mission-graph/scripts/mission-graph.mjs +12 -0
  94. package/skills/pause-mission/SKILL.md +1 -1
  95. package/skills/place-node/README.md +1 -1
  96. package/skills/place-node/SKILL.md +1 -1
  97. package/skills/place-node/scripts/place-node.bundle.mjs +115 -0
  98. package/skills/place-node/scripts/place-node.mjs +12 -0
  99. package/skills/plan-retirement/SKILL.md +1 -1
  100. package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +121 -0
  101. package/skills/plan-retirement/scripts/retire-plans.mjs +12 -0
  102. package/skills/plan-retirement/scripts/retire-plans.mts +6 -0
  103. package/skills/plugin-contract-governance/SKILL.md +5 -12
  104. package/skills/resolve-governances/README.md +1 -1
  105. package/skills/resolve-governances/SKILL.md +1 -1
  106. package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +350 -0
  107. package/skills/resolve-governances/scripts/resolve-governances.mjs +12 -0
  108. package/skills/resolve-governances/scripts/resolve-governances.mts +1 -1
  109. package/skills/resolve-tracking/SKILL.md +1 -1
  110. package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +150 -0
  111. package/skills/resolve-tracking/scripts/resolve-tracking.mjs +12 -0
  112. package/skills/scaffold-project-spec/SKILL.md +2 -2
  113. package/skills/sdd/SKILL.md +1 -1
  114. package/skills/solution-producer-governance/README.md +1 -1
  115. package/skills/solution-producer-governance/SKILL.md +1 -1
  116. package/skills/spec-gate/SKILL.md +5 -5
  117. package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +420 -0
  118. package/skills/spec-gate/scripts/check-spec-state.mjs +12 -0
  119. package/skills/spec-gate/scripts/check-suite.bundle.mjs +6498 -0
  120. package/skills/spec-gate/scripts/check-suite.mjs +12 -0
  121. package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +6577 -0
  122. package/skills/spec-gate/scripts/classify-edit-class.mjs +12 -0
  123. package/skills/spec-producer-governance/README.md +1 -1
  124. package/skills/spec-producer-governance/SKILL.md +3 -3
  125. package/skills/spec-structure-governance/README.md +4 -2
  126. package/skills/spec-structure-governance/SKILL.md +42 -7
  127. package/skills/start-mission/README.md +1 -1
  128. package/skills/start-mission/SKILL.md +6 -6
  129. package/skills/suite-format-governance/references/rubric.md +1 -1
  130. package/skills/touch-set-correction/SKILL.md +2 -2
  131. package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +6655 -0
  132. package/skills/touch-set-correction/scripts/touch-set-correction.mjs +12 -0
  133. package/skills/touch-set-correction/scripts/touch-set-correction.mts +12 -3
  134. package/skills/verify-scenarios/README.md +3 -3
  135. package/skills/verify-scenarios/SKILL.md +3 -3
  136. package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +6537 -0
  137. 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.mts" --root . --path <repo-relative-path> [--artifact-type <type>] [--explicit tracked|ignored]
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
@@ -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.mts" --root . --path <repo-relative-path> [--artifact-type <type>] [--explicit tracked|ignored]
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.mts" [--root <specs-dir>]
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.mts" --files <the CR's touched .feature files>
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.mts" --files <the CR's touched .md files> [--base <baseref>]
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.0.2 diff`, plus git rename detection for a pure `git mv`, **never a raw
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.mts" --files <the CR's touched .feature files> [--base <baseref>]
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