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,279 +0,0 @@
1
- #!/usr/bin/env node
2
- // wire-statusline — gateway/init's concrete wiring engine for the mission statusline. It reads/writes
3
- // ONLY project `.claude/settings.json` (never the global `~/.claude/settings.json`) and, only when the
4
- // root is a git repo, `.gitignore`. It never touches spec/contract state (status, approval, spec.md).
5
- // It also READS the global `~/.claude/settings.json` (or an override path) to detect and, on a fresh
6
- // wire, compose its statusLine command as the wrapped base — read-only, never written.
7
- //
8
- // Operation:
9
- // --wire --mode own-line|same-line compose the SDD statusLine segment into project settings and
10
- // (in a git repo) add the status file to .gitignore; idempotent
11
- // [--global-settings <file>] [--no-global-base]
12
- // --detect [--global-settings <file>] read-only: report project/global statusLine state and shadow risk
13
- //
14
- // The wired command reads the single-line status file `.agents/sdd/statusline` the conductor writes
15
- // (`../../mission/conductor/`) and renders it — own-line (a new row) or same-line (an appended
16
- // segment) — falling through to nothing (or the composed base, unchanged) when the file is absent.
17
- // Compose, never stomp: an existing `statusLine` command is preserved as the wrapped "base"; the SDD
18
- // segment is added around it. Re-wiring (same or a different mode) rewrites the one managed block —
19
- // it never stacks a second SDD segment.
20
- //
21
- // Global shadow (issue #164): Claude Code's project statusLine REPLACES (never merges with) the
22
- // global one. When the project has no statusLine of its own, a fresh wire composes the global
23
- // command as the base by default so the global line keeps rendering instead of going blank. A
24
- // re-run of an already-wired (or foreign) project command never re-consults the global — only a
25
- // fresh wire ever adopts it.
26
- // Pure functions are exported for node:test; running the file directly drives the CLI. No deps.
27
-
28
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
29
- import { homedir } from 'node:os'
30
- import { dirname, join } from 'node:path'
31
- import { pathToFileURL } from 'node:url'
32
-
33
- export const STATUS_FILE = '.agents/sdd/statusline'
34
- export const SETTINGS_FILE = '.claude/settings.json'
35
- export const GITIGNORE_FILE = '.gitignore'
36
- export const GLOBAL_SETTINGS_FILE = join(homedir(), '.claude', 'settings.json')
37
-
38
- export type StatusLineMode = 'own-line' | 'same-line'
39
-
40
- const MARKER_BEGIN = '# sdd-statusline:begin'
41
- const MARKER_END = '# sdd-statusline:end'
42
- const MODE_PREFIX = '# sdd-statusline:mode:'
43
- const ORIG_OPEN = '__sdd_orig() {'
44
- const ORIG_CLOSE = '}'
45
- const NO_BASE = ':'
46
-
47
- // ── the managed command block ──
48
-
49
- // Build the self-contained POSIX sh command that reads the status file and renders it in `mode`,
50
- // wrapping `base` (the pre-existing statusLine command, if any) so its output is preserved.
51
- export function buildCommand(base: string | undefined, mode: StatusLineMode): string {
52
- const origBody = base && base.trim() !== '' ? base : NO_BASE
53
- const render =
54
- mode === 'own-line'
55
- ? `if [ -s "$__sdd_file" ]; then\n __sdd_val="$(cat "$__sdd_file")"\n printf '%s\\n%s\\n' "$__sdd_base" "$__sdd_val"\nelse\n printf '%s\\n' "$__sdd_base"\nfi`
56
- : `if [ -s "$__sdd_file" ]; then\n __sdd_val="$(cat "$__sdd_file")"\n printf '%s | %s\\n' "$__sdd_base" "$__sdd_val"\nelse\n printf '%s\\n' "$__sdd_base"\nfi`
57
- return [
58
- MARKER_BEGIN,
59
- `${MODE_PREFIX}${mode}`,
60
- ORIG_OPEN,
61
- origBody,
62
- ORIG_CLOSE,
63
- '__sdd_base="$(__sdd_orig 2>/dev/null)"',
64
- `__sdd_file="${STATUS_FILE}"`,
65
- render,
66
- MARKER_END,
67
- ].join('\n')
68
- }
69
-
70
- export interface ParsedCommand {
71
- wired: boolean
72
- base?: string
73
- mode?: StatusLineMode
74
- }
75
-
76
- // Recognize a previously wired command and recover its wrapped base + mode, so re-wiring rebuilds the
77
- // same managed block instead of stacking a second one. A command with no marker is not wired — the
78
- // whole string becomes the base to wrap.
79
- export function parseCommand(command: string | undefined): ParsedCommand {
80
- if (!command?.includes(MARKER_BEGIN)) return { wired: false }
81
- const modeMatch = new RegExp(`${MODE_PREFIX}(own-line|same-line)`).exec(command)
82
- const origMatch = /__sdd_orig\(\) \{\n([\s\S]*?)\n\}/.exec(command)
83
- const mode = (modeMatch?.[1] as StatusLineMode | undefined) ?? 'own-line'
84
- const origBody = origMatch?.[1] ?? NO_BASE
85
- return { wired: true, base: origBody === NO_BASE ? undefined : origBody, mode }
86
- }
87
-
88
- // The command to wrap as `base` when composing: an already-wired command hands back its recovered
89
- // original base (never nests); any other command is the base verbatim.
90
- function baseToWrap(existingCommand: string | undefined): string | undefined {
91
- const parsed = parseCommand(existingCommand)
92
- return parsed.wired ? parsed.base : existingCommand
93
- }
94
-
95
- // ── project settings.json ──
96
-
97
- interface StatusLineConfig {
98
- type: string
99
- command: string
100
- [key: string]: unknown
101
- }
102
-
103
- interface Settings {
104
- statusLine?: StatusLineConfig
105
- [key: string]: unknown
106
- }
107
-
108
- function readSettingsFile(file: string): Settings {
109
- if (!existsSync(file)) return {}
110
- try {
111
- return JSON.parse(readFileSync(file, 'utf8')) as Settings
112
- } catch {
113
- return {}
114
- }
115
- }
116
-
117
- function readSettings(root: string): Settings {
118
- return readSettingsFile(join(root, SETTINGS_FILE))
119
- }
120
-
121
- // Read a settings JSON file's statusLine.command, read-only. Absent file, unparseable JSON, or a
122
- // missing/non-string/blank command all collapse to undefined — a malformed global settings file is
123
- // treated the same as no global statusLine (frozen scenario).
124
- export function readStatusLineCommand(file: string): string | undefined {
125
- const settings = readSettingsFile(file)
126
- const command = settings.statusLine?.command
127
- if (typeof command !== 'string' || command.trim() === '') return undefined
128
- return command
129
- }
130
-
131
- function writeSettings(root: string, settings: Settings): void {
132
- const file = join(root, SETTINGS_FILE)
133
- mkdirSync(dirname(file), { recursive: true })
134
- writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`)
135
- }
136
-
137
- export interface ComposeResult {
138
- changed: boolean
139
- command: string
140
- globalBaseComposed: boolean
141
- }
142
-
143
- // Compose the SDD statusLine segment into project settings.json. Preserves any existing statusLine
144
- // command's output as the wrapped base (never overwrites it); creates the key when absent. Rewiring
145
- // the same mode over an already-wired command is a no-op (byte-identical command).
146
- //
147
- // Base selection: when the project settings define no usable `statusLine` command (the same
148
- // predicate detectStatusLines reports as `absent`), this is a fresh wire — the base is
149
- // `globalCommand` (possibly undefined). When a usable project `statusLine` already exists (foreign
150
- // or already wired), today's behavior is unchanged — the existing command's recovered base is used
151
- // and `globalCommand` is ignored entirely, so a re-run never re-consults or adopts the global.
152
- export function composeStatusLine(root: string, mode: StatusLineMode, globalCommand?: string): ComposeResult {
153
- const settings = readSettings(root)
154
- const existing = settings.statusLine?.command
155
- const hasProjectStatusLine = typeof existing === 'string' && existing.trim() !== ''
156
- const globalBaseComposed = !hasProjectStatusLine && globalCommand !== undefined
157
- const base = hasProjectStatusLine ? baseToWrap(existing) : globalCommand
158
- const command = buildCommand(base, mode)
159
- if (existing === command) return { changed: false, command, globalBaseComposed: false }
160
- settings.statusLine = { ...settings.statusLine, type: 'command', command }
161
- writeSettings(root, settings)
162
- return { changed: true, command, globalBaseComposed }
163
- }
164
-
165
- // ── .gitignore ──
166
-
167
- export function isGitRepo(root: string): boolean {
168
- return existsSync(join(root, '.git'))
169
- }
170
-
171
- function splitLines(text: string): string[] {
172
- const lines = text.split('\n')
173
- if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
174
- return lines
175
- }
176
-
177
- // Idempotently add the status file to .gitignore (creating it if absent). Only called when the root
178
- // is a git repo; a no-op when the entry is already present.
179
- export function addGitignoreEntry(root: string): { changed: boolean } {
180
- const file = join(root, GITIGNORE_FILE)
181
- const lines = existsSync(file) ? splitLines(readFileSync(file, 'utf8')) : []
182
- if (lines.some((l) => l.trim() === STATUS_FILE)) return { changed: false }
183
- const next = [...lines, STATUS_FILE]
184
- writeFileSync(file, `${next.join('\n')}\n`)
185
- return { changed: true }
186
- }
187
-
188
- // ── global statusline detection ──
189
-
190
- export interface DetectResult {
191
- project: 'absent' | 'wired' | 'foreign'
192
- global: 'absent' | 'present'
193
- shadow: boolean
194
- }
195
-
196
- // Read-only: report the project statusLine state, the global statusLine state, and whether wiring
197
- // would shadow a global statusLine that is currently rendering (project has none, global has one).
198
- // Never writes anything — used to surface the shadow risk before wiring and by `--detect`.
199
- export function detectStatusLines(root: string, globalSettingsFile?: string): DetectResult {
200
- const projectCommand = readStatusLineCommand(join(root, SETTINGS_FILE))
201
- const project = projectCommand === undefined ? 'absent' : parseCommand(projectCommand).wired ? 'wired' : 'foreign'
202
- const global = readStatusLineCommand(globalSettingsFile ?? GLOBAL_SETTINGS_FILE) !== undefined ? 'present' : 'absent'
203
- const shadow = project === 'absent' && global === 'present'
204
- return { project, global, shadow }
205
- }
206
-
207
- // ── top-level wire ──
208
-
209
- export interface WireResult {
210
- settings: ComposeResult
211
- gitignore: { changed: boolean; skipped: boolean }
212
- }
213
-
214
- export interface WireOptions {
215
- globalSettingsFile?: string
216
- globalBase?: boolean
217
- }
218
-
219
- // Wire the reader into project settings and (only in a git repo) gitignore the status file. The
220
- // single entry point init's SKILL.md calls on consent. `globalBase` (default true) controls whether
221
- // a fresh wire composes the global statusLine command as the base; `globalSettingsFile` overrides
222
- // the default global settings path (for tests, or a non-default HOME).
223
- export function wireStatusline(root: string, mode: StatusLineMode, opts?: WireOptions): WireResult {
224
- const globalBase = opts?.globalBase ?? true
225
- const globalCommand = globalBase ? readStatusLineCommand(opts?.globalSettingsFile ?? GLOBAL_SETTINGS_FILE) : undefined
226
- const settings = composeStatusLine(root, mode, globalCommand)
227
- const gitignore = isGitRepo(root) ? { ...addGitignoreEntry(root), skipped: false } : { changed: false, skipped: true }
228
- return { settings, gitignore }
229
- }
230
-
231
- // ── CLI ──
232
-
233
- function flag(argv: string[], name: string): string | undefined {
234
- const i = argv.indexOf(name)
235
- return i === -1 ? undefined : argv[i + 1]
236
- }
237
-
238
- export function main(argv: string[]): number {
239
- const root = flag(argv, '--root') ?? '.'
240
- const globalSettingsFile = flag(argv, '--global-settings')
241
- const w = (s: string) => process.stdout.write(`${s}\n`)
242
-
243
- if (argv.includes('--detect')) {
244
- const result = detectStatusLines(root, globalSettingsFile)
245
- w(`project statusLine: ${result.project}`)
246
- w(`global statusLine: ${result.global}`)
247
- w(
248
- result.shadow
249
- ? 'shadow risk: yes — wiring would shadow the global statusline; compose it as the base (default) or pass --no-global-base to shadow deliberately'
250
- : 'shadow risk: no',
251
- )
252
- return 0
253
- }
254
-
255
- if (argv.includes('--wire')) {
256
- const mode = flag(argv, '--mode')
257
- if (mode !== 'own-line' && mode !== 'same-line') {
258
- w('refused: --mode must be own-line or same-line')
259
- return 1
260
- }
261
- const globalBase = !argv.includes('--no-global-base')
262
- const result = wireStatusline(root, mode, { globalSettingsFile, globalBase })
263
- w(result.settings.changed ? `wired statusLine (${mode})` : 'statusLine already wired (unchanged)')
264
- if (result.settings.globalBaseComposed) w('composed the global statusLine command as the wrapped base')
265
- if (result.gitignore.skipped) w('not a git repo — gitignore skipped')
266
- else w(result.gitignore.changed ? `added ${STATUS_FILE} to .gitignore` : '.gitignore already up to date')
267
- return 0
268
- }
269
-
270
- w(
271
- 'usage: wire-statusline --root <dir> --wire --mode own-line|same-line [--global-settings <file>] [--no-global-base]\n' +
272
- ' wire-statusline --root <dir> --detect [--global-settings <file>]',
273
- )
274
- return 0
275
- }
276
-
277
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
278
- process.exit(main(process.argv.slice(2)))
279
- }
@@ -1,244 +0,0 @@
1
- #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- //#region skills/manage-ignore/scripts/manage-ignore.mts
6
- const IGNORE_FILE = ".agents/sdd/.sddignore";
7
- const SKIP_DIRS = /* @__PURE__ */ new Set([
8
- "node_modules",
9
- ".git",
10
- "dist",
11
- ".turbo",
12
- ".next",
13
- "coverage"
14
- ]);
15
- function splitLines(text) {
16
- const lines = text.split("\n");
17
- if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
18
- return lines;
19
- }
20
- function readIgnoreLines(root) {
21
- const file = join(root, IGNORE_FILE);
22
- if (!existsSync(file)) return [];
23
- try {
24
- return splitLines(readFileSync(file, "utf8"));
25
- } catch {
26
- return [];
27
- }
28
- }
29
- function writeIgnoreLines(root, lines) {
30
- const file = join(root, IGNORE_FILE);
31
- mkdirSync(dirname(file), { recursive: true });
32
- writeFileSync(file, lines.length === 0 ? "" : `${lines.join("\n")}\n`);
33
- }
34
- function isRuleLine(line) {
35
- const t = line.trim();
36
- return t !== "" && !t.startsWith("#");
37
- }
38
- function listRules(root) {
39
- return readIgnoreLines(root).filter(isRuleLine).map((l) => l.trim());
40
- }
41
- function hasUnclosedBracket(s) {
42
- let i = 0;
43
- while (i < s.length) {
44
- if (s[i] === "\\") {
45
- i += 2;
46
- continue;
47
- }
48
- if (s[i] === "[") {
49
- const close = s.indexOf("]", i + 1);
50
- if (close === -1) return true;
51
- i = close + 1;
52
- continue;
53
- }
54
- i++;
55
- }
56
- return false;
57
- }
58
- function isValidPattern(pattern) {
59
- let s = pattern;
60
- if (s.startsWith("!")) s = s.slice(1);
61
- s = s.replace(/^\s+|\s+$/g, "");
62
- if (s === "" || s.startsWith("#")) return false;
63
- if (hasUnclosedBracket(s)) return false;
64
- return true;
65
- }
66
- function addRule(root, pattern) {
67
- const p = pattern.trim();
68
- if (!isValidPattern(p)) return {
69
- ok: false,
70
- reason: "malformed gitignore pattern"
71
- };
72
- const lines = readIgnoreLines(root);
73
- if (lines.some((l) => l.trim() === p)) return {
74
- ok: true,
75
- changed: false,
76
- rules: listRules(root),
77
- message: "already present"
78
- };
79
- const next = [...lines, p];
80
- writeIgnoreLines(root, next);
81
- return {
82
- ok: true,
83
- changed: true,
84
- rules: next.filter(isRuleLine).map((l) => l.trim()),
85
- message: `added ${p}`
86
- };
87
- }
88
- function removeRule(root, pattern) {
89
- const p = pattern.trim();
90
- const lines = readIgnoreLines(root);
91
- const next = lines.filter((l) => l.trim() !== p);
92
- if (next.length === lines.length) return {
93
- ok: true,
94
- changed: false,
95
- rules: listRules(root),
96
- message: "nothing was removed"
97
- };
98
- writeIgnoreLines(root, next);
99
- return {
100
- ok: true,
101
- changed: true,
102
- rules: next.filter(isRuleLine).map((l) => l.trim()),
103
- message: `removed ${p}`
104
- };
105
- }
106
- function inducePatterns(_root, samplePath) {
107
- const normalized = samplePath.trim().replace(/\\/g, "/");
108
- if (normalized.startsWith("/")) return {
109
- ok: false,
110
- reason: "path is not repo-relative (absolute)"
111
- };
112
- const rel = normalized.replace(/\/+$/g, "");
113
- if (rel === "" || /(^|\/)\.\.(\/|$)/.test(rel)) return {
114
- ok: false,
115
- reason: "path is not repo-relative"
116
- };
117
- const generalized = `**/${rel.split("/").pop() ?? rel}`;
118
- return {
119
- ok: true,
120
- candidates: generalized === rel ? [rel] : [rel, generalized]
121
- };
122
- }
123
- function gitignoreToRegExp(pattern) {
124
- let pat = pattern;
125
- if (pat.endsWith("/")) pat = pat.slice(0, -1);
126
- let anchored = pat.startsWith("/");
127
- if (anchored) pat = pat.slice(1);
128
- if (pat.includes("/")) anchored = true;
129
- let body = "";
130
- for (let i = 0; i < pat.length; i++) {
131
- const c = pat[i];
132
- if (c === "*") {
133
- if (pat[i + 1] === "*") {
134
- i++;
135
- if (pat[i + 1] === "/") {
136
- i++;
137
- body += "(?:.*/)?";
138
- } else body += ".*";
139
- } else body += "[^/]*";
140
- } else if (c === "?") body += "[^/]";
141
- else if (c === "[") {
142
- let j = i + 1;
143
- let cls = "[";
144
- if (pat[j] === "!") {
145
- cls += "^";
146
- j++;
147
- }
148
- while (j < pat.length && pat[j] !== "]") {
149
- cls += /[\\^\]]/.test(pat[j]) ? `\\${pat[j]}` : pat[j];
150
- j++;
151
- }
152
- cls += "]";
153
- body += cls;
154
- i = j;
155
- } else body += c.replace(/[.+^${}()|\\]/g, "\\$&");
156
- }
157
- return new RegExp(`${anchored ? "^" : "^(?:.*/)?"}${body}(?:/.*)?$`);
158
- }
159
- function matchesPattern(pattern, path) {
160
- return gitignoreToRegExp(pattern.startsWith("!") ? pattern.slice(1) : pattern).test(path);
161
- }
162
- function walkTree(root) {
163
- const out = [];
164
- const walk = (rel) => {
165
- let entries;
166
- try {
167
- entries = readdirSync(join(root, rel), { withFileTypes: true });
168
- } catch {
169
- return;
170
- }
171
- for (const e of entries) {
172
- if (SKIP_DIRS.has(e.name)) continue;
173
- const child = rel ? `${rel}/${e.name}` : e.name;
174
- out.push(child);
175
- if (e.isDirectory()) walk(child);
176
- }
177
- };
178
- walk("");
179
- return out.sort();
180
- }
181
- function previewPattern(root, pattern) {
182
- const p = pattern.trim();
183
- if (!isValidPattern(p)) return {
184
- ok: false,
185
- reason: "malformed gitignore pattern"
186
- };
187
- return {
188
- ok: true,
189
- negated: p.startsWith("!"),
190
- matches: walkTree(root).filter((path) => matchesPattern(p, path))
191
- };
192
- }
193
- function flag(argv, name) {
194
- const i = argv.indexOf(name);
195
- return i === -1 ? void 0 : argv[i + 1];
196
- }
197
- function main(argv) {
198
- const root = flag(argv, "--root") ?? ".";
199
- const w = (s) => process.stdout.write(`${s}\n`);
200
- if (argv.includes("--list")) {
201
- for (const rule of listRules(root)) w(rule);
202
- return 0;
203
- }
204
- if (argv.includes("--add")) {
205
- const r = addRule(root, flag(argv, "--add") ?? "");
206
- w(r.ok ? r.message : `refused: ${r.reason}`);
207
- return r.ok ? 0 : 1;
208
- }
209
- if (argv.includes("--remove")) {
210
- const r = removeRule(root, flag(argv, "--remove") ?? "");
211
- w(r.ok ? r.message : `refused: ${r.reason}`);
212
- return r.ok ? 0 : 1;
213
- }
214
- if (argv.includes("--induce")) {
215
- const r = inducePatterns(root, flag(argv, "--induce") ?? "");
216
- if (!r.ok) {
217
- w(`unusable: ${r.reason}`);
218
- return 1;
219
- }
220
- w("Candidate ignore patterns:");
221
- for (const c of r.candidates) w(` ${c}`);
222
- return 0;
223
- }
224
- if (argv.includes("--preview")) {
225
- const r = previewPattern(root, flag(argv, "--preview") ?? "");
226
- if (!r.ok) {
227
- w(`invalid: ${r.reason}`);
228
- return 1;
229
- }
230
- const verb = r.negated ? "re-track" : "ignore";
231
- if (r.matches.length === 0) {
232
- w(`This pattern would ${verb} no path.`);
233
- return 0;
234
- }
235
- w(`This pattern would ${verb}:`);
236
- for (const m of r.matches) w(` ${m}`);
237
- return 0;
238
- }
239
- w("usage: manage-ignore --list | --add <p> | --remove <p> | --induce <path> | --preview <p>");
240
- return 0;
241
- }
242
- if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
243
- //#endregion
244
- export { addRule, gitignoreToRegExp, inducePatterns, isRuleLine, isValidPattern, listRules, main, matchesPattern, previewPattern, readIgnoreLines, removeRule, splitLines };