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,328 @@
1
+ #!/usr/bin/env node
2
+ // manage-spec-anchors — corpus/spec-anchors' concrete curation engine. Declares and curates the
3
+ // OPT-IN extra spec anchors that discover-specs scans on top of the three fixed conventions
4
+ // (ADR-0019). It reads/writes ONLY `.agents/sdd/spec-anchors.toml` — never a spec.md, status,
5
+ // approval, or freeze.
6
+ //
7
+ // Operations:
8
+ // --list list the three fixed anchors (each explained) + every custom anchor
9
+ // --add <pattern> validate + append a custom anchor (creates the config if absent)
10
+ // --remove <pattern> drop a custom anchor (no-op + unchanged when absent)
11
+ // --edit <old> <new> replace one custom anchor (rejects a malformed <new>)
12
+ // --induce <path> candidate patterns for a sample spec dir (literal + <project> capture)
13
+ // --preview <pattern> the project(s) a candidate pattern would discover, without persisting
14
+ //
15
+ // A pattern is a repo-relative directory pattern; `*` globs one segment, `**` globs zero or more
16
+ // segments (any depth), `<project>` globs AND captures a segment as the spec name. Fixed conventions
17
+ // are implicit and cannot be curated here.
18
+ // Pure functions are exported for node:test; running the file directly drives the CLI. No deps.
19
+
20
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
21
+ import { dirname, join } from 'node:path'
22
+
23
+ const ANCHORS_CONFIG = '.agents/sdd/spec-anchors.toml'
24
+ const LIFECYCLE_STATUSES = new Set(['draft', 'approved', 'implemented', 'deprecated'])
25
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.turbo', '.next', 'coverage'])
26
+
27
+ export interface AnchorEntry {
28
+ pattern: string
29
+ kind: 'fixed' | 'custom'
30
+ explanation: string
31
+ }
32
+
33
+ // The three fixed conventions, always scanned by discovery and never curated here.
34
+ export const FIXED_ANCHORS: AnchorEntry[] = [
35
+ { pattern: '.agents/spec/', kind: 'fixed', explanation: 'the repo-root single-project spec' },
36
+ {
37
+ pattern: '.agents/specs/<project>/',
38
+ kind: 'fixed',
39
+ explanation: 'each repo-root multi-project spec (the folder names the project)',
40
+ },
41
+ {
42
+ pattern: '<project-path>/.agents/spec/',
43
+ kind: 'fixed',
44
+ explanation: "a nested project's hoisted spec, at any depth",
45
+ },
46
+ ]
47
+
48
+ // ── Config read/write (a minimal TOML: the `anchors = [ … ]` string array) ──
49
+
50
+ export function parseAnchorsToml(text: string): string[] {
51
+ const m = /(^|\n)\s*anchors\s*=\s*\[([\s\S]*?)\]/.exec(text)
52
+ if (!m) return []
53
+ const out: string[] = []
54
+ for (const q of m[2].matchAll(/"([^"]*)"|'([^']*)'/g)) out.push((q[1] ?? q[2]).trim())
55
+ return out.filter((s) => s !== '')
56
+ }
57
+
58
+ export function readCustomAnchors(root: string): string[] {
59
+ const file = join(root, ANCHORS_CONFIG)
60
+ if (!existsSync(file)) return []
61
+ try {
62
+ return parseAnchorsToml(readFileSync(file, 'utf8'))
63
+ } catch {
64
+ return []
65
+ }
66
+ }
67
+
68
+ export function serializeAnchors(patterns: string[]): string {
69
+ if (patterns.length === 0) return 'anchors = []\n'
70
+ return `anchors = [\n${patterns.map((p) => ` "${p}",`).join('\n')}\n]\n`
71
+ }
72
+
73
+ function writeCustomAnchors(root: string, patterns: string[]): void {
74
+ const file = join(root, ANCHORS_CONFIG)
75
+ mkdirSync(dirname(file), { recursive: true })
76
+ writeFileSync(file, serializeAnchors(patterns))
77
+ }
78
+
79
+ // ── Validation ──
80
+
81
+ // A pattern is well-formed iff it is repo-relative (no leading slash, no `..`) and every segment is
82
+ // a literal, `*`, `**`, or the `<project>` capture token (no other `<…>` token, no empty segment).
83
+ export function isValidPattern(pattern: string): boolean {
84
+ const p = pattern.trim().replace(/\\/g, '/')
85
+ if (p === '' || p.startsWith('/') || /(^|\/)\.\.(\/|$)/.test(p)) return false
86
+ const segs = p.replace(/^\/+|\/+$/g, '').split('/')
87
+ if (segs.some((s) => s === '')) return false
88
+ return segs.every((s) => s === '*' || s === '**' || s === '<project>' || !/[<>]/.test(s))
89
+ }
90
+
91
+ // True when a pattern names one of the three fixed conventions (which are implicit, not curated).
92
+ export function isFixedConvention(pattern: string): boolean {
93
+ const p = pattern
94
+ .trim()
95
+ .replace(/\\/g, '/')
96
+ .replace(/^\/+|\/+$/g, '')
97
+ return (
98
+ p === '.agents/spec' ||
99
+ p === '.agents/specs/<project>' ||
100
+ p === '.agents/specs/*' ||
101
+ p.endsWith('/.agents/spec') ||
102
+ p.endsWith('/.agents/specs/<project>')
103
+ )
104
+ }
105
+
106
+ // ── list ──
107
+
108
+ export function listAnchors(root: string): AnchorEntry[] {
109
+ const custom = readCustomAnchors(root).map(
110
+ (pattern): AnchorEntry => ({ pattern, kind: 'custom', explanation: 'a declared extra anchor' }),
111
+ )
112
+ return [...FIXED_ANCHORS, ...custom]
113
+ }
114
+
115
+ // ── add / remove / edit ──
116
+
117
+ export type CrudResult =
118
+ | { ok: true; changed: boolean; anchors: string[]; message: string }
119
+ | { ok: false; reason: string }
120
+
121
+ export function addAnchor(root: string, pattern: string): CrudResult {
122
+ const p = pattern.trim()
123
+ if (isFixedConvention(p)) return { ok: false, reason: 'that is a fixed convention — always scanned, never curated' }
124
+ if (!isValidPattern(p)) return { ok: false, reason: 'malformed anchor pattern' }
125
+ const current = readCustomAnchors(root)
126
+ if (current.includes(p)) return { ok: true, changed: false, anchors: current, message: 'already present' }
127
+ const next = [...current, p]
128
+ writeCustomAnchors(root, next)
129
+ return { ok: true, changed: true, anchors: next, message: `added ${p}` }
130
+ }
131
+
132
+ export function removeAnchor(root: string, pattern: string): CrudResult {
133
+ const p = pattern.trim()
134
+ const current = readCustomAnchors(root)
135
+ if (!current.includes(p)) return { ok: true, changed: false, anchors: current, message: 'nothing was removed' }
136
+ const next = current.filter((a) => a !== p)
137
+ writeCustomAnchors(root, next)
138
+ return { ok: true, changed: true, anchors: next, message: `removed ${p}` }
139
+ }
140
+
141
+ export function editAnchor(root: string, oldPattern: string, newPattern: string): CrudResult {
142
+ const oldP = oldPattern.trim()
143
+ const newP = newPattern.trim()
144
+ if (isFixedConvention(oldP)) return { ok: false, reason: 'cannot edit a fixed convention' }
145
+ if (!isValidPattern(newP)) return { ok: false, reason: 'malformed replacement pattern' }
146
+ if (isFixedConvention(newP)) return { ok: false, reason: 'replacement is a fixed convention' }
147
+ const current = readCustomAnchors(root)
148
+ if (!current.includes(oldP)) return { ok: false, reason: `no such custom anchor: ${oldP}` }
149
+ const next = current.map((a) => (a === oldP ? newP : a))
150
+ writeCustomAnchors(root, next)
151
+ return { ok: true, changed: true, anchors: next, message: `edited ${oldP} -> ${newP}` }
152
+ }
153
+
154
+ // ── induce ──
155
+
156
+ export type InduceResult = { ok: true; candidates: string[] } | { ok: false; reason: string }
157
+
158
+ // From a sample path to a spec's directory, offer a literal-directory candidate and a <project>-token
159
+ // generalization (deepest segment replaced with the capture). Rejects a path that is not a directory
160
+ // under the repo root.
161
+ export function inducePatterns(root: string, samplePath: string): InduceResult {
162
+ const rel = samplePath
163
+ .trim()
164
+ .replace(/\\/g, '/')
165
+ .replace(/^\/+|\/+$/g, '')
166
+ if (rel === '' || /(^|\/)\.\.(\/|$)/.test(rel)) return { ok: false, reason: 'path is not repo-relative' }
167
+ let ok = false
168
+ try {
169
+ ok = statSync(join(root, rel)).isDirectory()
170
+ } catch {
171
+ ok = false
172
+ }
173
+ if (!ok) return { ok: false, reason: `sample path does not resolve to a directory under the repo: ${rel}` }
174
+ const segs = rel.split('/')
175
+ const generalized = [...segs.slice(0, -1), '<project>'].join('/')
176
+ const candidates = generalized === rel ? [rel] : [rel, generalized]
177
+ return { ok: true, candidates }
178
+ }
179
+
180
+ // ── preview ──
181
+
182
+ interface PreviewMatch {
183
+ path: string
184
+ name: string
185
+ }
186
+
187
+ export type PreviewResult = { ok: true; matches: PreviewMatch[] } | { ok: false; reason: string }
188
+
189
+ // Every dir reachable from `startDir` by descending zero or more levels (startDir itself included),
190
+ // skipping SKIP_DIRS. Backs the `**` segment (any-depth glob) below.
191
+ function collectDescendants(root: string, startDir: string): string[] {
192
+ const out = [startDir]
193
+ let entries: import('node:fs').Dirent[]
194
+ try {
195
+ entries = readdirSync(join(root, startDir), { withFileTypes: true })
196
+ } catch {
197
+ return out
198
+ }
199
+ for (const e of entries) {
200
+ if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue
201
+ out.push(...collectDescendants(root, startDir ? `${startDir}/${e.name}` : e.name))
202
+ }
203
+ return out
204
+ }
205
+
206
+ // Expand a pattern against the filesystem (mirrors discover-specs' expandAnchor), then keep only the
207
+ // dirs holding a spec.md whose frontmatter status is in the lifecycle enum — the specs discovery
208
+ // would actually surface. Does NOT read or persist anything to the config.
209
+ export function previewPattern(root: string, pattern: string): PreviewResult {
210
+ const p = pattern.trim()
211
+ if (!isValidPattern(p)) return { ok: false, reason: 'malformed candidate pattern' }
212
+ const segs = p
213
+ .replace(/\\/g, '/')
214
+ .replace(/^\/+|\/+$/g, '')
215
+ .split('/')
216
+ .filter(Boolean)
217
+ let frontier: { dir: string; capturedName?: string }[] = [{ dir: '' }]
218
+ for (const seg of segs) {
219
+ const next: { dir: string; capturedName?: string }[] = []
220
+ if (seg === '**') {
221
+ for (const node of frontier) {
222
+ for (const dir of collectDescendants(root, node.dir)) next.push({ dir, capturedName: node.capturedName })
223
+ }
224
+ frontier = next
225
+ continue
226
+ }
227
+ const isGlob = seg === '*' || seg === '<project>'
228
+ for (const node of frontier) {
229
+ if (isGlob) {
230
+ let entries: import('node:fs').Dirent[]
231
+ try {
232
+ entries = readdirSync(join(root, node.dir), { withFileTypes: true })
233
+ } catch {
234
+ continue
235
+ }
236
+ for (const e of entries) {
237
+ if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue
238
+ next.push({
239
+ dir: node.dir ? `${node.dir}/${e.name}` : e.name,
240
+ capturedName: seg === '<project>' ? e.name : node.capturedName,
241
+ })
242
+ }
243
+ } else {
244
+ next.push({ dir: node.dir ? `${node.dir}/${seg}` : seg, capturedName: node.capturedName })
245
+ }
246
+ }
247
+ frontier = next
248
+ }
249
+ const matches: PreviewMatch[] = []
250
+ for (const node of frontier) {
251
+ const rel = node.dir ? `${node.dir}/spec.md` : 'spec.md'
252
+ let status: string | undefined
253
+ try {
254
+ status = /(^|\n)status:\s*["']?([a-z]+)/.exec(readFileSync(join(root, rel), 'utf8'))?.[2]
255
+ } catch {
256
+ continue
257
+ }
258
+ if (!status || !LIFECYCLE_STATUSES.has(status)) continue
259
+ matches.push({ path: node.dir, name: node.capturedName ?? node.dir.split('/').pop() ?? node.dir })
260
+ }
261
+ matches.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
262
+ return { ok: true, matches }
263
+ }
264
+
265
+ // ── CLI ──
266
+
267
+ function flag(argv: string[], name: string): string | undefined {
268
+ const i = argv.indexOf(name)
269
+ return i === -1 ? undefined : argv[i + 1]
270
+ }
271
+
272
+ export function main(argv: string[]): number {
273
+ const root = flag(argv, '--root') ?? '.'
274
+ const w = (s: string) => process.stdout.write(`${s}\n`)
275
+
276
+ if (argv.includes('--list')) {
277
+ w('Spec anchors discovery scans (fixed conventions are always scanned):')
278
+ for (const a of listAnchors(root)) w(` [${a.kind}] ${a.pattern} — ${a.explanation}`)
279
+ return 0
280
+ }
281
+ if (argv.includes('--add')) {
282
+ const r = addAnchor(root, flag(argv, '--add') ?? '')
283
+ w(r.ok ? r.message : `refused: ${r.reason}`)
284
+ return r.ok ? 0 : 1
285
+ }
286
+ if (argv.includes('--remove')) {
287
+ const r = removeAnchor(root, flag(argv, '--remove') ?? '')
288
+ w(r.ok ? r.message : `refused: ${r.reason}`)
289
+ return r.ok ? 0 : 1
290
+ }
291
+ if (argv.includes('--edit')) {
292
+ const i = argv.indexOf('--edit')
293
+ const r = editAnchor(root, argv[i + 1] ?? '', argv[i + 2] ?? '')
294
+ w(r.ok ? r.message : `refused: ${r.reason}`)
295
+ return r.ok ? 0 : 1
296
+ }
297
+ if (argv.includes('--induce')) {
298
+ const r = inducePatterns(root, flag(argv, '--induce') ?? '')
299
+ if (!r.ok) {
300
+ w(`unusable: ${r.reason}`)
301
+ return 1
302
+ }
303
+ w('Candidate anchor patterns:')
304
+ for (const c of r.candidates) w(` ${c}`)
305
+ return 0
306
+ }
307
+ if (argv.includes('--preview')) {
308
+ const r = previewPattern(root, flag(argv, '--preview') ?? '')
309
+ if (!r.ok) {
310
+ w(`invalid: ${r.reason}`)
311
+ return 1
312
+ }
313
+ if (r.matches.length === 0) {
314
+ w('This pattern matches no project.')
315
+ return 0
316
+ }
317
+ w('This pattern would discover:')
318
+ for (const m of r.matches) w(` ${m.path} (${m.name})`)
319
+ return 0
320
+ }
321
+
322
+ w(
323
+ 'usage: manage-spec-anchors --list | --add <p> | --remove <p> | --edit <old> <new> | --induce <path> | --preview <p>',
324
+ )
325
+ return 0
326
+ }
327
+
328
+ if (import.meta.main) process.exit(main(process.argv.slice(2)))
@@ -0,0 +1,15 @@
1
+ # mission-graph
2
+
3
+ The concrete engine for the **mission-graph kernel** — a git-tracked, append-only work-graph store
4
+ (nodes, edges, status, tombstones, schema `v:1`) folded into a read-only `ready` frontier and a
5
+ `cycles` repair view, plus an Operation deliverability check. Built for Op1.M1 of the
6
+ **cyberfleet-batch** change request; see
7
+ [`.agents/specs/sdd/mission-graph/README.md`](../../../../.agents/specs/sdd/mission-graph/README.md)
8
+ for the authoritative behavior description and
9
+ [`mission-graph.feature`](../../../../.agents/specs/sdd/mission-graph/mission-graph.feature) for
10
+ the frozen 36-scenario contract.
11
+
12
+ - **Skill contract:** [`SKILL.md`](./SKILL.md)
13
+ - **Script:** [`scripts/mission-graph.mts`](./scripts/mission-graph.mts)
14
+ - **Tests:** [`scripts/mission-graph.test.mts`](./scripts/mission-graph.test.mts) (`node:test`) —
15
+ one test per frozen scenario, titled `scenario: <verbatim frozen scenario name>`.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: mission-graph
3
+ description: "Internal skill: the mission-graph kernel — a git-tracked, append-only work-graph store folded into a ready frontier and a cycles repair view; used by the cyberfleet-batch dispatch loop, not triggered by users directly."
4
+ user-invocable: false
5
+ metadata:
6
+ internal: true
7
+ ---
8
+
9
+ # Mission Graph
10
+
11
+ The concrete engine for the **mission-graph kernel** (Op1.M1 of cyberfleet-batch): a project's work
12
+ list — Missions and Operations, the RAW/parent-child/discovered-from links between them, status
13
+ changes, and tombstones — written down as a **git-tracked, append-only event log** (schema `v:1`),
14
+ plus a zero-dependency **fold** into two read-only views and one write-time guard:
15
+
16
+ - **`ready`** — every Mission that is RAW-satisfied (every dependency retired, transitively) and
17
+ not WAW-held (its declared touch-set doesn't clash with in-flight work or a lower-id peer).
18
+ Deterministic and read-only.
19
+ - **`cycles`** — every RAW strongly-connected component, reported as a repair item. The fold never
20
+ throws on a knotted plan; every Mission on a cycle (and anything depending on it) is quarantined
21
+ out of `ready` instead.
22
+ - **`checkOperation`** — is a hand-declared Operation dependency-closed (the capstone's RAW closure
23
+ ⊆ the declared set), what's its release floor (the closure alone — support members don't gate
24
+ it), and its completed/total progress.
25
+
26
+ It carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention). Pure
27
+ derivations (`fold`, `ready`, `cycles`, `checkOperation`, `proposeEdge`) take and return plain data
28
+ only — no fs access — kept apart from a thin store-IO seam (v1: an in-tree JSONL file) so a later
29
+ swap to a shared, branch-independent store never touches them.
30
+
31
+ ## Run it
32
+
33
+ ```bash
34
+ node "<skill>/scripts/mission-graph.mts" ready [--root .] [--format toon|json]
35
+ node "<skill>/scripts/mission-graph.mts" cycles [--root .] [--format toon|json]
36
+ node "<skill>/scripts/mission-graph.mts" operation --id <operation-id> [--root .] [--format toon|json]
37
+
38
+ # the write path (append-only; --root defaults to .)
39
+ node "<skill>/scripts/mission-graph.mts" append node --id <id> \
40
+ [--kind mission|operation] [--status open|claimed|retired] [--touch-set a,b,c] \
41
+ [--blast <level>] [--hitl|--afk] [--model-tier <tier>] [--brief-pointer <path>] [--capstone <id>]
42
+
43
+ node "<skill>/scripts/mission-graph.mts" append edge --kind RAW|parent-child|discovered-from \
44
+ --from <id> --to <id> [--override]
45
+
46
+ node "<skill>/scripts/mission-graph.mts" append tombstone --target node --id <id>
47
+ node "<skill>/scripts/mission-graph.mts" append tombstone --target edge --kind <kind> --from <id> --to <id>
48
+ ```
49
+
50
+ - Default `--root` is the current directory; the store lives at
51
+ `<root>/.agents/mission-graph/events.jsonl`. Default `--format` is **TOON**; `--format json`
52
+ emits the same records as JSON for non-LLM consumers.
53
+ - `append edge` runs the **write-time cycle guard** first: a RAW edge that would close a loop is
54
+ **rejected** unless `--override` is passed (a genuinely-discovered mutual dependency). A
55
+ parent-child/discovered-from edge is never guarded — only RAW ("wait for") edges can cycle.
56
+ - `ready`'s frontier entries carry `id, node, operation, blast, hitlOrAfk, modelTier, briefPointer,
57
+ whyReady` — `node` is the node's kind (always `mission`: an Operation is a container, never
58
+ itself scheduled).
59
+
60
+ ## Boundaries
61
+
62
+ Read-only derivation + an append-only write path over one in-tree store — it does **not** decide
63
+ how to split a request into Missions, compute a Mission's touch-set, tell a real collision from a
64
+ false one at finer-than-node grain (v1: any same-node touch-set overlap is a hard collision), rank
65
+ or annotate the frontier, run or assign a Mission, or coordinate a fleet of agents (all deferred,
66
+ see the spec's Non-goals). Status is read **only** from the graph, never from a Mission's
67
+ `.plan.md` brief — the graph is the sole scheduling authority.