cyber-sdd 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.claude-plugin/plugin.json +17 -0
  2. package/.codex-plugin/plugin.json +17 -0
  3. package/.plugin/plugin.json +17 -0
  4. package/README.md +159 -0
  5. package/agents/sdd-automaton.md +97 -0
  6. package/agents/sdd-impl-judge.md +214 -0
  7. package/agents/sdd-scanner.md +120 -0
  8. package/agents/sdd-spec-judge.md +224 -0
  9. package/agents/sdd-warden.md +101 -0
  10. package/package.json +24 -0
  11. package/skills/align-spec/README.md +20 -0
  12. package/skills/align-spec/SKILL.md +111 -0
  13. package/skills/align-spec/scripts/align-spec.mts +187 -0
  14. package/skills/architect-impl-governance/README.md +46 -0
  15. package/skills/architect-impl-governance/SKILL.md +45 -0
  16. package/skills/architect-spec-governance/README.md +48 -0
  17. package/skills/architect-spec-governance/SKILL.md +59 -0
  18. package/skills/blast-estimate/README.md +47 -0
  19. package/skills/blast-estimate/SKILL.md +133 -0
  20. package/skills/blast-estimate/scripts/blast-estimate.mts +583 -0
  21. package/skills/builder-impl-governance/README.md +47 -0
  22. package/skills/builder-impl-governance/SKILL.md +47 -0
  23. package/skills/builder-spec-governance/README.md +49 -0
  24. package/skills/builder-spec-governance/SKILL.md +36 -0
  25. package/skills/check-partition-quality/README.md +22 -0
  26. package/skills/check-partition-quality/SKILL.md +51 -0
  27. package/skills/check-partition-quality/scripts/check-partition-quality.mts +336 -0
  28. package/skills/check-plan-safety/README.md +17 -0
  29. package/skills/check-plan-safety/SKILL.md +60 -0
  30. package/skills/check-plan-safety/scripts/check-plan-safety.mts +145 -0
  31. package/skills/check-project-specs/README.md +19 -0
  32. package/skills/check-project-specs/SKILL.md +69 -0
  33. package/skills/check-project-specs/scripts/check-project-specs.mts +217 -0
  34. package/skills/check-scenario-overlap/README.md +19 -0
  35. package/skills/check-scenario-overlap/SKILL.md +74 -0
  36. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +249 -0
  37. package/skills/check-spec-structure/README.md +17 -0
  38. package/skills/check-spec-structure/SKILL.md +66 -0
  39. package/skills/check-spec-structure/scripts/check-spec-structure.mts +346 -0
  40. package/skills/collision-ladder/README.md +18 -0
  41. package/skills/collision-ladder/SKILL.md +83 -0
  42. package/skills/collision-ladder/scripts/collision-ladder.mts +657 -0
  43. package/skills/combat-log-governance/README.md +13 -0
  44. package/skills/combat-log-governance/SKILL.md +257 -0
  45. package/skills/concept-index/README.md +13 -0
  46. package/skills/concept-index/SKILL.md +38 -0
  47. package/skills/concept-index/scripts/concept-index.mts +245 -0
  48. package/skills/discover-plans/README.md +16 -0
  49. package/skills/discover-plans/SKILL.md +74 -0
  50. package/skills/discover-plans/scripts/discover-plans.mts +212 -0
  51. package/skills/discover-specs/README.md +15 -0
  52. package/skills/discover-specs/SKILL.md +76 -0
  53. package/skills/discover-specs/scripts/discover-specs.mts +396 -0
  54. package/skills/doctrine-loop/README.md +15 -0
  55. package/skills/doctrine-loop/SKILL.md +97 -0
  56. package/skills/formation-loop/README.md +17 -0
  57. package/skills/formation-loop/SKILL.md +140 -0
  58. package/skills/gate-validation-governance/README.md +12 -0
  59. package/skills/gate-validation-governance/SKILL.md +87 -0
  60. package/skills/impl-producer-governance/README.md +48 -0
  61. package/skills/impl-producer-governance/SKILL.md +85 -0
  62. package/skills/init/README.md +27 -0
  63. package/skills/init/SKILL.md +68 -0
  64. package/skills/init/scripts/wire-statusline.mts +276 -0
  65. package/skills/lifecycle-governance/README.md +11 -0
  66. package/skills/lifecycle-governance/SKILL.md +168 -0
  67. package/skills/manage/README.md +9 -0
  68. package/skills/manage/SKILL.md +62 -0
  69. package/skills/manage-ignore/README.md +19 -0
  70. package/skills/manage-ignore/SKILL.md +52 -0
  71. package/skills/manage-ignore/scripts/manage-ignore.mts +294 -0
  72. package/skills/manage-scenario-bridge/README.md +20 -0
  73. package/skills/manage-scenario-bridge/SKILL.md +60 -0
  74. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +156 -0
  75. package/skills/manage-spec-anchors/README.md +18 -0
  76. package/skills/manage-spec-anchors/SKILL.md +56 -0
  77. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +328 -0
  78. package/skills/mission-graph/README.md +15 -0
  79. package/skills/mission-graph/SKILL.md +67 -0
  80. package/skills/mission-graph/scripts/mission-graph.mts +844 -0
  81. package/skills/oracle-spec-governance/README.md +45 -0
  82. package/skills/oracle-spec-governance/SKILL.md +45 -0
  83. package/skills/ownership-governance/README.md +65 -0
  84. package/skills/ownership-governance/SKILL.md +104 -0
  85. package/skills/pause-mission/README.md +18 -0
  86. package/skills/pause-mission/SKILL.md +112 -0
  87. package/skills/place-node/README.md +12 -0
  88. package/skills/place-node/SKILL.md +47 -0
  89. package/skills/place-node/scripts/place-node.mts +157 -0
  90. package/skills/plan-retirement/README.md +32 -0
  91. package/skills/plan-retirement/SKILL.md +90 -0
  92. package/skills/plan-retirement/scripts/retire-plans.mts +196 -0
  93. package/skills/plugin-contract-governance/README.md +12 -0
  94. package/skills/plugin-contract-governance/SKILL.md +112 -0
  95. package/skills/remediation-governance/README.md +46 -0
  96. package/skills/remediation-governance/SKILL.md +78 -0
  97. package/skills/resolve-governances/README.md +18 -0
  98. package/skills/resolve-governances/SKILL.md +50 -0
  99. package/skills/resolve-governances/scripts/resolve-governances.mts +515 -0
  100. package/skills/resolve-tracking/SKILL.md +64 -0
  101. package/skills/resolve-tracking/scripts/resolve-tracking.mts +213 -0
  102. package/skills/resume-mission/README.md +12 -0
  103. package/skills/resume-mission/SKILL.md +53 -0
  104. package/skills/scaffold-project-spec/README.md +7 -0
  105. package/skills/scaffold-project-spec/SKILL.md +192 -0
  106. package/skills/sdd/README.md +7 -0
  107. package/skills/sdd/SKILL.md +92 -0
  108. package/skills/solution-producer-governance/README.md +9 -0
  109. package/skills/solution-producer-governance/SKILL.md +44 -0
  110. package/skills/spec-format-governance/README.md +73 -0
  111. package/skills/spec-format-governance/SKILL.md +114 -0
  112. package/skills/spec-gate/README.md +26 -0
  113. package/skills/spec-gate/SKILL.md +201 -0
  114. package/skills/spec-gate/scripts/check-spec-state.mts +601 -0
  115. package/skills/spec-gate/scripts/check-suite.mts +501 -0
  116. package/skills/spec-gate/scripts/classify-edit-class.mts +411 -0
  117. package/skills/spec-producer-governance/README.md +7 -0
  118. package/skills/spec-producer-governance/SKILL.md +86 -0
  119. package/skills/spec-structure-governance/README.md +40 -0
  120. package/skills/spec-structure-governance/SKILL.md +169 -0
  121. package/skills/ssa-lowering/README.md +26 -0
  122. package/skills/ssa-lowering/SKILL.md +181 -0
  123. package/skills/start-mission/README.md +7 -0
  124. package/skills/start-mission/SKILL.md +115 -0
  125. package/skills/suite-format-governance/README.md +75 -0
  126. package/skills/suite-format-governance/SKILL.md +299 -0
  127. package/skills/suite-format-governance/references/rubric.md +313 -0
  128. package/skills/touch-set-correction/README.md +16 -0
  129. package/skills/touch-set-correction/SKILL.md +67 -0
  130. package/skills/touch-set-correction/scripts/touch-set-correction.mts +418 -0
  131. package/skills/verify-scenarios/README.md +17 -0
  132. package/skills/verify-scenarios/SKILL.md +109 -0
  133. package/skills/verify-scenarios/scripts/verify-scenarios.mts +386 -0
@@ -0,0 +1,276 @@
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, writeFileSync } from 'node:fs'
29
+ import { homedir } from 'node:os'
30
+ import { dirname, join } from 'node:path'
31
+
32
+ export const STATUS_FILE = '.agents/sdd/statusline'
33
+ export const SETTINGS_FILE = '.claude/settings.json'
34
+ export const GITIGNORE_FILE = '.gitignore'
35
+ export const GLOBAL_SETTINGS_FILE = join(homedir(), '.claude', 'settings.json')
36
+
37
+ export type StatusLineMode = 'own-line' | 'same-line'
38
+
39
+ const MARKER_BEGIN = '# sdd-statusline:begin'
40
+ const MARKER_END = '# sdd-statusline:end'
41
+ const MODE_PREFIX = '# sdd-statusline:mode:'
42
+ const ORIG_OPEN = '__sdd_orig() {'
43
+ const ORIG_CLOSE = '}'
44
+ const NO_BASE = ':'
45
+
46
+ // ── the managed command block ──
47
+
48
+ // Build the self-contained POSIX sh command that reads the status file and renders it in `mode`,
49
+ // wrapping `base` (the pre-existing statusLine command, if any) so its output is preserved.
50
+ export function buildCommand(base: string | undefined, mode: StatusLineMode): string {
51
+ const origBody = base && base.trim() !== '' ? base : NO_BASE
52
+ const render =
53
+ mode === 'own-line'
54
+ ? `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`
55
+ : `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`
56
+ return [
57
+ MARKER_BEGIN,
58
+ `${MODE_PREFIX}${mode}`,
59
+ ORIG_OPEN,
60
+ origBody,
61
+ ORIG_CLOSE,
62
+ '__sdd_base="$(__sdd_orig 2>/dev/null)"',
63
+ `__sdd_file="${STATUS_FILE}"`,
64
+ render,
65
+ MARKER_END,
66
+ ].join('\n')
67
+ }
68
+
69
+ export interface ParsedCommand {
70
+ wired: boolean
71
+ base?: string
72
+ mode?: StatusLineMode
73
+ }
74
+
75
+ // Recognize a previously wired command and recover its wrapped base + mode, so re-wiring rebuilds the
76
+ // same managed block instead of stacking a second one. A command with no marker is not wired — the
77
+ // whole string becomes the base to wrap.
78
+ export function parseCommand(command: string | undefined): ParsedCommand {
79
+ if (!command?.includes(MARKER_BEGIN)) return { wired: false }
80
+ const modeMatch = new RegExp(`${MODE_PREFIX}(own-line|same-line)`).exec(command)
81
+ const origMatch = /__sdd_orig\(\) \{\n([\s\S]*?)\n\}/.exec(command)
82
+ const mode = (modeMatch?.[1] as StatusLineMode | undefined) ?? 'own-line'
83
+ const origBody = origMatch?.[1] ?? NO_BASE
84
+ return { wired: true, base: origBody === NO_BASE ? undefined : origBody, mode }
85
+ }
86
+
87
+ // The command to wrap as `base` when composing: an already-wired command hands back its recovered
88
+ // original base (never nests); any other command is the base verbatim.
89
+ function baseToWrap(existingCommand: string | undefined): string | undefined {
90
+ const parsed = parseCommand(existingCommand)
91
+ return parsed.wired ? parsed.base : existingCommand
92
+ }
93
+
94
+ // ── project settings.json ──
95
+
96
+ interface StatusLineConfig {
97
+ type: string
98
+ command: string
99
+ [key: string]: unknown
100
+ }
101
+
102
+ interface Settings {
103
+ statusLine?: StatusLineConfig
104
+ [key: string]: unknown
105
+ }
106
+
107
+ function readSettingsFile(file: string): Settings {
108
+ if (!existsSync(file)) return {}
109
+ try {
110
+ return JSON.parse(readFileSync(file, 'utf8')) as Settings
111
+ } catch {
112
+ return {}
113
+ }
114
+ }
115
+
116
+ function readSettings(root: string): Settings {
117
+ return readSettingsFile(join(root, SETTINGS_FILE))
118
+ }
119
+
120
+ // Read a settings JSON file's statusLine.command, read-only. Absent file, unparseable JSON, or a
121
+ // missing/non-string/blank command all collapse to undefined — a malformed global settings file is
122
+ // treated the same as no global statusLine (frozen scenario).
123
+ export function readStatusLineCommand(file: string): string | undefined {
124
+ const settings = readSettingsFile(file)
125
+ const command = settings.statusLine?.command
126
+ if (typeof command !== 'string' || command.trim() === '') return undefined
127
+ return command
128
+ }
129
+
130
+ function writeSettings(root: string, settings: Settings): void {
131
+ const file = join(root, SETTINGS_FILE)
132
+ mkdirSync(dirname(file), { recursive: true })
133
+ writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`)
134
+ }
135
+
136
+ export interface ComposeResult {
137
+ changed: boolean
138
+ command: string
139
+ globalBaseComposed: boolean
140
+ }
141
+
142
+ // Compose the SDD statusLine segment into project settings.json. Preserves any existing statusLine
143
+ // command's output as the wrapped base (never overwrites it); creates the key when absent. Rewiring
144
+ // the same mode over an already-wired command is a no-op (byte-identical command).
145
+ //
146
+ // Base selection: when the project settings define no usable `statusLine` command (the same
147
+ // predicate detectStatusLines reports as `absent`), this is a fresh wire — the base is
148
+ // `globalCommand` (possibly undefined). When a usable project `statusLine` already exists (foreign
149
+ // or already wired), today's behavior is unchanged — the existing command's recovered base is used
150
+ // and `globalCommand` is ignored entirely, so a re-run never re-consults or adopts the global.
151
+ export function composeStatusLine(root: string, mode: StatusLineMode, globalCommand?: string): ComposeResult {
152
+ const settings = readSettings(root)
153
+ const existing = settings.statusLine?.command
154
+ const hasProjectStatusLine = typeof existing === 'string' && existing.trim() !== ''
155
+ const globalBaseComposed = !hasProjectStatusLine && globalCommand !== undefined
156
+ const base = hasProjectStatusLine ? baseToWrap(existing) : globalCommand
157
+ const command = buildCommand(base, mode)
158
+ if (existing === command) return { changed: false, command, globalBaseComposed: false }
159
+ settings.statusLine = { ...settings.statusLine, type: 'command', command }
160
+ writeSettings(root, settings)
161
+ return { changed: true, command, globalBaseComposed }
162
+ }
163
+
164
+ // ── .gitignore ──
165
+
166
+ export function isGitRepo(root: string): boolean {
167
+ return existsSync(join(root, '.git'))
168
+ }
169
+
170
+ function splitLines(text: string): string[] {
171
+ const lines = text.split('\n')
172
+ if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
173
+ return lines
174
+ }
175
+
176
+ // Idempotently add the status file to .gitignore (creating it if absent). Only called when the root
177
+ // is a git repo; a no-op when the entry is already present.
178
+ export function addGitignoreEntry(root: string): { changed: boolean } {
179
+ const file = join(root, GITIGNORE_FILE)
180
+ const lines = existsSync(file) ? splitLines(readFileSync(file, 'utf8')) : []
181
+ if (lines.some((l) => l.trim() === STATUS_FILE)) return { changed: false }
182
+ const next = [...lines, STATUS_FILE]
183
+ writeFileSync(file, `${next.join('\n')}\n`)
184
+ return { changed: true }
185
+ }
186
+
187
+ // ── global statusline detection ──
188
+
189
+ export interface DetectResult {
190
+ project: 'absent' | 'wired' | 'foreign'
191
+ global: 'absent' | 'present'
192
+ shadow: boolean
193
+ }
194
+
195
+ // Read-only: report the project statusLine state, the global statusLine state, and whether wiring
196
+ // would shadow a global statusLine that is currently rendering (project has none, global has one).
197
+ // Never writes anything — used to surface the shadow risk before wiring and by `--detect`.
198
+ export function detectStatusLines(root: string, globalSettingsFile?: string): DetectResult {
199
+ const projectCommand = readStatusLineCommand(join(root, SETTINGS_FILE))
200
+ const project = projectCommand === undefined ? 'absent' : parseCommand(projectCommand).wired ? 'wired' : 'foreign'
201
+ const global = readStatusLineCommand(globalSettingsFile ?? GLOBAL_SETTINGS_FILE) !== undefined ? 'present' : 'absent'
202
+ const shadow = project === 'absent' && global === 'present'
203
+ return { project, global, shadow }
204
+ }
205
+
206
+ // ── top-level wire ──
207
+
208
+ export interface WireResult {
209
+ settings: ComposeResult
210
+ gitignore: { changed: boolean; skipped: boolean }
211
+ }
212
+
213
+ export interface WireOptions {
214
+ globalSettingsFile?: string
215
+ globalBase?: boolean
216
+ }
217
+
218
+ // Wire the reader into project settings and (only in a git repo) gitignore the status file. The
219
+ // single entry point init's SKILL.md calls on consent. `globalBase` (default true) controls whether
220
+ // a fresh wire composes the global statusLine command as the base; `globalSettingsFile` overrides
221
+ // the default global settings path (for tests, or a non-default HOME).
222
+ export function wireStatusline(root: string, mode: StatusLineMode, opts?: WireOptions): WireResult {
223
+ const globalBase = opts?.globalBase ?? true
224
+ const globalCommand = globalBase ? readStatusLineCommand(opts?.globalSettingsFile ?? GLOBAL_SETTINGS_FILE) : undefined
225
+ const settings = composeStatusLine(root, mode, globalCommand)
226
+ const gitignore = isGitRepo(root) ? { ...addGitignoreEntry(root), skipped: false } : { changed: false, skipped: true }
227
+ return { settings, gitignore }
228
+ }
229
+
230
+ // ── CLI ──
231
+
232
+ function flag(argv: string[], name: string): string | undefined {
233
+ const i = argv.indexOf(name)
234
+ return i === -1 ? undefined : argv[i + 1]
235
+ }
236
+
237
+ export function main(argv: string[]): number {
238
+ const root = flag(argv, '--root') ?? '.'
239
+ const globalSettingsFile = flag(argv, '--global-settings')
240
+ const w = (s: string) => process.stdout.write(`${s}\n`)
241
+
242
+ if (argv.includes('--detect')) {
243
+ const result = detectStatusLines(root, globalSettingsFile)
244
+ w(`project statusLine: ${result.project}`)
245
+ w(`global statusLine: ${result.global}`)
246
+ w(
247
+ result.shadow
248
+ ? 'shadow risk: yes — wiring would shadow the global statusline; compose it as the base (default) or pass --no-global-base to shadow deliberately'
249
+ : 'shadow risk: no',
250
+ )
251
+ return 0
252
+ }
253
+
254
+ if (argv.includes('--wire')) {
255
+ const mode = flag(argv, '--mode')
256
+ if (mode !== 'own-line' && mode !== 'same-line') {
257
+ w('refused: --mode must be own-line or same-line')
258
+ return 1
259
+ }
260
+ const globalBase = !argv.includes('--no-global-base')
261
+ const result = wireStatusline(root, mode, { globalSettingsFile, globalBase })
262
+ w(result.settings.changed ? `wired statusLine (${mode})` : 'statusLine already wired (unchanged)')
263
+ if (result.settings.globalBaseComposed) w('composed the global statusLine command as the wrapped base')
264
+ if (result.gitignore.skipped) w('not a git repo — gitignore skipped')
265
+ else w(result.gitignore.changed ? `added ${STATUS_FILE} to .gitignore` : '.gitignore already up to date')
266
+ return 0
267
+ }
268
+
269
+ w(
270
+ 'usage: wire-statusline --root <dir> --wire --mode own-line|same-line [--global-settings <file>] [--no-global-base]\n' +
271
+ ' wire-statusline --root <dir> --detect [--global-settings <file>]',
272
+ )
273
+ return 0
274
+ }
275
+
276
+ if (import.meta.main) process.exit(main(process.argv.slice(2)))
@@ -0,0 +1,11 @@
1
+ # lifecycle-governance
2
+
3
+ Internal SDD governance (`user-invocable: false`). The **lifecycle** contract — the state machine a
4
+ `spec.md` moves through: the root frontmatter schema, the `status` enum, the legal status
5
+ transitions, open-marker gating, and the per-file freeze state-transition.
6
+
7
+ A fixed-universal SDD governance, invariant per role (not face-split). Loaded by `sdd`,
8
+ `spec-gate`, `start-mission`, the conductor, and the spec-judge. Write-ownership of these fields
9
+ lives in `ownership-governance`; legality of field combinations and gate verdicts in
10
+ `gate-validation-governance`; the ledger shape in `combat-log-governance`. Not triggered by users
11
+ directly.
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: lifecycle-governance
3
+ description: "Partial Skill: invoke by name only — the SDD spec lifecycle contract. Loaded by sdd, spec-gate, start-mission, the conductor, and the spec-judge, not user-triggered."
4
+ user-invocable: false
5
+ ---
6
+
7
+ # SDD Lifecycle Governance
8
+
9
+ The state machine a `spec.md` moves through, and the frontmatter that records it. This skill is the
10
+ canonical home consumers load instead of restating it. Write-ownership of these fields lives in
11
+ `sdd:ownership-governance`; legality of field combinations and gate verdicts in
12
+ `sdd:gate-validation-governance`; the ledger shape in `sdd:combat-log-governance`.
13
+
14
+ ## Frontmatter schema
15
+
16
+ The **root** `spec.md` carries YAML frontmatter:
17
+
18
+ ```yaml
19
+ ---
20
+ status: draft # draft | approved | implemented | deprecated
21
+ project-path: plugins/sdd # repo-relative source dir this spec governs; the location mode is derivable
22
+ name: SDD # OPTIONAL declared project name; authoritative for name→spec resolution
23
+ approval: # per-gate verdict
24
+ spec: # verdict: approve | pause | reject
25
+ verdict: approve
26
+ by: agent # agent (self-asserted, provisional) | <human name> (ratified); omitted on pause
27
+ cause: dimension # dimension | ceiling — what drove the verdict
28
+ why: # verdict derivation (agent self-assertion or pause)
29
+ floor: <none | clearance | conflict | compatibility | consent>
30
+ blast: <low|high — reason>
31
+ novelty: <low|high — reason>
32
+ confidence: <low|high — reason>
33
+ impl: { verdict: approve, by: <human name> } # ratified — no why needed
34
+ produced-by: # who produced each artifact; see sdd:combat-log-governance
35
+ spec-producer: <plugin>:<agent>
36
+ ---
37
+ ```
38
+
39
+ Open input is recorded in the body as `<!-- open: ... -->` markers, not in frontmatter.
40
+
41
+ The frontmatter is the **router's upfront index** (ADR-0017): the gateway scans every spec at the
42
+ three SDD spec locations, frontmatter only (no bodies), to route, so it carries only what routing
43
+ needs. `status` is the base schema;
44
+ `project-path`, `approval`, and `produced-by` are the SDD-workflow additions. `project-path` records
45
+ the repo-relative source dir the spec governs; the spec **location mode** (`colocated | hoisted |
46
+ monorepo-member`) is *derived* from it (hoisted iff `project-path` is not the spec's own dir), never
47
+ stored. There is **no `aligned`, `spec-layout`, or run-level `leash` field** — sync is derived
48
+ (below), the organization strategy is declared in the body placement map, and the leash is
49
+ session-local on the ledger/plan (the conductor's autonomy bar, `start-mission`).
50
+
51
+ **A file's artifact-type is resolved per file, never stored here.** Each file's artifact-type (the
52
+ squad key) resolves its own squad against the project's registered plugins
53
+ (`sdd:plugin-contract-governance`); the conductor classifies it by convention, falling back to the
54
+ optional `.agents/sdd/artifact-types.toml` tiebreaker on ambiguity. The contested-type →
55
+ chosen-plugin disambiguation lives in `.agents/sdd/` resolution state, **distinct from
56
+ `produced-by`**, never a frontmatter field. The project carries no root `artifact-types` summary.
57
+
58
+ **This whole frontmatter is root-`spec.md`-only.** A capability node README (a `reference` or
59
+ `behavioral` spec — `sdd:spec-format-governance`) carries **only** its `spec-type` marker, never a
60
+ lifecycle field. Folders are views, never lifecycle units: the project has **one** lifecycle, on the
61
+ root.
62
+
63
+ ## Spec discovery
64
+
65
+ A spec is **location-bounded and shape-confirmed** (ADR-0017): an SDD spec is a git-tracked
66
+ `spec.md` that sits at one of the three fixed SDD spec locations — **or** at an extra anchor the
67
+ project declared (ADR-0019) — **and** whose frontmatter `status` is one of the enum values below:
68
+
69
+ 1. `.agents/spec/spec.md` — repo-root single-project
70
+ 2. `.agents/specs/<project>/spec.md` — repo-root multi-project
71
+ 3. `<project-path>/.agents/spec/spec.md` — a nested project (the `**` is the project-path, any depth)
72
+ 4. any extra anchor declared in `.agents/sdd/spec-anchors.toml` — **opt-in and additive**; absent
73
+ config ⇒ only 1–3 (today's behavior). The three fixed conventions need no registry; the extra
74
+ anchors are a declared, curated registry (`manage-spec-anchors`), not a derived hot path.
75
+
76
+ To locate specs, scan the fixed conventions (plus any declared extra anchors) and keep git-tracked
77
+ files whose `status` is in the enum. A `spec.md` at a recognized location with no lifecycle `status`
78
+ is **not** a spec (so a stray file is never grabbed by accident); a status-bearing `spec.md` at
79
+ neither a fixed convention nor a declared extra anchor is not a spec either. An unreadable/malformed
80
+ `spec-anchors.toml` is ignored (fall back to the fixed conventions). The concrete engine is the
81
+ `discover-specs` skill (frontmatter only, TOON output).
82
+
83
+ Each spec carries a **project name** so a consumer can resolve a name → spec. The name is `declared`
84
+ (the optional frontmatter `name`, authoritative), else `derived` (the repo-root single-project →
85
+ `repo`; a `.agents/specs/<project>` folder names itself), else `guessed` (a nested project's folder
86
+ basename — confirm with the user). The optional `name` field is **written at spec creation**
87
+ (`sdd:scaffold-project-spec`) — it earns its router slot because the gateway presents project names
88
+ to the user; it is required in practice only for a nested project whose folder is not the user's name.
89
+
90
+ **Sync is derived, not stored (no `aligned` flag).** "Synced" is two properties, each derived or
91
+ judged (ADR-0017): contract-sync (`spec.md` ↔ `.feature`) is *judged* at the spec gate (Builder
92
+ coverage lens); impl-sync is the impl gate's *runtime suite run* (advance to `implemented` only when
93
+ every impl-judge passes); per-node settled state is the **`@frozen`** scan; what is in flux now is the
94
+ `.plan.md` todos. Do not commit SDD artifacts while a touched `.feature` is unfrozen or the plan's
95
+ todos are incomplete.
96
+
97
+ ## Status enum
98
+
99
+ | Status | Meaning |
100
+ |---|---|
101
+ | `draft` | Contract can still evolve; not yet implementable as a fixed bar |
102
+ | `approved` | Contract is frozen; ready to implement against |
103
+ | `implemented` | Implementation passed the impl gate |
104
+ | `deprecated` | Historical spec only; not implementable work |
105
+
106
+ ## Status transitions
107
+
108
+ ```mermaid
109
+ stateDiagram-v2
110
+ [*] --> draft: start-mission (new or backfill)
111
+ draft --> approved: spec gate (spec-gate --target spec)
112
+ approved --> implemented: impl gate (spec-gate --target impl)
113
+ approved --> draft: behavior change (re-open)
114
+ implemented --> draft: behavior change (re-open)
115
+ draft --> deprecated: Oracle-lens kill (scope)
116
+ approved --> deprecated
117
+ implemented --> deprecated
118
+ ```
119
+
120
+ - **Draft → Approved** is the **spec gate**: judges `spec.md` + the `.feature`.
121
+ - **Approved → Implemented** is the **impl gate**: judges the implementation against the frozen `.feature`.
122
+ - A behavior change after approval is **not** a direct edit — revert to `draft` and re-pass the spec
123
+ gate. Re-open is a lightweight "change needed" flag an auditor sets; only re-approval is the heavy
124
+ positional act.
125
+ - Deprecation retains the spec as a historical record; never treat it as implementable.
126
+
127
+ ## Freeze (per suite file)
128
+
129
+ Reaching a spec-gate `approve` **freezes** the `.feature` files the CR *touched* — each via its own
130
+ feature-level **`@frozen` tag** (set/cleared per file; the vocabulary is **freeze / unfreeze**, never
131
+ lock/unlock — lock is the concurrency layer, `cr-concurrency`). Files the CR did not touch keep
132
+ whatever state they held. "Which scenarios are the frozen contract" is answered by the set of
133
+ `@frozen` files — a plain per-file flag, no computed baseline. The `@frozen` tag is metadata,
134
+ excluded from the content the freeze protects; toggling it is not a scenario edit. The matching
135
+ write constraint ("never write a frozen `.feature`") is in `sdd:ownership-governance`.
136
+
137
+ - **The unfreeze trigger is risk, not phase.** *Narrowing or rewriting* a scenario unfreezes its
138
+ file (in explore or deliver alike) — at the gate that is **Clearance** (the conductor's autonomy
139
+ bar, `start-mission`), contract narrowed → escalate. An *additive* scenario never unfreezes its file:
140
+ it widens the contract, cannot break existing impl, and **self-clears** — folding into the frozen
141
+ file under the conductor's authority, logged as a detail-adjustment.
142
+ - **A pure move/rename preserves the freeze.** What a freeze protects is the scenario **content**,
143
+ not where the file sits. A *pure rename* — a `git mv` with **zero content delta** (a git `R100`) —
144
+ does **not** unfreeze the file and is **not** a gate-able edit; it stays `@frozen` at the same
145
+ baseline. This is what lets **placement be finalized at handoff** (`design/spec-layout.md`): a node
146
+ may be relocated to its blessed home in the same change without re-opening its contract. Only a
147
+ *content* change (the narrowing/rewriting trigger above) unfreezes.
148
+ - **`spec.md` is kept in sync, never frozen** — the readable abstraction of the suite, free to be
149
+ reworded/restructured as long as it does not contradict a frozen scenario. Enforced by the
150
+ spec-judge applying the Builder (coverage) lens, not by freezing the prose.
151
+ - **The ledger is never frozen and never gated** — it keeps appending across the whole lifecycle,
152
+ including while files sit `@frozen` (`sdd:combat-log-governance`).
153
+ - **Spec owns behavior.** If the implementation disagrees with `spec.md`, the implementation is
154
+ wrong — fix it, or unfreeze the relevant file for a new cycle. The **impl gate** is the only place
155
+ a frozen file reopens — via the Oracle-lens revert (building proved the contract wrong). Rare
156
+ and deliberate.
157
+
158
+ **Two modes.** Before a file's freeze, exploration may update `spec.md`, that `.feature`, the plan
159
+ (brief + ordered `todos`), and spikes. After it freezes, implementation proceeds against it; every
160
+ frozen scenario must pass the full impl-gate run before `implemented`.
161
+
162
+ ## Open-marker gating
163
+
164
+ Missing contributor input is recorded as `<!-- open: ... -->` in the owning artifact. Open markers
165
+ must be resolved (count = 0) before a spec may advance to `approved`; they are permitted at `draft`
166
+ (markers block only the *gate*, not the draft state). `sdd:gate-validation-governance` defines how
167
+ markers interact with legal state; producers emit gaps that the conductor turns into markers
168
+ (`sdd:ownership-governance`).
@@ -0,0 +1,9 @@
1
+ # manage
2
+
3
+ The user-facing entry for **manage-level (non-mission) work** on an SDD project — the handler for the gateway's **"Manage the corpus"** route, sibling to `start-mission`. Where `start-mission` **changes what the project specifies**, `manage` does non-mission work on the corpus: **setup & discovery**, **inspect**, **audit & align**, **housekeeping**.
4
+
5
+ User-invocable. A **thin dispatcher** (the in-session realization of the manage unit): it classifies a manage request and **loads the matching engine in the current session**, holding no production logic, loading no governance, and writing no contract state.
6
+
7
+ Bakes in: two-level intake (fast path when the operation is named; a four-group menu when bare, within the four-option rule); the group→engine routing table (Setup & discovery → `scaffold-project-spec` / `manage-spec-anchors`; Inspect → `discover-specs` / `concept-index` / `place-node` / `discover-plans`; Audit & align → `check-spec-structure` / `formation-loop` / `align-spec` (planned); Housekeeping → `plan-retirement`); loading the engine in-session (read-only engines run in place, write-capable ones stay owned by their engine); and the non-mission boundary — opens no CR, invokes no gate, writes no `status`/`approval`, and **hands a behavior change off to `start-mission`**. Reviewing pending strategy stays gateway-owned, not a manage operation.
8
+
9
+ Pairs with the `sdd` gateway (which routes its option-2 here) and `start-mission` (which manage hands off to when an operation needs a real behavior change).
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: manage
3
+ description: Use this skill when doing manage-level (non-mission) SDD work on the spec corpus — bootstrap, inspect, audit, or housekeeping — such as "set up the project spec", "backfill the spec", "list the SDD specs and statuses", "audit the corpus structure", "check for spec/suite drift", or "retire completed mission plans". Routes to the matching engine; it does not change what the project specifies (that is start-mission).
4
+ ---
5
+
6
+ # manage
7
+
8
+ The **manage-level** front door to an SDD project — the user-facing handler for the gateway's **"Manage the corpus"** route, sibling to `start-mission`. Where `start-mission` **changes what the project specifies** (opens a CR, runs the mission loop), `manage` does **non-mission** work on the corpus: **bootstrap**, **inspect**, **audit**, **housekeeping**.
9
+
10
+ `manage` is a **thin dispatcher**: it classifies a manage request and **loads the matching engine in the current session**, holding **no production logic**, loading **no governance**, and writing **no contract state**. It **opens no CR**, **invokes no gate**, and **performs no behavior change** itself — a needed change to the project's specified behavior is **handed off to `start-mission`**.
11
+
12
+ > **Manage picks no model.** Like the gateway, the model + effort a piece of work needs is set by the **engine you load** — the `.mts` engines are light; a `backfill` or `formation` grill wants a capable model. The loaded engine advises; the user switches manually. (Harness gap: `manage` cannot switch the session model itself.)
13
+
14
+ ## Intake
15
+
16
+ - **Fast path — skip the menu.** When the invocation already **names the operation** — "backfill the project spec", "list the specs", "check spec structure", "retire the finished plans" — load the matching engine directly, no menu.
17
+ - **Two-level menu — bare invocation.** When `manage` is invoked with **no operation named**, do not guess. Conduct intake as a **two-level menu**, never a flat list. **Never ask more than four options** in a single `AskUserQuestion` (the tool rejects more than four). The top-level question presents the **four operation groups**; the second level picks the specific engine.
18
+
19
+ | # | Operation group | Covers |
20
+ |---|---|---|
21
+ | 1 | **Setup & discovery** | scaffold a project's spec envelope for the first time → `scaffold-project-spec`; curate discovery's extra spec anchors → `manage-spec-anchors`; curate the ignore file → `manage-ignore`; wire a project's scenario bridge → `manage-scenario-bridge`; set up or configure the mission statusline → `init` (all are prerequisites for a project being found and usable, not routine cleanup) |
22
+ | 2 | **Inspect** | list / navigate the corpus → `discover-specs`, `concept-index`, `place-node`, `discover-plans` |
23
+ | 3 | **Audit & align** | audit node-shape, drift, structure → `check-spec-structure`, `formation-loop`, `align-spec` *(planned)*; scan plan briefs for machine-local path leaks → `check-plan-safety` |
24
+ | 4 | **Housekeeping** | retire completed mission plans → `plan-retirement` |
25
+
26
+ When a group's engine list would exceed four at the second level, present only the most-relevant few (≤ 4) or ask the user to name the engine directly; never enumerate into an over-four question and never truncate silently.
27
+
28
+ ## The routing table — group → engine
29
+
30
+ Classification routes a manage request to the **handler** that handles it; every handler already exists and is loaded here — most are `user-invocable: false` engines, except `init`, which stays independently user-invocable.
31
+
32
+ | Group | Request | Engine (handler) |
33
+ |---|---|---|
34
+ | **Setup & discovery** | set up / backfill a project's spec for the first time | **`scaffold-project-spec`** — scaffolds the spec envelope + stub nodes |
35
+ | **Setup & discovery** | list / change discovery's extra spec anchors | **`manage-spec-anchors`** — list fixed + custom anchors, CRUD the custom ones, induce a pattern from a path, preview its match (writes only `.agents/sdd/spec-anchors.toml`) |
36
+ | **Setup & discovery** | curate the ignore file | **`manage-ignore`** — curate `.agents/sdd/.sddignore` (list / add / remove / induce / preview); writes only the ignore file |
37
+ | **Setup & discovery** | scaffold or curate a project's scenario-bridge config | **`manage-scenario-bridge`** — list / scaffold / add sources in `<project-path>/.agents/sdd/scenario-bridge.toml`; writes only that project's scenario-bridge config |
38
+ | **Setup & discovery** | set up / configure the mission statusline | **`init`** — user-invocable onboarding skill; offers the opt-in statusline, wires the reader into project `.claude/settings.json` |
39
+ | **Inspect** | list the specs + statuses | **`discover-specs`** — frontmatter-only corpus scan |
40
+ | **Inspect** | render / refresh the by-concept view | **`concept-index`** — `--check` (read) / `--write` (refresh block) |
41
+ | **Inspect** | where does a new concept belong | **`place-node`** — provisional home + duplicate catch |
42
+ | **Inspect** | list in-progress (resumable) missions | **`discover-plans`** — plan-brief scan |
43
+ | **Audit & align** | audit node-shape (orphans / oversized) | **`check-spec-structure`** — read-only advisory |
44
+ | **Audit & align** | scan plan briefs for machine-local path leaks | **`check-plan-safety`** — read-only guard; flags home-abs paths + `$HOME`/`$USER` in `.agents/plans` |
45
+ | **Audit & align** | reconcile prose↔suite drift | **`align-spec`** *(planned — spec-only, no engine yet)* — a fix that edits behavior **hands off to `start-mission`** |
46
+ | **Audit & align** | corpus-wide audit / split / reconcile | **`formation-loop`** — emits new CRs (→ `start-mission`) |
47
+ | **Housekeeping** | retire completed mission plans | **`plan-retirement`** — gated, idempotent deletion of retired briefs |
48
+
49
+ Reviewing **pending strategy** is **not** a manage operation — it stays **gateway-owned** (the gateway's episodic pending-count, its option 3). `manage` does not surface or ratify strategy.
50
+
51
+ ## Load the engine in-session
52
+
53
+ When the route resolves, **load the matched engine in the current session** and run it directly — **spawn nothing**. Read-only engines (`discover-specs`, `discover-plans`, `check-spec-structure`, `check-plan-safety`, `place-node`, `concept-index --check`) run in place; **write-capable** operations stay **owned by their engine** — `scaffold-project-spec` scaffolds the skeleton, `plan-retirement` performs its gated deletion, `concept-index --write` refreshes the generated block, `manage-spec-anchors` writes its `spec-anchors.toml` config, `manage-scenario-bridge` writes its project's `scenario-bridge.toml` config. `manage` only routes.
54
+
55
+ ## Non-mission — the boundary
56
+
57
+ `manage` maintains and inspects the corpus; it **never changes what the project specifies**.
58
+
59
+ - **Opens no CR, invokes no gate, writes no `status` / `approval`.** Those belong to `start-mission` and the internal gates.
60
+ - **Hand off a behavior change.** When an operation surfaces a needed change to the project's specified behavior — a `formation` reconcile, an `align-spec` drift whose fix edits the spec/suite — **hand off to `start-mission`**, which opens a CR and runs the mission loop. Do not edit the spec/suite here.
61
+ - **A change request is not a manage operation.** A request to **add or revise** the project's specified behavior is **redirected to `start-mission`**, not handled as a manage operation.
62
+ - **Thin classifier.** Classifying loads **no governance** and holds **no production logic** — it only loads the matched engine.
@@ -0,0 +1,19 @@
1
+ # manage-ignore
2
+
3
+ The concrete engine for `.agents/sdd/.sddignore` — the optional gitignore-syntax file
4
+ `resolve-tracking` reads to decide whether an artifact is tracked or ignored. A non-user-invocable
5
+ skill, loaded in-session by the `manage` gateway (Housekeeping), carrying a self-contained `.mts`
6
+ script that lists / adds / removes / induces / previews the ignore rules so users never hand-edit the
7
+ file. Order is meaningful (last-match-wins), so rules are never re-sorted.
8
+
9
+ - **Skill contract:** [`SKILL.md`](./SKILL.md)
10
+ - **Script:** [`scripts/manage-ignore.mts`](./scripts/manage-ignore.mts)
11
+ - **Tests:** [`scripts/manage-ignore.test.mts`](./scripts/manage-ignore.test.mts) (`node:test`)
12
+
13
+ ```bash
14
+ node scripts/manage-ignore.mts --list
15
+ node scripts/manage-ignore.mts --induce build/output/app.log
16
+ node scripts/manage-ignore.mts --preview '*.log'
17
+ node scripts/manage-ignore.mts --preview '!keep.log'
18
+ node scripts/manage-ignore.mts --add '*.log'
19
+ ```
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: manage-ignore
3
+ description: "Partial Skill: invoke by name only — intake/manage-ignore's curation engine for the SDD ignore rules — loaded by the manage gateway (Housekeeping), not triggered by users directly."
4
+ user-invocable: false
5
+ metadata:
6
+ internal: true
7
+ ---
8
+
9
+ # Manage Ignore
10
+
11
+ The concrete engine for `.agents/sdd/.sddignore` — the optional gitignore-syntax file
12
+ `resolve-tracking` consults to decide whether an artifact is **tracked** or **ignored**. It is the
13
+ **write** side of the ignore file (`resolve-tracking` is the read side). It exists so a user curates
14
+ the ignore rules through a clean interface instead of hand-editing the file. Loaded **in-session** by
15
+ the `manage` gateway (Housekeeping group); it carries a self-contained `.mts` script (the repo's
16
+ node-≥23.6 / no-deps convention).
17
+
18
+ ## The file it curates
19
+
20
+ `.agents/sdd/.sddignore` is plain **gitignore syntax**: a bare pattern **ignores**, a leading `!`
21
+ **re-includes** (re-tracks), `#` comments and blank lines are allowed. Order is **meaningful** —
22
+ `.sddignore` is **last-match-wins**, so a later `!rule` re-tracks a path an earlier rule ignored. The
23
+ engine therefore **never re-sorts**: `--add` appends, `--remove` preserves the order of the survivors.
24
+
25
+ ## Run an operation
26
+
27
+ ```bash
28
+ node "<skill>/scripts/manage-ignore.mts" [--root .] <operation>
29
+ ```
30
+
31
+ | Operation | Effect |
32
+ |---|---|
33
+ | `--list` | print every rule in file order (comments/blanks omitted); a missing file prints nothing, no error |
34
+ | `--add <pattern>` | validate + append a well-formed rule (creates the file if absent); a malformed pattern is refused (file unchanged, nonzero exit) |
35
+ | `--remove <pattern>` | drop the matching rule, keeping the order of the rest; removing an absent rule is a no-op (file byte-for-byte unchanged) |
36
+ | `--induce <path>` | from a sample repo-relative path, offer a literal-path candidate and a `**/<basename>` generalization; persists nothing; refuses a path not under the repo |
37
+ | `--preview <pattern>` | list the working-tree paths the candidate would **ignore** (or, for a `!pattern`, the paths it would **re-track**), without persisting it; a malformed pattern is refused |
38
+
39
+ **Curate with preview.** The intended flow: take the user's sample path → `--induce` it → `--preview`
40
+ each candidate to show the affected paths → confirm with the user → `--add` the chosen rule. Never
41
+ write a rule the user has not seen the effect of.
42
+
43
+ ## Boundaries
44
+
45
+ Writes **only** `.agents/sdd/.sddignore` — never a `spec.md`, `status`, `approval`, or a freeze; it is
46
+ operational config, not spec content (so `manage`'s write-ownership guard holds). It does **not**
47
+ resolve an artifact's tracking signal — that is `resolve-tracking`, which *reads* this file. The
48
+ **read-side** fail-safe (an already-corrupted file never breaks intake) is `resolve-tracking`'s
49
+ guarantee; this engine validates on the **write** side so a malformed pattern is never persisted.
50
+
51
+ When `node` is absent, an agent performs the same edits by hand: read the file, apply the CRUD,
52
+ validate a line is a well-formed gitignore pattern, and preserve rule order (never re-sort).