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.
- package/.claude-plugin/plugin.json +17 -0
- package/.codex-plugin/plugin.json +17 -0
- package/.plugin/plugin.json +17 -0
- package/README.md +159 -0
- package/agents/sdd-automaton.md +97 -0
- package/agents/sdd-impl-judge.md +214 -0
- package/agents/sdd-scanner.md +120 -0
- package/agents/sdd-spec-judge.md +224 -0
- package/agents/sdd-warden.md +101 -0
- package/package.json +24 -0
- package/skills/align-spec/README.md +20 -0
- package/skills/align-spec/SKILL.md +111 -0
- package/skills/align-spec/scripts/align-spec.mts +187 -0
- package/skills/architect-impl-governance/README.md +46 -0
- package/skills/architect-impl-governance/SKILL.md +45 -0
- package/skills/architect-spec-governance/README.md +48 -0
- package/skills/architect-spec-governance/SKILL.md +59 -0
- package/skills/blast-estimate/README.md +47 -0
- package/skills/blast-estimate/SKILL.md +133 -0
- package/skills/blast-estimate/scripts/blast-estimate.mts +583 -0
- package/skills/builder-impl-governance/README.md +47 -0
- package/skills/builder-impl-governance/SKILL.md +47 -0
- package/skills/builder-spec-governance/README.md +49 -0
- package/skills/builder-spec-governance/SKILL.md +36 -0
- package/skills/check-partition-quality/README.md +22 -0
- package/skills/check-partition-quality/SKILL.md +51 -0
- package/skills/check-partition-quality/scripts/check-partition-quality.mts +336 -0
- package/skills/check-plan-safety/README.md +17 -0
- package/skills/check-plan-safety/SKILL.md +60 -0
- package/skills/check-plan-safety/scripts/check-plan-safety.mts +145 -0
- package/skills/check-project-specs/README.md +19 -0
- package/skills/check-project-specs/SKILL.md +69 -0
- package/skills/check-project-specs/scripts/check-project-specs.mts +217 -0
- package/skills/check-scenario-overlap/README.md +19 -0
- package/skills/check-scenario-overlap/SKILL.md +74 -0
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +249 -0
- package/skills/check-spec-structure/README.md +17 -0
- package/skills/check-spec-structure/SKILL.md +66 -0
- package/skills/check-spec-structure/scripts/check-spec-structure.mts +346 -0
- package/skills/collision-ladder/README.md +18 -0
- package/skills/collision-ladder/SKILL.md +83 -0
- package/skills/collision-ladder/scripts/collision-ladder.mts +657 -0
- package/skills/combat-log-governance/README.md +13 -0
- package/skills/combat-log-governance/SKILL.md +257 -0
- package/skills/concept-index/README.md +13 -0
- package/skills/concept-index/SKILL.md +38 -0
- package/skills/concept-index/scripts/concept-index.mts +245 -0
- package/skills/discover-plans/README.md +16 -0
- package/skills/discover-plans/SKILL.md +74 -0
- package/skills/discover-plans/scripts/discover-plans.mts +212 -0
- package/skills/discover-specs/README.md +15 -0
- package/skills/discover-specs/SKILL.md +76 -0
- package/skills/discover-specs/scripts/discover-specs.mts +396 -0
- package/skills/doctrine-loop/README.md +15 -0
- package/skills/doctrine-loop/SKILL.md +97 -0
- package/skills/formation-loop/README.md +17 -0
- package/skills/formation-loop/SKILL.md +140 -0
- package/skills/gate-validation-governance/README.md +12 -0
- package/skills/gate-validation-governance/SKILL.md +87 -0
- package/skills/impl-producer-governance/README.md +48 -0
- package/skills/impl-producer-governance/SKILL.md +85 -0
- package/skills/init/README.md +27 -0
- package/skills/init/SKILL.md +68 -0
- package/skills/init/scripts/wire-statusline.mts +276 -0
- package/skills/lifecycle-governance/README.md +11 -0
- package/skills/lifecycle-governance/SKILL.md +168 -0
- package/skills/manage/README.md +9 -0
- package/skills/manage/SKILL.md +62 -0
- package/skills/manage-ignore/README.md +19 -0
- package/skills/manage-ignore/SKILL.md +52 -0
- package/skills/manage-ignore/scripts/manage-ignore.mts +294 -0
- package/skills/manage-scenario-bridge/README.md +20 -0
- package/skills/manage-scenario-bridge/SKILL.md +60 -0
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +156 -0
- package/skills/manage-spec-anchors/README.md +18 -0
- package/skills/manage-spec-anchors/SKILL.md +56 -0
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +328 -0
- package/skills/mission-graph/README.md +15 -0
- package/skills/mission-graph/SKILL.md +67 -0
- package/skills/mission-graph/scripts/mission-graph.mts +844 -0
- package/skills/oracle-spec-governance/README.md +45 -0
- package/skills/oracle-spec-governance/SKILL.md +45 -0
- package/skills/ownership-governance/README.md +65 -0
- package/skills/ownership-governance/SKILL.md +104 -0
- package/skills/pause-mission/README.md +18 -0
- package/skills/pause-mission/SKILL.md +112 -0
- package/skills/place-node/README.md +12 -0
- package/skills/place-node/SKILL.md +47 -0
- package/skills/place-node/scripts/place-node.mts +157 -0
- package/skills/plan-retirement/README.md +32 -0
- package/skills/plan-retirement/SKILL.md +90 -0
- package/skills/plan-retirement/scripts/retire-plans.mts +196 -0
- package/skills/plugin-contract-governance/README.md +12 -0
- package/skills/plugin-contract-governance/SKILL.md +112 -0
- package/skills/remediation-governance/README.md +46 -0
- package/skills/remediation-governance/SKILL.md +78 -0
- package/skills/resolve-governances/README.md +18 -0
- package/skills/resolve-governances/SKILL.md +50 -0
- package/skills/resolve-governances/scripts/resolve-governances.mts +515 -0
- package/skills/resolve-tracking/SKILL.md +64 -0
- package/skills/resolve-tracking/scripts/resolve-tracking.mts +213 -0
- package/skills/resume-mission/README.md +12 -0
- package/skills/resume-mission/SKILL.md +53 -0
- package/skills/scaffold-project-spec/README.md +7 -0
- package/skills/scaffold-project-spec/SKILL.md +192 -0
- package/skills/sdd/README.md +7 -0
- package/skills/sdd/SKILL.md +92 -0
- package/skills/solution-producer-governance/README.md +9 -0
- package/skills/solution-producer-governance/SKILL.md +44 -0
- package/skills/spec-format-governance/README.md +73 -0
- package/skills/spec-format-governance/SKILL.md +114 -0
- package/skills/spec-gate/README.md +26 -0
- package/skills/spec-gate/SKILL.md +201 -0
- package/skills/spec-gate/scripts/check-spec-state.mts +601 -0
- package/skills/spec-gate/scripts/check-suite.mts +501 -0
- package/skills/spec-gate/scripts/classify-edit-class.mts +411 -0
- package/skills/spec-producer-governance/README.md +7 -0
- package/skills/spec-producer-governance/SKILL.md +86 -0
- package/skills/spec-structure-governance/README.md +40 -0
- package/skills/spec-structure-governance/SKILL.md +169 -0
- package/skills/ssa-lowering/README.md +26 -0
- package/skills/ssa-lowering/SKILL.md +181 -0
- package/skills/start-mission/README.md +7 -0
- package/skills/start-mission/SKILL.md +115 -0
- package/skills/suite-format-governance/README.md +75 -0
- package/skills/suite-format-governance/SKILL.md +299 -0
- package/skills/suite-format-governance/references/rubric.md +313 -0
- package/skills/touch-set-correction/README.md +16 -0
- package/skills/touch-set-correction/SKILL.md +67 -0
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +418 -0
- package/skills/verify-scenarios/README.md +17 -0
- package/skills/verify-scenarios/SKILL.md +109 -0
- 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).
|