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.
Files changed (150) hide show
  1. package/.claude-plugin/plugin.json +1 -2
  2. package/.codex-plugin/plugin.json +1 -2
  3. package/CHANGELOG.md +320 -0
  4. package/agents/sdd-scanner.md +1 -1
  5. package/package.json +11 -12
  6. package/plugin.json +55 -0
  7. package/skills/align-spec/SKILL.md +1 -1
  8. package/skills/align-spec/scripts/align-spec.mjs +6755 -12
  9. package/skills/blast-estimate/README.md +3 -3
  10. package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -12
  11. package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -12
  12. package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -12
  13. package/skills/check-plan-safety/SKILL.md +2 -3
  14. package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -12
  15. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -12
  16. package/skills/check-project-specs/SKILL.md +1 -1
  17. package/skills/check-project-specs/scripts/check-project-specs.mjs +593 -12
  18. package/skills/check-retired-terms/SKILL.md +2 -3
  19. package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -12
  20. package/skills/check-scenario-overlap/SKILL.md +1 -2
  21. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +177 -12
  22. package/skills/check-spec-references/SKILL.md +2 -2
  23. package/skills/check-spec-references/scripts/check-spec-references.mjs +287 -12
  24. package/skills/check-spec-structure/SKILL.md +1 -2
  25. package/skills/check-spec-structure/scripts/check-spec-structure.mjs +243 -12
  26. package/skills/collision-ladder/README.md +2 -2
  27. package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -12
  28. package/skills/combat-log-governance/SKILL.md +1 -1
  29. package/skills/concept-index/SKILL.md +1 -1
  30. package/skills/concept-index/scripts/concept-index.mjs +183 -12
  31. package/skills/discover-plans/README.md +3 -3
  32. package/skills/discover-plans/SKILL.md +1 -1
  33. package/skills/discover-plans/scripts/discover-plans.mjs +137 -12
  34. package/skills/discover-specs/README.md +3 -3
  35. package/skills/discover-specs/SKILL.md +1 -1
  36. package/skills/discover-specs/scripts/discover-specs.mjs +321 -12
  37. package/skills/doctrine-loop/SKILL.md +1 -1
  38. package/skills/gate-validation-governance/SKILL.md +1 -1
  39. package/skills/init/README.md +2 -2
  40. package/skills/init/scripts/wire-statusline.mjs +168 -12
  41. package/skills/manage-ignore/README.md +3 -3
  42. package/skills/manage-ignore/SKILL.md +1 -2
  43. package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -12
  44. package/skills/manage-scenario-bridge/README.md +3 -3
  45. package/skills/manage-scenario-bridge/SKILL.md +1 -1
  46. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +239 -12
  47. package/skills/manage-spec-anchors/README.md +3 -3
  48. package/skills/manage-spec-anchors/SKILL.md +1 -1
  49. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -12
  50. package/skills/mission-graph/README.md +2 -2
  51. package/skills/mission-graph/SKILL.md +1 -1
  52. package/skills/mission-graph/scripts/mission-graph.mjs +1040 -12
  53. package/skills/pause-mission/SKILL.md +1 -1
  54. package/skills/place-node/SKILL.md +2 -2
  55. package/skills/place-node/scripts/place-node.mjs +117 -12
  56. package/skills/plan-retirement/README.md +2 -2
  57. package/skills/plan-retirement/scripts/retire-plans.mjs +123 -12
  58. package/skills/resolve-governances/README.md +3 -3
  59. package/skills/resolve-governances/SKILL.md +1 -1
  60. package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -12
  61. package/skills/resolve-tracking/SKILL.md +1 -1
  62. package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -12
  63. package/skills/scaffold-project-spec/SKILL.md +1 -1
  64. package/skills/spec-gate/README.md +12 -9
  65. package/skills/spec-gate/SKILL.md +2 -2
  66. package/skills/spec-gate/scripts/check-spec-state.mjs +422 -12
  67. package/skills/spec-gate/scripts/check-suite.mjs +6500 -12
  68. package/skills/spec-gate/scripts/classify-edit-class.mjs +6579 -12
  69. package/skills/spec-producer-governance/SKILL.md +2 -2
  70. package/skills/start-mission/SKILL.md +1 -1
  71. package/skills/suite-format-governance/README.md +1 -1
  72. package/skills/suite-format-governance/SKILL.md +1 -1
  73. package/skills/touch-set-correction/README.md +5 -5
  74. package/skills/touch-set-correction/SKILL.md +1 -1
  75. package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -12
  76. package/skills/verify-scenarios/README.md +6 -6
  77. package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -12
  78. package/.plugin/pins.json +0 -3
  79. package/.plugin/plugin.json +0 -19
  80. package/dist/align-spec/scripts/align-spec.js +0 -102
  81. package/dist/check-project-specs/scripts/check-project-specs.js +0 -312
  82. package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +0 -175
  83. package/dist/check-spec-references/scripts/check-spec-references.js +0 -285
  84. package/dist/check-spec-structure/scripts/check-spec-structure.js +0 -241
  85. package/dist/concept-index/scripts/concept-index.js +0 -181
  86. package/dist/discover-specs/scripts/discover-specs.js +0 -318
  87. package/dist/spec-gate/scripts/check-spec-state.js +0 -420
  88. package/dist/spec-gate/scripts/check-suite.js +0 -317
  89. package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
  90. package/dist/spec-gate/vendor/gherkin-cli.js +0 -6369
  91. package/skills/align-spec/scripts/align-spec.bundle.mjs +0 -6753
  92. package/skills/align-spec/scripts/align-spec.mts +0 -188
  93. package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +0 -593
  94. package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
  95. package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +0 -251
  96. package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
  97. package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +0 -233
  98. package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
  99. package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +0 -107
  100. package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
  101. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +0 -248
  102. package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
  103. package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +0 -584
  104. package/skills/check-project-specs/scripts/check-project-specs.mts +0 -370
  105. package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +0 -184
  106. package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
  107. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +0 -175
  108. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
  109. package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +0 -285
  110. package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
  111. package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +0 -241
  112. package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
  113. package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +0 -7041
  114. package/skills/collision-ladder/scripts/collision-ladder.mts +0 -662
  115. package/skills/concept-index/scripts/concept-index.bundle.mjs +0 -181
  116. package/skills/concept-index/scripts/concept-index.mts +0 -246
  117. package/skills/discover-plans/scripts/discover-plans.bundle.mjs +0 -135
  118. package/skills/discover-plans/scripts/discover-plans.mts +0 -215
  119. package/skills/discover-specs/scripts/discover-specs.bundle.mjs +0 -319
  120. package/skills/discover-specs/scripts/discover-specs.mts +0 -420
  121. package/skills/init/scripts/wire-statusline.bundle.mjs +0 -166
  122. package/skills/init/scripts/wire-statusline.mts +0 -279
  123. package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +0 -244
  124. package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
  125. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +0 -237
  126. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
  127. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +0 -308
  128. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
  129. package/skills/mission-graph/scripts/mission-graph.bundle.mjs +0 -1038
  130. package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
  131. package/skills/place-node/scripts/place-node.bundle.mjs +0 -115
  132. package/skills/place-node/scripts/place-node.mts +0 -158
  133. package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +0 -121
  134. package/skills/plan-retirement/scripts/retire-plans.mts +0 -205
  135. package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +0 -350
  136. package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
  137. package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +0 -150
  138. package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
  139. package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +0 -420
  140. package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
  141. package/skills/spec-gate/scripts/check-suite.bundle.mjs +0 -6498
  142. package/skills/spec-gate/scripts/check-suite.mts +0 -538
  143. package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +0 -6577
  144. package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
  145. package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
  146. package/skills/spec-gate/vendor/gherkin-cli.mjs +0 -6545
  147. package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +0 -6655
  148. package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -429
  149. package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +0 -6537
  150. package/skills/verify-scenarios/scripts/verify-scenarios.mts +0 -470
@@ -1,12 +1,352 @@
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
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 `.mts` script (the repo's node-≥23.6 / no-deps convention).
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
- // 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
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.mts --root
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.mts` — the root
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
- ## scripts/
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
- It imports gherkin-cli from `../vendor/gherkin-cli.mjs`, a committed self-contained bundle, so it
28
- runs from an installed plugin, which has no `node_modules`. `pnpm build:vendor` rebuilds the
29
- bundle; `check-suite-installed.test.mts` runs the engine from a copy of the plugin's files and
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. It imports the same `../vendor/gherkin-cli.mjs` bundle; `classify-edit-class-installed.test.mts`
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` import it from
35
- here too, so the plugin ships one copy.
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.mts` is the **`.feature`-form** authority — Gherkin validity,
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.mts` (`--files <paths> [--base <ref>]`): a **structural** per-named-`Scenario`
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