cyber-sdd 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -2
- package/.codex-plugin/plugin.json +1 -2
- package/CHANGELOG.md +320 -0
- package/agents/sdd-scanner.md +1 -1
- package/package.json +11 -12
- package/plugin.json +55 -0
- package/skills/align-spec/SKILL.md +1 -1
- package/skills/align-spec/scripts/align-spec.mjs +6755 -12
- package/skills/blast-estimate/README.md +3 -3
- package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -12
- package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -12
- package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -12
- package/skills/check-plan-safety/SKILL.md +2 -3
- package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -12
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -12
- package/skills/check-project-specs/SKILL.md +1 -1
- package/skills/check-project-specs/scripts/check-project-specs.mjs +593 -12
- package/skills/check-retired-terms/SKILL.md +2 -3
- package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -12
- package/skills/check-scenario-overlap/SKILL.md +1 -2
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +177 -12
- package/skills/check-spec-references/SKILL.md +2 -2
- package/skills/check-spec-references/scripts/check-spec-references.mjs +287 -12
- package/skills/check-spec-structure/SKILL.md +1 -2
- package/skills/check-spec-structure/scripts/check-spec-structure.mjs +243 -12
- package/skills/collision-ladder/README.md +2 -2
- package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -12
- package/skills/combat-log-governance/SKILL.md +1 -1
- package/skills/concept-index/SKILL.md +1 -1
- package/skills/concept-index/scripts/concept-index.mjs +183 -12
- package/skills/discover-plans/README.md +3 -3
- package/skills/discover-plans/SKILL.md +1 -1
- package/skills/discover-plans/scripts/discover-plans.mjs +137 -12
- package/skills/discover-specs/README.md +3 -3
- package/skills/discover-specs/SKILL.md +1 -1
- package/skills/discover-specs/scripts/discover-specs.mjs +321 -12
- package/skills/doctrine-loop/SKILL.md +1 -1
- package/skills/gate-validation-governance/SKILL.md +1 -1
- package/skills/init/README.md +2 -2
- package/skills/init/scripts/wire-statusline.mjs +168 -12
- package/skills/manage-ignore/README.md +3 -3
- package/skills/manage-ignore/SKILL.md +1 -2
- package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -12
- package/skills/manage-scenario-bridge/README.md +3 -3
- package/skills/manage-scenario-bridge/SKILL.md +1 -1
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +239 -12
- package/skills/manage-spec-anchors/README.md +3 -3
- package/skills/manage-spec-anchors/SKILL.md +1 -1
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -12
- package/skills/mission-graph/README.md +2 -2
- package/skills/mission-graph/SKILL.md +1 -1
- package/skills/mission-graph/scripts/mission-graph.mjs +1040 -12
- package/skills/pause-mission/SKILL.md +1 -1
- package/skills/place-node/SKILL.md +2 -2
- package/skills/place-node/scripts/place-node.mjs +117 -12
- package/skills/plan-retirement/README.md +2 -2
- package/skills/plan-retirement/scripts/retire-plans.mjs +123 -12
- package/skills/resolve-governances/README.md +3 -3
- package/skills/resolve-governances/SKILL.md +1 -1
- package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -12
- package/skills/resolve-tracking/SKILL.md +1 -1
- package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -12
- package/skills/scaffold-project-spec/SKILL.md +1 -1
- package/skills/spec-gate/README.md +12 -9
- package/skills/spec-gate/SKILL.md +2 -2
- package/skills/spec-gate/scripts/check-spec-state.mjs +422 -12
- package/skills/spec-gate/scripts/check-suite.mjs +6500 -12
- package/skills/spec-gate/scripts/classify-edit-class.mjs +6579 -12
- package/skills/spec-producer-governance/SKILL.md +2 -2
- package/skills/start-mission/SKILL.md +1 -1
- package/skills/suite-format-governance/README.md +1 -1
- package/skills/suite-format-governance/SKILL.md +1 -1
- package/skills/touch-set-correction/README.md +5 -5
- package/skills/touch-set-correction/SKILL.md +1 -1
- package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -12
- package/skills/verify-scenarios/README.md +6 -6
- package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -12
- package/.plugin/pins.json +0 -3
- package/.plugin/plugin.json +0 -19
- package/dist/align-spec/scripts/align-spec.js +0 -102
- package/dist/check-project-specs/scripts/check-project-specs.js +0 -312
- package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +0 -175
- package/dist/check-spec-references/scripts/check-spec-references.js +0 -285
- package/dist/check-spec-structure/scripts/check-spec-structure.js +0 -241
- package/dist/concept-index/scripts/concept-index.js +0 -181
- package/dist/discover-specs/scripts/discover-specs.js +0 -318
- package/dist/spec-gate/scripts/check-spec-state.js +0 -420
- package/dist/spec-gate/scripts/check-suite.js +0 -317
- package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
- package/dist/spec-gate/vendor/gherkin-cli.js +0 -6369
- package/skills/align-spec/scripts/align-spec.bundle.mjs +0 -6753
- package/skills/align-spec/scripts/align-spec.mts +0 -188
- package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +0 -593
- package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
- package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +0 -251
- package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
- package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +0 -233
- package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
- package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +0 -107
- package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +0 -248
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
- package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +0 -584
- package/skills/check-project-specs/scripts/check-project-specs.mts +0 -370
- package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +0 -184
- package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +0 -175
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
- package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +0 -285
- package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
- package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +0 -241
- package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
- package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +0 -7041
- package/skills/collision-ladder/scripts/collision-ladder.mts +0 -662
- package/skills/concept-index/scripts/concept-index.bundle.mjs +0 -181
- package/skills/concept-index/scripts/concept-index.mts +0 -246
- package/skills/discover-plans/scripts/discover-plans.bundle.mjs +0 -135
- package/skills/discover-plans/scripts/discover-plans.mts +0 -215
- package/skills/discover-specs/scripts/discover-specs.bundle.mjs +0 -319
- package/skills/discover-specs/scripts/discover-specs.mts +0 -420
- package/skills/init/scripts/wire-statusline.bundle.mjs +0 -166
- package/skills/init/scripts/wire-statusline.mts +0 -279
- package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +0 -244
- package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +0 -237
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +0 -308
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
- package/skills/mission-graph/scripts/mission-graph.bundle.mjs +0 -1038
- package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
- package/skills/place-node/scripts/place-node.bundle.mjs +0 -115
- package/skills/place-node/scripts/place-node.mts +0 -158
- package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +0 -121
- package/skills/plan-retirement/scripts/retire-plans.mts +0 -205
- package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +0 -350
- package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
- package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +0 -150
- package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
- package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +0 -420
- package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
- package/skills/spec-gate/scripts/check-suite.bundle.mjs +0 -6498
- package/skills/spec-gate/scripts/check-suite.mts +0 -538
- package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +0 -6577
- package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
- package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
- package/skills/spec-gate/vendor/gherkin-cli.mjs +0 -6545
- package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +0 -6655
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -429
- package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +0 -6537
- package/skills/verify-scenarios/scripts/verify-scenarios.mts +0 -470
|
@@ -1,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
|
-
}
|