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,297 +0,0 @@
1
- #!/usr/bin/env node
2
- // manage-ignore — the concrete curation engine for `.agents/sdd/.sddignore`, the optional
3
- // gitignore-syntax file `resolve-tracking` consults to decide whether an artifact is tracked or
4
- // ignored. It reads/writes ONLY `.agents/sdd/.sddignore` — never a spec.md, status, approval, or
5
- // freeze.
6
- //
7
- // Operations:
8
- // --list print every rule in file order (a missing file prints nothing)
9
- // --add <pattern> validate + append a well-formed gitignore rule (creates the file)
10
- // --remove <pattern> drop a matching rule, preserving the order of the rest (absent = no-op)
11
- // --induce <path> offer a literal-path candidate + a ** generalization (persists nothing)
12
- // --preview <pattern> list the working-tree paths the pattern would ignore / re-track
13
- //
14
- // `.sddignore` is gitignore syntax: a plain pattern => ignored, a leading `!` => re-included, `#`
15
- // comments and blank lines allowed. Order is MEANINGFUL (last-match-wins), so rules are NEVER
16
- // re-sorted — add appends, remove keeps the surviving order.
17
- // Pure functions are exported for node:test; running the file directly drives the CLI. No deps
18
- // (the repo's node-≥23.6 convention).
19
-
20
- import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
21
- import { dirname, join } from 'node:path'
22
- import { pathToFileURL } from 'node:url'
23
-
24
- const IGNORE_FILE = '.agents/sdd/.sddignore'
25
- const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.turbo', '.next', 'coverage'])
26
-
27
- // ── the ignore file (raw-line preserving so order + comments survive CRUD) ──
28
-
29
- // Split into lines, dropping the single trailing empty produced by a final newline.
30
- export function splitLines(text: string): string[] {
31
- const lines = text.split('\n')
32
- if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
33
- return lines
34
- }
35
-
36
- export function readIgnoreLines(root: string): string[] {
37
- const file = join(root, IGNORE_FILE)
38
- if (!existsSync(file)) return []
39
- try {
40
- return splitLines(readFileSync(file, 'utf8'))
41
- } catch {
42
- return []
43
- }
44
- }
45
-
46
- function writeIgnoreLines(root: string, lines: string[]): void {
47
- const file = join(root, IGNORE_FILE)
48
- mkdirSync(dirname(file), { recursive: true })
49
- writeFileSync(file, lines.length === 0 ? '' : `${lines.join('\n')}\n`)
50
- }
51
-
52
- // A line is a rule when it is not blank and not a `#` comment. `!re-track` rules are rules.
53
- export function isRuleLine(line: string): boolean {
54
- const t = line.trim()
55
- return t !== '' && !t.startsWith('#')
56
- }
57
-
58
- // ── list ──
59
-
60
- // Every rule in file order; comments and blank lines are omitted (a missing file yields []).
61
- export function listRules(root: string): string[] {
62
- return readIgnoreLines(root)
63
- .filter(isRuleLine)
64
- .map((l) => l.trim())
65
- }
66
-
67
- // ── validation ──
68
-
69
- // True when the bracket expression opened at some `[` is never closed by a later `]`.
70
- function hasUnclosedBracket(s: string): boolean {
71
- let i = 0
72
- while (i < s.length) {
73
- if (s[i] === '\\') {
74
- i += 2
75
- continue
76
- }
77
- if (s[i] === '[') {
78
- const close = s.indexOf(']', i + 1)
79
- if (close === -1) return true
80
- i = close + 1
81
- continue
82
- }
83
- i++
84
- }
85
- return false
86
- }
87
-
88
- // A well-formed gitignore pattern: non-empty after stripping a leading `!` and surrounding space,
89
- // not a comment, and with no unclosed `[…]` bracket expression.
90
- export function isValidPattern(pattern: string): boolean {
91
- let s = pattern
92
- if (s.startsWith('!')) s = s.slice(1)
93
- s = s.replace(/^\s+|\s+$/g, '')
94
- if (s === '' || s.startsWith('#')) return false
95
- if (hasUnclosedBracket(s)) return false
96
- return true
97
- }
98
-
99
- // ── add / remove (CRUD, order-preserving) ──
100
-
101
- export type CrudResult =
102
- | { ok: true; changed: boolean; rules: string[]; message: string }
103
- | { ok: false; reason: string }
104
-
105
- // Append a well-formed rule after the existing lines (creating the file when absent). Existing
106
- // order is preserved; a duplicate is a no-op.
107
- export function addRule(root: string, pattern: string): CrudResult {
108
- const p = pattern.trim()
109
- if (!isValidPattern(p)) return { ok: false, reason: 'malformed gitignore pattern' }
110
- const lines = readIgnoreLines(root)
111
- if (lines.some((l) => l.trim() === p)) {
112
- return { ok: true, changed: false, rules: listRules(root), message: 'already present' }
113
- }
114
- const next = [...lines, p]
115
- writeIgnoreLines(root, next)
116
- return { ok: true, changed: true, rules: next.filter(isRuleLine).map((l) => l.trim()), message: `added ${p}` }
117
- }
118
-
119
- // Drop every line whose rule text equals the pattern, keeping the order of the rest. Removing an
120
- // absent rule leaves the file byte-for-byte unchanged.
121
- export function removeRule(root: string, pattern: string): CrudResult {
122
- const p = pattern.trim()
123
- const lines = readIgnoreLines(root)
124
- const next = lines.filter((l) => l.trim() !== p)
125
- if (next.length === lines.length) {
126
- return { ok: true, changed: false, rules: listRules(root), message: 'nothing was removed' }
127
- }
128
- writeIgnoreLines(root, next)
129
- return { ok: true, changed: true, rules: next.filter(isRuleLine).map((l) => l.trim()), message: `removed ${p}` }
130
- }
131
-
132
- // ── induce ──
133
-
134
- export type InduceResult = { ok: true; candidates: string[] } | { ok: false; reason: string }
135
-
136
- // From a sample repo-relative path, offer a literal-path candidate and a `**` generalization that
137
- // ignores the same basename anywhere in the tree. Persists nothing; refuses a path not under the
138
- // repo (leading slash or a `..` escape).
139
- export function inducePatterns(_root: string, samplePath: string): InduceResult {
140
- const normalized = samplePath.trim().replace(/\\/g, '/')
141
- if (normalized.startsWith('/')) return { ok: false, reason: 'path is not repo-relative (absolute)' }
142
- const rel = normalized.replace(/\/+$/g, '')
143
- if (rel === '' || /(^|\/)\.\.(\/|$)/.test(rel)) {
144
- return { ok: false, reason: 'path is not repo-relative' }
145
- }
146
- const base = rel.split('/').pop() ?? rel
147
- const generalized = `**/${base}`
148
- const candidates = generalized === rel ? [rel] : [rel, generalized]
149
- return { ok: true, candidates }
150
- }
151
-
152
- // ── preview ──
153
-
154
- export type PreviewResult = { ok: true; negated: boolean; matches: string[] } | { ok: false; reason: string }
155
-
156
- // Compile one gitignore pattern (already `!`-stripped) to a full-match RegExp. `**` spans
157
- // separators, `*`/`?` stay within one segment, a leading `/` (or an internal `/`) anchors to the
158
- // repo root, a trailing `/` marks a dir. Every match also covers the node's descendants.
159
- export function gitignoreToRegExp(pattern: string): RegExp {
160
- let pat = pattern
161
- if (pat.endsWith('/')) pat = pat.slice(0, -1) // trailing-slash dir marker
162
- let anchored = pat.startsWith('/')
163
- if (anchored) pat = pat.slice(1)
164
- if (pat.includes('/')) anchored = true // an internal separator anchors to root
165
- let body = ''
166
- for (let i = 0; i < pat.length; i++) {
167
- const c = pat[i]
168
- if (c === '*') {
169
- if (pat[i + 1] === '*') {
170
- i++
171
- if (pat[i + 1] === '/') {
172
- i++
173
- body += '(?:.*/)?' // `**/` spans zero or more directory segments
174
- } else {
175
- body += '.*' // `**` spans separators
176
- }
177
- } else {
178
- body += '[^/]*' // `*` stays within a segment
179
- }
180
- } else if (c === '?') {
181
- body += '[^/]'
182
- } else if (c === '[') {
183
- let j = i + 1
184
- let cls = '['
185
- if (pat[j] === '!') {
186
- cls += '^'
187
- j++
188
- }
189
- while (j < pat.length && pat[j] !== ']') {
190
- cls += /[\\^\]]/.test(pat[j]) ? `\\${pat[j]}` : pat[j]
191
- j++
192
- }
193
- cls += ']'
194
- body += cls
195
- i = j
196
- } else {
197
- body += c.replace(/[.+^${}()|\\]/g, '\\$&')
198
- }
199
- }
200
- const prefix = anchored ? '^' : '^(?:.*/)?' // unanchored patterns match at any depth
201
- return new RegExp(`${prefix}${body}(?:/.*)?$`) // also cover everything below the matched node
202
- }
203
-
204
- export function matchesPattern(pattern: string, path: string): boolean {
205
- const body = pattern.startsWith('!') ? pattern.slice(1) : pattern
206
- return gitignoreToRegExp(body).test(path)
207
- }
208
-
209
- // Every repo-relative path (files and dirs) reachable from the root, skipping SKIP_DIRS.
210
- function walkTree(root: string): string[] {
211
- const out: string[] = []
212
- const walk = (rel: string): void => {
213
- let entries: import('node:fs').Dirent[]
214
- try {
215
- entries = readdirSync(join(root, rel), { withFileTypes: true })
216
- } catch {
217
- return
218
- }
219
- for (const e of entries) {
220
- if (SKIP_DIRS.has(e.name)) continue
221
- const child = rel ? `${rel}/${e.name}` : e.name
222
- out.push(child)
223
- if (e.isDirectory()) walk(child)
224
- }
225
- }
226
- walk('')
227
- return out.sort()
228
- }
229
-
230
- // List the working-tree paths a candidate pattern would ignore. For a `!pattern`, the same paths
231
- // are the ones it would RE-TRACK (re-include). Persists nothing; a malformed pattern is refused.
232
- export function previewPattern(root: string, pattern: string): PreviewResult {
233
- const p = pattern.trim()
234
- if (!isValidPattern(p)) return { ok: false, reason: 'malformed gitignore pattern' }
235
- const negated = p.startsWith('!')
236
- const matches = walkTree(root).filter((path) => matchesPattern(p, path))
237
- return { ok: true, negated, matches }
238
- }
239
-
240
- // ── CLI ──
241
-
242
- function flag(argv: string[], name: string): string | undefined {
243
- const i = argv.indexOf(name)
244
- return i === -1 ? undefined : argv[i + 1]
245
- }
246
-
247
- export function main(argv: string[]): number {
248
- const root = flag(argv, '--root') ?? '.'
249
- const w = (s: string) => process.stdout.write(`${s}\n`)
250
-
251
- if (argv.includes('--list')) {
252
- for (const rule of listRules(root)) w(rule)
253
- return 0
254
- }
255
- if (argv.includes('--add')) {
256
- const r = addRule(root, flag(argv, '--add') ?? '')
257
- w(r.ok ? r.message : `refused: ${r.reason}`)
258
- return r.ok ? 0 : 1
259
- }
260
- if (argv.includes('--remove')) {
261
- const r = removeRule(root, flag(argv, '--remove') ?? '')
262
- w(r.ok ? r.message : `refused: ${r.reason}`)
263
- return r.ok ? 0 : 1
264
- }
265
- if (argv.includes('--induce')) {
266
- const r = inducePatterns(root, flag(argv, '--induce') ?? '')
267
- if (!r.ok) {
268
- w(`unusable: ${r.reason}`)
269
- return 1
270
- }
271
- w('Candidate ignore patterns:')
272
- for (const c of r.candidates) w(` ${c}`)
273
- return 0
274
- }
275
- if (argv.includes('--preview')) {
276
- const r = previewPattern(root, flag(argv, '--preview') ?? '')
277
- if (!r.ok) {
278
- w(`invalid: ${r.reason}`)
279
- return 1
280
- }
281
- const verb = r.negated ? 're-track' : 'ignore'
282
- if (r.matches.length === 0) {
283
- w(`This pattern would ${verb} no path.`)
284
- return 0
285
- }
286
- w(`This pattern would ${verb}:`)
287
- for (const m of r.matches) w(` ${m}`)
288
- return 0
289
- }
290
-
291
- w('usage: manage-ignore --list | --add <p> | --remove <p> | --induce <path> | --preview <p>')
292
- return 0
293
- }
294
-
295
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
296
- process.exit(main(process.argv.slice(2)))
297
- }
@@ -1,237 +0,0 @@
1
- #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- //#region skills/spec-gate/vendor/gherkin-cli.mjs
6
- var TokenType;
7
- (function(TokenType) {
8
- TokenType[TokenType["None"] = 0] = "None";
9
- TokenType[TokenType["EOF"] = 1] = "EOF";
10
- TokenType[TokenType["Empty"] = 2] = "Empty";
11
- TokenType[TokenType["Comment"] = 3] = "Comment";
12
- TokenType[TokenType["TagLine"] = 4] = "TagLine";
13
- TokenType[TokenType["FeatureLine"] = 5] = "FeatureLine";
14
- TokenType[TokenType["RuleLine"] = 6] = "RuleLine";
15
- TokenType[TokenType["BackgroundLine"] = 7] = "BackgroundLine";
16
- TokenType[TokenType["ScenarioLine"] = 8] = "ScenarioLine";
17
- TokenType[TokenType["ExamplesLine"] = 9] = "ExamplesLine";
18
- TokenType[TokenType["StepLine"] = 10] = "StepLine";
19
- TokenType[TokenType["DocStringSeparator"] = 11] = "DocStringSeparator";
20
- TokenType[TokenType["TableRow"] = 12] = "TableRow";
21
- TokenType[TokenType["Language"] = 13] = "Language";
22
- TokenType[TokenType["Other"] = 14] = "Other";
23
- })(TokenType || (TokenType = {}));
24
- var RuleType;
25
- (function(RuleType) {
26
- RuleType[RuleType["None"] = 0] = "None";
27
- RuleType[RuleType["_EOF"] = 1] = "_EOF";
28
- RuleType[RuleType["_Empty"] = 2] = "_Empty";
29
- RuleType[RuleType["_Comment"] = 3] = "_Comment";
30
- RuleType[RuleType["_TagLine"] = 4] = "_TagLine";
31
- RuleType[RuleType["_FeatureLine"] = 5] = "_FeatureLine";
32
- RuleType[RuleType["_RuleLine"] = 6] = "_RuleLine";
33
- RuleType[RuleType["_BackgroundLine"] = 7] = "_BackgroundLine";
34
- RuleType[RuleType["_ScenarioLine"] = 8] = "_ScenarioLine";
35
- RuleType[RuleType["_ExamplesLine"] = 9] = "_ExamplesLine";
36
- RuleType[RuleType["_StepLine"] = 10] = "_StepLine";
37
- RuleType[RuleType["_DocStringSeparator"] = 11] = "_DocStringSeparator";
38
- RuleType[RuleType["_TableRow"] = 12] = "_TableRow";
39
- RuleType[RuleType["_Language"] = 13] = "_Language";
40
- RuleType[RuleType["_Other"] = 14] = "_Other";
41
- RuleType[RuleType["GherkinDocument"] = 15] = "GherkinDocument";
42
- RuleType[RuleType["Feature"] = 16] = "Feature";
43
- RuleType[RuleType["FeatureHeader"] = 17] = "FeatureHeader";
44
- RuleType[RuleType["Rule"] = 18] = "Rule";
45
- RuleType[RuleType["RuleHeader"] = 19] = "RuleHeader";
46
- RuleType[RuleType["Background"] = 20] = "Background";
47
- RuleType[RuleType["ScenarioDefinition"] = 21] = "ScenarioDefinition";
48
- RuleType[RuleType["Scenario"] = 22] = "Scenario";
49
- RuleType[RuleType["ExamplesDefinition"] = 23] = "ExamplesDefinition";
50
- RuleType[RuleType["Examples"] = 24] = "Examples";
51
- RuleType[RuleType["ExamplesTable"] = 25] = "ExamplesTable";
52
- RuleType[RuleType["Step"] = 26] = "Step";
53
- RuleType[RuleType["StepArg"] = 27] = "StepArg";
54
- RuleType[RuleType["DataTable"] = 28] = "DataTable";
55
- RuleType[RuleType["DocString"] = 29] = "DocString";
56
- RuleType[RuleType["Tags"] = 30] = "Tags";
57
- RuleType[RuleType["DescriptionHelper"] = 31] = "DescriptionHelper";
58
- RuleType[RuleType["Description"] = 32] = "Description";
59
- })(RuleType || (RuleType = {}));
60
- var AttachmentContentEncoding;
61
- (function(AttachmentContentEncoding) {
62
- AttachmentContentEncoding["IDENTITY"] = "IDENTITY";
63
- AttachmentContentEncoding["BASE64"] = "BASE64";
64
- })(AttachmentContentEncoding || (AttachmentContentEncoding = {}));
65
- var HookType;
66
- (function(HookType) {
67
- HookType["BEFORE_TEST_RUN"] = "BEFORE_TEST_RUN";
68
- HookType["AFTER_TEST_RUN"] = "AFTER_TEST_RUN";
69
- HookType["BEFORE_TEST_CASE"] = "BEFORE_TEST_CASE";
70
- HookType["AFTER_TEST_CASE"] = "AFTER_TEST_CASE";
71
- HookType["BEFORE_TEST_STEP"] = "BEFORE_TEST_STEP";
72
- HookType["AFTER_TEST_STEP"] = "AFTER_TEST_STEP";
73
- })(HookType || (HookType = {}));
74
- var PickleStepType;
75
- (function(PickleStepType) {
76
- PickleStepType["UNKNOWN"] = "Unknown";
77
- PickleStepType["CONTEXT"] = "Context";
78
- PickleStepType["ACTION"] = "Action";
79
- PickleStepType["OUTCOME"] = "Outcome";
80
- })(PickleStepType || (PickleStepType = {}));
81
- var SourceMediaType;
82
- (function(SourceMediaType) {
83
- SourceMediaType["TEXT_X_CUCUMBER_GHERKIN_PLAIN"] = "text/x.cucumber.gherkin+plain";
84
- SourceMediaType["TEXT_X_CUCUMBER_GHERKIN_MARKDOWN"] = "text/x.cucumber.gherkin+markdown";
85
- })(SourceMediaType || (SourceMediaType = {}));
86
- var StepDefinitionPatternType;
87
- (function(StepDefinitionPatternType) {
88
- StepDefinitionPatternType["CUCUMBER_EXPRESSION"] = "CUCUMBER_EXPRESSION";
89
- StepDefinitionPatternType["REGULAR_EXPRESSION"] = "REGULAR_EXPRESSION";
90
- })(StepDefinitionPatternType || (StepDefinitionPatternType = {}));
91
- var StepKeywordType;
92
- (function(StepKeywordType) {
93
- StepKeywordType["UNKNOWN"] = "Unknown";
94
- StepKeywordType["CONTEXT"] = "Context";
95
- StepKeywordType["ACTION"] = "Action";
96
- StepKeywordType["OUTCOME"] = "Outcome";
97
- StepKeywordType["CONJUNCTION"] = "Conjunction";
98
- })(StepKeywordType || (StepKeywordType = {}));
99
- var TestStepResultStatus;
100
- (function(TestStepResultStatus) {
101
- TestStepResultStatus["UNKNOWN"] = "UNKNOWN";
102
- TestStepResultStatus["PASSED"] = "PASSED";
103
- TestStepResultStatus["SKIPPED"] = "SKIPPED";
104
- TestStepResultStatus["PENDING"] = "PENDING";
105
- TestStepResultStatus["UNDEFINED"] = "UNDEFINED";
106
- TestStepResultStatus["AMBIGUOUS"] = "AMBIGUOUS";
107
- TestStepResultStatus["FAILED"] = "FAILED";
108
- })(TestStepResultStatus || (TestStepResultStatus = {}));
109
- var KeywordPrefix;
110
- (function(KeywordPrefix) {
111
- KeywordPrefix["BULLET"] = "^(\\s*[*+-]\\s*)";
112
- KeywordPrefix["HEADER"] = "^(#{1,6}\\s)";
113
- })(KeywordPrefix || (KeywordPrefix = {}));
114
- StepKeywordType.UNKNOWN, PickleStepType.UNKNOWN, StepKeywordType.CONTEXT, PickleStepType.CONTEXT, StepKeywordType.ACTION, PickleStepType.ACTION, StepKeywordType.OUTCOME, PickleStepType.OUTCOME, StepKeywordType.CONJUNCTION;
115
- //#endregion
116
- //#region skills/verify-scenarios/scripts/verify-scenarios.mts
117
- function parseSourcesToml(text) {
118
- const out = [];
119
- const blocks = text.split(/(?=^\s*\[\[source\]\]\s*$)/m).filter((b) => /^\s*\[\[source\]\]\s*$/m.test(b));
120
- for (const block of blocks) {
121
- const body = block.replace(/^\s*\[\[source\]\]\s*$/m, "");
122
- const fields = {};
123
- for (const m of body.matchAll(/^\s*([A-Za-z_][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')\s*$/gm)) fields[m[1]] = m[2] ?? m[3] ?? "";
124
- if (!fields.adapter || !fields.reportPath) continue;
125
- out.push({
126
- adapter: fields.adapter,
127
- command: fields.command,
128
- reportPath: fields.reportPath
129
- });
130
- }
131
- return out;
132
- }
133
- if (process.argv[1] && false);
134
- //#endregion
135
- //#region skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts
136
- const CONFIG_REL_PATH = ".agents/sdd/scenario-bridge.toml";
137
- function configPath(projectPath) {
138
- return join(projectPath, CONFIG_REL_PATH);
139
- }
140
- function listSources(projectPath) {
141
- const file = configPath(projectPath);
142
- if (!existsSync(file)) return [];
143
- try {
144
- return parseSourcesToml(readFileSync(file, "utf8"));
145
- } catch {
146
- return [];
147
- }
148
- }
149
- function renderBlock(source) {
150
- return [
151
- "[[source]]",
152
- `adapter = "${source.adapter}"`,
153
- ...source.command !== void 0 ? [`command = "${source.command}"`] : [],
154
- `reportPath = "${source.reportPath}"`
155
- ].join("\n");
156
- }
157
- function validateInput(input) {
158
- if (!input.adapter || !input.reportPath) return {
159
- ok: false,
160
- reason: "a source needs both adapter and reportPath"
161
- };
162
- return {
163
- ok: true,
164
- adapter: input.adapter,
165
- command: input.command,
166
- reportPath: input.reportPath
167
- };
168
- }
169
- function scaffoldSource(projectPath, input) {
170
- const valid = validateInput(input);
171
- if (!valid.ok) return valid;
172
- const file = configPath(projectPath);
173
- if (existsSync(file)) return {
174
- ok: false,
175
- reason: "scenario-bridge.toml already exists; use add to append a source"
176
- };
177
- mkdirSync(dirname(file), { recursive: true });
178
- writeFileSync(file, `${renderBlock(valid)}\n`);
179
- const sources = listSources(projectPath);
180
- return {
181
- ok: true,
182
- message: `scaffolded ${file}`,
183
- sources
184
- };
185
- }
186
- function addSource(projectPath, input) {
187
- const valid = validateInput(input);
188
- if (!valid.ok) return valid;
189
- const file = configPath(projectPath);
190
- if (!existsSync(file)) return {
191
- ok: false,
192
- reason: "scenario-bridge.toml does not exist yet; use scaffold to create it first"
193
- };
194
- const existing = readFileSync(file, "utf8");
195
- const sep = existing.endsWith("\n") ? "" : "\n";
196
- writeFileSync(file, `${existing}${sep}\n${renderBlock(valid)}\n`);
197
- const sources = listSources(projectPath);
198
- return {
199
- ok: true,
200
- message: `added a source to ${file}`,
201
- sources
202
- };
203
- }
204
- function flag(argv, name) {
205
- const i = argv.indexOf(name);
206
- return i === -1 ? void 0 : argv[i + 1];
207
- }
208
- function inputFromArgv(argv) {
209
- return {
210
- adapter: flag(argv, "--adapter"),
211
- command: flag(argv, "--command"),
212
- reportPath: flag(argv, "--report-path")
213
- };
214
- }
215
- function main(argv) {
216
- const projectPath = flag(argv, "--project-path") ?? ".";
217
- const w = (s) => process.stdout.write(`${s}\n`);
218
- if (argv.includes("--list")) {
219
- for (const s of listSources(projectPath)) w(`${s.adapter}${s.command ? ` (${s.command})` : ""} -> ${s.reportPath}`);
220
- return 0;
221
- }
222
- if (argv.includes("--scaffold")) {
223
- const r = scaffoldSource(projectPath, inputFromArgv(argv));
224
- w(r.ok ? r.message : `refused: ${r.reason}`);
225
- return r.ok ? 0 : 1;
226
- }
227
- if (argv.includes("--add")) {
228
- const r = addSource(projectPath, inputFromArgv(argv));
229
- w(r.ok ? r.message : `refused: ${r.reason}`);
230
- return r.ok ? 0 : 1;
231
- }
232
- w("usage: manage-scenario-bridge --project-path <dir> --list | --scaffold --adapter <a> [--command <c>] --report-path <p> | --add --adapter <a> [--command <c>] --report-path <p>");
233
- return 0;
234
- }
235
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
236
- //#endregion
237
- export { addSource, configPath, listSources, main, scaffoldSource };
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env node
2
- // manage-scenario-bridge — the concrete curation engine for `<project-path>/.agents/sdd/scenario-bridge.toml`,
3
- // the one-time per-project wiring the verify-scenarios bridge and the impl-judge step-0 consumption
4
- // both read. It reads/writes ONLY that one project's scenario-bridge.toml — never a spec.md, status,
5
- // approval, or a freeze; it never runs the bridge or a test.
6
- //
7
- // Operations:
8
- // --list print every configured [[source]] block in order
9
- // --scaffold --adapter <a> [--command <c>] --report-path <p>
10
- // create the config with one source block; refused
11
- // when the file already exists
12
- // --add --adapter <a> [--command <c>] --report-path <p>
13
- // append a source block to an existing config;
14
- // refused when the file does not exist
15
- //
16
- // Every read/write is resolved beneath --project-path (a colocated project's project-path is its own
17
- // repo root, so the config lands at the familiar .agents/sdd/scenario-bridge.toml there).
18
- //
19
- // Reuses the verify-scenarios bridge's own SourceConfig/parseSourcesToml shape — never diverge the
20
- // format the reader (verify-scenarios) and the writer (this engine) agree on.
21
- //
22
- // Pure functions are exported for node:test; running the file directly drives the CLI. No deps (the
23
- // repo's node-≥23.6 convention).
24
-
25
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
26
- import { dirname, join } from 'node:path'
27
- import { pathToFileURL } from 'node:url'
28
- import { parseSourcesToml, type SourceConfig } from '../../verify-scenarios/scripts/verify-scenarios.mts'
29
-
30
- export type { SourceConfig }
31
-
32
- const CONFIG_REL_PATH = '.agents/sdd/scenario-bridge.toml'
33
-
34
- export function configPath(projectPath: string): string {
35
- return join(projectPath, CONFIG_REL_PATH)
36
- }
37
-
38
- // ── list ──
39
-
40
- // Every configured source in file order; a missing config lists nothing without error.
41
- export function listSources(projectPath: string): SourceConfig[] {
42
- const file = configPath(projectPath)
43
- if (!existsSync(file)) return []
44
- try {
45
- return parseSourcesToml(readFileSync(file, 'utf8'))
46
- } catch {
47
- return []
48
- }
49
- }
50
-
51
- // ── render one [[source]] block ──
52
-
53
- function renderBlock(source: { adapter: string; command?: string; reportPath: string }): string {
54
- const lines = [
55
- '[[source]]',
56
- `adapter = "${source.adapter}"`,
57
- ...(source.command !== undefined ? [`command = "${source.command}"`] : []),
58
- `reportPath = "${source.reportPath}"`,
59
- ]
60
- return lines.join('\n')
61
- }
62
-
63
- // ── scaffold / add ──
64
-
65
- export interface NewSourceInput {
66
- adapter?: string
67
- command?: string
68
- reportPath?: string
69
- }
70
-
71
- export type MutateResult = { ok: true; message: string; sources: SourceConfig[] } | { ok: false; reason: string }
72
-
73
- function validateInput(
74
- input: NewSourceInput,
75
- ): { ok: true; adapter: string; command?: string; reportPath: string } | { ok: false; reason: string } {
76
- if (!input.adapter || !input.reportPath) {
77
- return { ok: false, reason: 'a source needs both adapter and reportPath' }
78
- }
79
- return { ok: true, adapter: input.adapter, command: input.command, reportPath: input.reportPath }
80
- }
81
-
82
- // Creates <project-path>/.agents/sdd/scenario-bridge.toml with one [[source]] block. Refuses when the
83
- // file already exists (use `add` instead) or when adapter/reportPath is missing — no source block is
84
- // written in the refused case.
85
- export function scaffoldSource(projectPath: string, input: NewSourceInput): MutateResult {
86
- const valid = validateInput(input)
87
- if (!valid.ok) return valid
88
- const file = configPath(projectPath)
89
- if (existsSync(file)) {
90
- return { ok: false, reason: 'scenario-bridge.toml already exists; use add to append a source' }
91
- }
92
- mkdirSync(dirname(file), { recursive: true })
93
- writeFileSync(file, `${renderBlock(valid)}\n`)
94
- const sources = listSources(projectPath)
95
- return { ok: true, message: `scaffolded ${file}`, sources }
96
- }
97
-
98
- // Appends a [[source]] block to an existing config, leaving the existing sources untouched. Refuses
99
- // when the config does not exist yet (names scaffold as the entry point) or when adapter/reportPath
100
- // is missing.
101
- export function addSource(projectPath: string, input: NewSourceInput): MutateResult {
102
- const valid = validateInput(input)
103
- if (!valid.ok) return valid
104
- const file = configPath(projectPath)
105
- if (!existsSync(file)) {
106
- return { ok: false, reason: 'scenario-bridge.toml does not exist yet; use scaffold to create it first' }
107
- }
108
- const existing = readFileSync(file, 'utf8')
109
- const sep = existing.endsWith('\n') ? '' : '\n'
110
- writeFileSync(file, `${existing}${sep}\n${renderBlock(valid)}\n`)
111
- const sources = listSources(projectPath)
112
- return { ok: true, message: `added a source to ${file}`, sources }
113
- }
114
-
115
- // ── CLI ──
116
-
117
- function flag(argv: string[], name: string): string | undefined {
118
- const i = argv.indexOf(name)
119
- return i === -1 ? undefined : argv[i + 1]
120
- }
121
-
122
- function inputFromArgv(argv: string[]): NewSourceInput {
123
- return {
124
- adapter: flag(argv, '--adapter'),
125
- command: flag(argv, '--command'),
126
- reportPath: flag(argv, '--report-path'),
127
- }
128
- }
129
-
130
- export function main(argv: string[]): number {
131
- const projectPath = flag(argv, '--project-path') ?? '.'
132
- const w = (s: string) => process.stdout.write(`${s}\n`)
133
-
134
- if (argv.includes('--list')) {
135
- for (const s of listSources(projectPath)) {
136
- w(`${s.adapter}${s.command ? ` (${s.command})` : ''} -> ${s.reportPath}`)
137
- }
138
- return 0
139
- }
140
- if (argv.includes('--scaffold')) {
141
- const r = scaffoldSource(projectPath, inputFromArgv(argv))
142
- w(r.ok ? r.message : `refused: ${r.reason}`)
143
- return r.ok ? 0 : 1
144
- }
145
- if (argv.includes('--add')) {
146
- const r = addSource(projectPath, inputFromArgv(argv))
147
- w(r.ok ? r.message : `refused: ${r.reason}`)
148
- return r.ok ? 0 : 1
149
- }
150
-
151
- w(
152
- 'usage: manage-scenario-bridge --project-path <dir> --list | --scaffold --adapter <a> [--command <c>] --report-path <p> | --add --adapter <a> [--command <c>] --report-path <p>',
153
- )
154
- return 0
155
- }
156
-
157
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
158
- process.exit(main(process.argv.slice(2)))
159
- }