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,257 @@
1
+ ---
2
+ name: combat-log-governance
3
+ description: "Partial Skill: invoke by name only — the SDD combat-log contract, the durable provenance record's shape. Loaded by the conductor, spec-gate, and the doctrine-loop Scanner, not user-triggered."
4
+ user-invocable: false
5
+ ---
6
+
7
+ # SDD Combat-Log Governance
8
+
9
+ The durable, harness-agnostic record of a spec's missions — what was produced, what was judged, what
10
+ was corrected, and the strategy distilled from it. This skill defines the **shape**; the tracked
11
+ deletion of a retired plan is the `plan-retirement` skill.
12
+
13
+ ## Two faces, two homes
14
+
15
+ The record has two complementary faces: current-state in `spec.md` frontmatter
16
+ (contract), the durable history in a sibling `ledger/` **directory** of per-writer shard files, sibling to the **root** `spec.md`.
17
+
18
+ | Face | Home | Shape | Mutability | Holds |
19
+ |---|---|---|---|---|
20
+ | **Current-state** | `spec.md` frontmatter | `produced-by` (map by role) + `approval` (map by gate) | **overwritten** — last write wins | the **standing** present: who produced each artifact, the latest CR's verdict per gate |
21
+ | **Ledger** | `ledger/` dir (root sibling), one `<cr-ref>.<hash>.jsonl` shard per CR per writer | one JSON object per line, appended to the writer's **own** shard | **immutable** — appended, never edited | the durable **history**: every CR's run-start `leash` block + `gate` verdict + `strategy` |
22
+
23
+ `approval` is **standing, not historical** — the one durable spec is flowed through by many CRs, and
24
+ `spec.md` `approval` holds only the **latest** CR's verdict (overwritten each time). The durable
25
+ per-CR record (*"CR #34's diff was approved by X"*) is a `gate` ledger line, keyed by `cr`. There is
26
+ no per-CR `approval` block and no sidecar file.
27
+
28
+ **The ledger is operational provenance, not contract** — the `ledger/` shards are **never frozen and never
29
+ gated**: writers keep appending across the whole lifecycle, including while `spec.md` + the `.feature`
30
+ are frozen at `approved`.
31
+
32
+ ## Two logs: the combat log (plan) vs the ledger (sibling dir)
33
+
34
+ Provenance splits by lifetime. Mid-flight detail is **per-mission and tracked with the work, then
35
+ removed at retro** (durable in git history); the durable record is sparse and outlives the CR.
36
+
37
+ - **Combat log** — `.agents/plans/<cr-ref>.log.jsonl`, beside the plan brief. Holds the chatty
38
+ mid-flight `report` / `correction` / `halt` lines. Tracked (committed, kept in the PR), deleted at retro
39
+ once distilled and the source is done/merged. Already one file per CR, so it never had the shared-file
40
+ merge problem.
41
+ - **Ledger** — the `ledger/` **directory**, sibling to the root `spec.md`. Holds only the sparse durable
42
+ `leash` / `gate` / `strategy` lines, as one `<cr-ref>.<hash>.jsonl` shard **per CR per writer**. Never
43
+ deleted.
44
+
45
+ **Sharded storage (ADR-0020).** Each writer appends only to its **own** shard, so no two writers ever
46
+ touch the same file — concurrent appends (two branches, or two sessions sharing one working tree) are
47
+ **non-colliding by construction**. A single shared `ledger.jsonl` conflicted on every concurrent mission
48
+ (EOF-append merge conflict) or was silently clobbered by a same-tree fork; sharding removes the shared
49
+ path, so **no merge driver is used or needed**. The reader **globs** `ledger/*.jsonl` (plus a legacy
50
+ `ledger.jsonl` if present) and concatenates. `<hash>` is **6 random hex minted once per writer-session**
51
+ (random, **not** a machine/host/user id — that would leak identity); same session + same CR → same shard.
52
+
53
+ "Combat log" always means the live per-mission log in the plan; "ledger" always means the durable
54
+ sibling `ledger/` directory. They are never the same store.
55
+
56
+ ## Entry shapes
57
+
58
+ One JSON object per line (JSON Lines). Every line carries a **`seq`** (append order *within its shard* —
59
+ its shard's own line count, restarting per shard, never a global counter), an optional pseudonymous
60
+ **`handle`**, and a `kind`. **Combat-log** lines additionally carry a **write-time UTC `ts`**; **ledger**
61
+ lines carry **no wall-clock time** (below). Seven kinds, split by tier: `report` / `correction` / `halt`
62
+ → the combat log; `leash` / `gate` / `strategy` / `followup` → the ledger. Every line carries an optional
63
+ `cr` (the one project ledger spans many CRs against the one durable spec; outer-loop `strategy` lines may
64
+ omit it).
65
+
66
+ **Safe-to-publish floor (committed-record rule).** The combat log is committed → every line is
67
+ **published to git history permanently** ("deleted at retro" is tree-only) and a distilled line may
68
+ go **upstream via Forge**. The floor binds **all** fields:
69
+
70
+ - **Categorical only** — structured fields are enums; the free-text `summary` / `detail` give the
71
+ decision or its class, commit-message-grade.
72
+ - **Never committed:** email, OS usernames, hostnames, absolute paths, session/machine ids, secrets,
73
+ code, prompts, literal values, **raw numbers** (token/cost) — those stay in the uncommitted transcripts.
74
+ - **Identity is a pseudonym** (`handle`, below), never `user.email`.
75
+
76
+ **Write-time `ts` — combat-log lines only.** `report` / `correction` / `halt` carry a UTC `ts` (ISO-8601)
77
+ stamped at write-time — the doctrine loop reads the committed combat log post-merge (possibly another
78
+ machine), when the session clock is gone; within a mission `ts` orders those lines and feeds the pre-merge
79
+ coarse-duration signal the efficiency dimension reads from the raw transcripts. **Ledger lines (`leash` /
80
+ `gate` / `strategy`) carry no `ts`** — they are the forever-public durable record, and a wall-clock stamp
81
+ on a committed cross-machine artifact leaks activity timing/timezone for no load-bearing gain (nothing
82
+ reads ledger `ts`; ordering within a shard is `seq`; the cross-mission timeline is git history). Legacy
83
+ ledger lines written before ADR-0020 carry a `ts` and are grandfathered (append-only, never rewritten).
84
+
85
+ **Identity — the per-entry `handle`.** `report` / `correction` / `strategy` carry a `handle` (the
86
+ writer's pseudonym); a `gate` line keeps `by` (the ratifier). Resolution at write-time: `SDD_HANDLE`
87
+ (env) if set, else omit `handle` and fall back to the git commit author; **never** `user.email`,
88
+ never a `git config` read. The in-file `handle` / `by` is **advisory, not proof** — a self-asserter
89
+ can write any string, so the git commit signature plus positional authority are the control, not the
90
+ field.
91
+
92
+ ### `report` — per-subagent dispatch (combat log)
93
+
94
+ ```jsonl
95
+ {"seq": 3, "ts": "2026-06-28T18:30:11Z", "handle": "unional", "kind": "report", "role": "spec-producer", "agent": "sdd:automaton", "outcome": "pass", "summary": "wrote 14 scenarios covering the ledger expansion"}
96
+ ```
97
+
98
+ `role` is the production role dispatched; `agent` is the plugin-qualified agent name; `outcome` is
99
+ `pass | fail`.
100
+
101
+ ### `correction` — correction-with-cause (combat log)
102
+
103
+ One line per correction: a gate rejection, a producer⇄judge iteration, or a Council kick-back. The
104
+ matchable `cause` is the load-bearing field; at retro the doctrine loop folds recurring `cause`s into
105
+ the ledger's `strategy` count.
106
+
107
+ ```jsonl
108
+ {"seq": 7, "ts": "2026-06-28T18:41:02Z", "handle": "unional", "kind": "correction", "correction-kind": "gate-reject", "cause": "coverage-gap", "detail": "spec gate rejected — no negative scenario for the malformed-entry path"}
109
+ ```
110
+
111
+ - **`correction-kind`** — the closed set `gate-reject | judge-iteration | council-kickback` (the
112
+ *occasion*, not the cause).
113
+ - **`cause`** — a minimal, **discovered** enum (the matchable category of *why*). Grounded so far:
114
+
115
+ | Cause | Means |
116
+ |---|---|
117
+ | `coverage-gap` | a use case or operation lacked a covering scenario |
118
+ | `design-overreach` | the design added a mechanism the architecture did not need |
119
+ | `spec-feature-contradiction` | the `spec.md` body and the `.feature` asserted contradictory behavior |
120
+ | `prose-impl-contradiction` | a skill's own operating docs or a sibling design doc asserted behavior the shipped implementation no longer has |
121
+
122
+ **Growth:** closed at any moment, discovered from usage — a new value is added only when a real
123
+ recurring correction has no category. Adding one is an **edit to this governance, ratified by the
124
+ Council** (a producer/judge/conductor never edits the enum). An **absent or off-enum `cause` fails
125
+ closed** (it breaks cross-mission matchability).
126
+
127
+ **Efficiency** is a categorical correction class the committed log is designed to carry — the
128
+ conductor flagging notable token-waste (a class, **never raw counts**), so the post-merge doctrine
129
+ loop keeps the dimension. Its concrete `correction-kind` / `cause` are **not seeded**; they enter by
130
+ the same Council-ratified growth, and the numeric depth stays transcript-only (the floor admits no
131
+ raw token number).
132
+
133
+ - **Durability discipline (the conductor's write duty).** A `correction` is a discrete line, never
134
+ left folded only into a verdict `why` (the doctrine loop matches `cause`, not prose):
135
+ - **At a gate reached via a judge-reject→fix→pass**, the self-asserting conductor appends the
136
+ `correction` line (`correction-kind: judge-iteration`, a matchable `cause`) **before** the gate
137
+ `why` it summarizes. A gate that passed clean with no iteration appends none.
138
+ - **At mission finalize**, a mission carrying a real correction whose line was **never flushed**
139
+ writes it now — **creating the combat log if none exists** — so the `cause` survives even the
140
+ no-log mission class (a mission with no correction forces nothing). The forced line stays a
141
+ combat-log `correction`, never a ledger line (the tier split above is invariant); its durability
142
+ is the retro distillation of the committed log into the ledger's `strategy` count.
143
+
144
+ ### `halt` — a mid-flight stop, not at a gate (combat log)
145
+
146
+ The agent halts mid-phase (a hard floor, an input it cannot supply, a blast radius it will not cross).
147
+ A gate-time stop is a `gate` line (`verdict: pause`); this `halt` line is its mid-flight twin, so *"why I
148
+ halted"* is as durable as *"why I went"*. **Flush it to the committed log during the mission** — the
149
+ doctrine loop reads only the committed log post-merge.
150
+
151
+ ```jsonl
152
+ {"seq": 5, "ts": "2026-06-28T18:50:33Z", "handle": "unional", "kind": "halt", "phase": "explore", "why": {"floor": "clearance", "blast": "high — would drop scenarios from a frozen suite", "novelty": "low", "confidence": "high"}}
153
+ ```
154
+
155
+ - **`phase`** — `intake | explore | deliver | handoff`, where the mission stopped.
156
+ - **`why`** — the same categorical block the `approval` map carries (`floor` / `blast` / `novelty` /
157
+ `confidence`), **classes only** — never the raw blocker content.
158
+
159
+ ### `gate` — the durable per-CR gate verdict (ledger)
160
+
161
+ ```jsonl
162
+ {"seq": 2, "kind": "gate", "cr": 34, "gate": "spec", "verdict": "approve", "by": "unional", "cause": "dimension", "frozen": ["intake/intake.feature", "mission/mission.feature"]}
163
+ ```
164
+
165
+ - **`gate`** — `spec | impl`. **`verdict`** — `approve | pause | reject`. **`by`** — a human name
166
+ (ratified) or `agent` (self-asserted, provisional; carries the `why` derivation).
167
+ - **`cause`** — `dimension | ceiling` (the **stop cause**, distinct from a `correction`'s matchable
168
+ `cause`).
169
+ - **`frozen`** — the suite files this verdict froze (spec-gate `approve` only), so the ledger answers
170
+ *"what was frozen as of CR #34"* standalone — no git walk.
171
+
172
+ ### `leash` — the conductor's run-start autonomy block (ledger)
173
+
174
+ The conductor's **initial strategy evaluation**, written once at run start: the run-level `leash`
175
+ reach + the `approach[]` containment methods. It is the conductor's autonomy bar for the mission —
176
+ **not** the Scanner's `strategy`, carries **no** `ratified` field, and is **never** counted as
177
+ pending strategy. The write is owned by the **conductor** (`start-mission`).
178
+
179
+ ```jsonl
180
+ {"seq": 1, "kind": "leash", "cr": "disambiguate-strategy-kind", "leash": "auto-spec", "by": "user", "blast": "medium", "approach": ["no-spike", "worktree"]}
181
+ ```
182
+
183
+ `leash` — `auto-none | auto-spec | auto-all`; `by` — `derived | user`; `blast` — the assessed
184
+ radius; `approach[]` — containment methods. The ceiling is not recorded (session-local). Pre-rename
185
+ historical run-start blocks appear as `kind: strategy` and are grandfathered (append-only ledger).
186
+
187
+ ### `strategy` — drafted strategy (ledger)
188
+
189
+ The Scanner records drafted strategy; this contract defines the **shape**, the **write is owned by
190
+ the doctrine-loop Scanner**. It carries the distilled recurrence count for a `cause` (in `evidence`).
191
+
192
+ ```jsonl
193
+ {"seq": 1, "handle": "sdd-scanner", "kind": "strategy", "recommendation": "codify the coverage-gap pattern as a spec-format-governance check", "evidence": ["coverage-gap x3 across sdd-foo, sdd-bar, sdd-baz"], "ratified": false}
194
+ ```
195
+
196
+ `ratified: false` means the Council holds keep-or-cut — unratified strategy never enters the corpus.
197
+
198
+ **The `distills` subject.** A strategy drafted from a **Ship** (`→ implemented`) or **Kill**
199
+ (`→ deprecated`) records the **one mission it was distilled from** in a `distills` field carrying that
200
+ mission's `<cr-ref>` — the same identifier that names the plan and the mission's `cr` on `leash` /
201
+ `gate` lines:
202
+
203
+ ```jsonl
204
+ {"seq": 2, "handle": "sdd-scanner", "kind": "strategy", "distills": "referenced-artifact-escalation", "recommendation": "...", "evidence": ["cross-ref: d2-correction-line-durability", "cross-ref: ba6a39"], "ratified": false}
205
+ ```
206
+
207
+ `distills` names the **subject** (the mission the line was drafted from); the cr-refs in `evidence`
208
+ are **cross-references** the recommendation leans on — never confuse the two. `distills` is the
209
+ machine-checkable hook the retirement sweep keys on to confirm a plan was distilled before deleting
210
+ its combat log (`sdd:plan-retirement` — the gate keys on `distills`, **never** an `evidence` mention,
211
+ and an **unratified** entry still counts). Milestone / drift / token-waste strategy that has **no
212
+ single subject mission omits `distills`** — only a Ship or Kill distillation gates a retirement.
213
+
214
+ ### `followup` — a recorded follow-up (ledger)
215
+
216
+ The durable record of work handoff identified but held out of scope. Written by the **conductor at
217
+ handoff**, unconditionally — no permission, no forge, no human — and **before any filing to the forge
218
+ is attempted**. It is a ledger kind, never a combat-log kind: the combat log is deleted from the tree
219
+ at retro, and a follow-up must outlive its mission.
220
+
221
+ ```jsonl
222
+ {"seq": 4, "kind": "followup", "cr": "github-237-handoff-followups", "class": "blocking", "summary": "Operator's admission (proposeEdge) has no dedupe against RAW cycles for follow-up edges", "contradicts": "handoff proposes follow-ups; nothing yet admits them", "evidence": ["cyberfleet-plugin/operator README claims single-writer admission, unimplemented"]}
223
+ ```
224
+
225
+ - **`class`** — `blocking` (the follow-up **contradicts a completion claim the mission already made**;
226
+ the line **names that claim** in `contradicts`) or `backlog` (genuinely new territory — `contradicts`
227
+ is omitted). A finding that the mission's own **frozen contract** was wrong is **not** a `followup` at
228
+ all — it is an Oracle-lens revert inside that mission, never routed here.
229
+ - **`contradicts`** — required when `class: blocking`; names the completion claim the follow-up
230
+ contradicts.
231
+ - **`evidence`** — the categorical support for the classification, commit-message-grade (the same floor
232
+ as every other field).
233
+ - **No filed-state, ever.** The line is never edited to mark it filed — the ledger is append-only. What
234
+ is still outstanding is **re-derived** at each drain by deduping against the forge's existing issues,
235
+ **open or closed** (matching only open ones would re-file a duplicate for a follow-up already filed
236
+ and resolved).
237
+ - **A proposal, not a verdict.** Recording a `followup` line grants nothing on its own — admission to
238
+ the mission graph is the graph's single writer's act; the conductor writes no node or edge here.
239
+
240
+ ## Write ownership
241
+
242
+ Append-only; each writer adds lines to its **own shard** with the next `seq` within that shard, never
243
+ editing another writer's shard, and never editing or deleting a prior line. Full matrix in
244
+ `sdd:ownership-governance`:
245
+
246
+ | Writer | May append | To |
247
+ |---|---|---|
248
+ | **conductor** | `report`, `correction`, `halt` | the **combat log** (plan `*.log.jsonl`) |
249
+ | **conductor** | run-start `leash` block (leash reach + `approach[]`) | the **ledger** |
250
+ | **conductor** | self-asserted `gate` (`by: agent`) | the **ledger** |
251
+ | **conductor** | `followup` (record, at handoff, unconditionally) | the **ledger** |
252
+ | **gate skill (`spec-gate`), in-session** | human-ratified `gate` (`by: <name>`) | the **ledger** |
253
+ | **doctrine-loop Scanner** | `strategy` | the **ledger** |
254
+ | producers / judges | nothing | — |
255
+
256
+ A human-ratified `gate` line follows the **positional authority** rule (`sdd:lifecycle-governance`):
257
+ only the in-session position holding the user channel writes `by: <name>`.
@@ -0,0 +1,13 @@
1
+ # concept-index
2
+
3
+ Internal SDD skill — the concrete engine for the **concept-index** step. Scans one project-spec for
4
+ every node's `concept:` frontmatter and renders the **by-concept view** into the root `spec.md`,
5
+ re-unifying a cross-cutting concern the capability folder tree scatters.
6
+
7
+ ```bash
8
+ node scripts/concept-index.mts --spec-dir <spec> --write # refresh the block
9
+ node scripts/concept-index.mts --spec-dir <spec> --check # no-drift guard
10
+ ```
11
+
12
+ Pure derivation, frontmatter only; the write touches only the delimited generated block. See
13
+ [`SKILL.md`](./SKILL.md) for the full contract. Not user-invocable.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: concept-index
3
+ description: "Partial Skill: invoke by name only — project-spec/concept-index's engine that derives the by-concept view of a project spec into spec.md — used to re-unify a cross-cutting concern the capability folder tree scatters, not triggered by users directly."
4
+ user-invocable: false
5
+ metadata:
6
+ internal: true
7
+ ---
8
+
9
+ # Concept Index
10
+
11
+ The concrete engine for the **concept-index** step. It scans
12
+ one project-spec for every node's `concept:` frontmatter and renders the **by-concept view** —
13
+ `concept → {its nodes across every folder}` — that re-unifies a cross-cutting concern the capability
14
+ folder tree scatters (the concept axis: one concern enacted across several capability folders). It
15
+ carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention).
16
+
17
+ ## Run it
18
+
19
+ ```bash
20
+ node "<skill>/scripts/concept-index.mts" --spec-dir <spec> [--write | --check]
21
+ ```
22
+
23
+ - default (no mode) — print the rendered "By concept" section to stdout (dry run).
24
+ - `--write` — replace the generated block in `<spec>/spec.md` with the freshly rendered table;
25
+ inserts the block at the `## Invariants` anchor when the markers are absent.
26
+ - `--check` — exit non-zero when `spec.md`'s block differs from the freshly rendered table (the
27
+ no-drift guard for CI).
28
+
29
+ The view is **pure derivation** from the `concept:` tags: rendering twice is byte-identical and a
30
+ `--write` over a current block is a no-op. Each node is annotated by **facet kind** — a node under `design/` → rule,
31
+ under `workflows/` → workflow, else `reference` / `behavior` / `index` from its `spec-type`.
32
+
33
+ ## Boundaries
34
+
35
+ Frontmatter only — no node body reaches the output. The write touches **only** the content between the
36
+ generated-block markers; lifecycle frontmatter, prose, and the capability map are left untouched. It
37
+ owns no lifecycle state and renders no verdict. When `node` is absent, an agent performs the same
38
+ derivation by hand: read each node's `concept:` tag, group by concept, and render the table.
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ // concept-index — project-spec/concept-index's concrete engine. Scans one project-spec for every node's
3
+ // `concept:` frontmatter and renders the by-concept view (concept → its nodes across every folder),
4
+ // which re-unifies a cross-cutting concern the capability folder tree scatters
5
+ // (the concept axis).
6
+ //
7
+ // Pure derivation from the `concept:` tags: rendering twice is byte-identical, and the generated
8
+ // block in the root spec.md is the only thing a --write touches. Frontmatter only — no node body
9
+ // reaches the output. No dependencies (the repo's node-≥23.6 / no-deps convention). Pure functions
10
+ // are exported for node:test; running the file directly drives the CLI.
11
+
12
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
13
+ import { join } from 'node:path'
14
+
15
+ export const BEGIN_MARKER = '<!-- BEGIN generated: by-concept (project-spec/concept-index) -->'
16
+ export const END_MARKER = '<!-- END generated: by-concept -->'
17
+ // Where the block is inserted when the markers are absent: just before this heading.
18
+ const ANCHOR_HEADING = '## Invariants'
19
+
20
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.turbo', '.next', 'coverage'])
21
+
22
+ export type FacetKind = 'rule' | 'workflow' | 'reference' | 'behavior' | 'index'
23
+
24
+ export interface NodeRecord {
25
+ /** Path relative to the spec directory (POSIX). */
26
+ relPath: string
27
+ /** Display form — a README.md node shows its folder, a design doc shows the file. */
28
+ display: string
29
+ concepts: string[]
30
+ facet: FacetKind
31
+ }
32
+
33
+ export interface NodeFrontmatter {
34
+ concepts: string[]
35
+ specType?: string
36
+ model: boolean
37
+ }
38
+
39
+ // ── Frontmatter parse (a minimal YAML subset — only the node classification schema) ──
40
+ // Reads the leading `---` … `---` block for `concept` (scalar | flow list | block list),
41
+ // `spec-type`, and `model`. Returns null when there is no frontmatter block.
42
+ export function parseFrontmatter(text: string): NodeFrontmatter | null {
43
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text)
44
+ if (!m) return null
45
+ const fm: NodeFrontmatter = { concepts: [], model: false }
46
+ const lines = m[1].split('\n').map((l) => l.replace(/\r$/, ''))
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const line = lines[i]
49
+ if (line.trim() === '' || line.trim().startsWith('#')) continue
50
+ if (line.length - line.trimStart().length !== 0) continue // only top-level keys
51
+ const [key, ...rest] = line.trim().split(':')
52
+ const value = rest.join(':').trim()
53
+ if (key === 'spec-type') fm.specType = unquote(value)
54
+ else if (key === 'model') fm.model = unquote(value) === 'true'
55
+ else if (key === 'concept') {
56
+ if (value === '' || value === '|' || value === '>') {
57
+ // block list on following more-indented `- item` lines
58
+ for (let j = i + 1; j < lines.length; j++) {
59
+ const item = lines[j]
60
+ if (item.trim() === '') continue
61
+ if (item.length - item.trimStart().length === 0) break
62
+ const dash = /^\s*-\s+(.*)$/.exec(item)
63
+ if (dash) fm.concepts.push(unquote(dash[1].trim()))
64
+ }
65
+ } else {
66
+ fm.concepts.push(...parseScalarOrFlow(value))
67
+ }
68
+ }
69
+ }
70
+ return fm
71
+ }
72
+
73
+ // `resolution` | `[governance, resolution]` → string[]
74
+ export function parseScalarOrFlow(value: string): string[] {
75
+ const v = value.trim()
76
+ if (v.startsWith('[') && v.endsWith(']')) {
77
+ return v
78
+ .slice(1, -1)
79
+ .split(',')
80
+ .map((s) => unquote(s.trim()))
81
+ .filter((s) => s.length > 0)
82
+ }
83
+ const single = unquote(v)
84
+ return single.length > 0 ? [single] : []
85
+ }
86
+
87
+ function unquote(v: string): string {
88
+ return v.replace(/^["']|["']$/g, '')
89
+ }
90
+
91
+ // ── Facet kind — where this node's facet sits, mechanically ──
92
+ export function facetKind(relPath: string, specType: string | undefined): FacetKind {
93
+ const p = relPath.replace(/\\/g, '/')
94
+ if (p.startsWith('design/')) return 'rule'
95
+ if (p.startsWith('workflows/')) return 'workflow'
96
+ if (specType === 'reference') return 'reference'
97
+ if (specType === 'behavioral') return 'behavior'
98
+ return 'index'
99
+ }
100
+
101
+ function displayPath(relPath: string): string {
102
+ const p = relPath.replace(/\\/g, '/')
103
+ return p.endsWith('/README.md') ? p.slice(0, -'README.md'.length) : p
104
+ }
105
+
106
+ // ── Scan the project-spec — every *.md node carrying a concept tag ──
107
+ export function scanProjectSpec(specDir: string): NodeRecord[] {
108
+ const records: NodeRecord[] = []
109
+ walk(specDir, specDir, records)
110
+ return records
111
+ }
112
+
113
+ function walk(dir: string, specDir: string, out: NodeRecord[]): void {
114
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
115
+ if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue
116
+ const full = join(dir, entry.name)
117
+ if (entry.isDirectory()) {
118
+ walk(full, specDir, out)
119
+ } else if (entry.name.endsWith('.md')) {
120
+ const fm = parseFrontmatter(readFileSync(full, 'utf8'))
121
+ if (!fm || fm.concepts.length === 0) continue
122
+ const relPath = full.slice(specDir.length + 1).replace(/\\/g, '/')
123
+ out.push({
124
+ relPath,
125
+ display: displayPath(relPath),
126
+ concepts: fm.concepts,
127
+ facet: facetKind(relPath, fm.specType),
128
+ })
129
+ }
130
+ }
131
+ }
132
+
133
+ // ── Group + render ──
134
+ export function groupByConcept(records: NodeRecord[]): Map<string, NodeRecord[]> {
135
+ const grouped = new Map<string, NodeRecord[]>()
136
+ for (const rec of records) {
137
+ for (const concept of rec.concepts) {
138
+ const list = grouped.get(concept) ?? []
139
+ list.push(rec)
140
+ grouped.set(concept, list)
141
+ }
142
+ }
143
+ // stable order: concepts alphabetical, nodes by display path
144
+ return new Map(
145
+ [...grouped.entries()]
146
+ .sort(([a], [b]) => a.localeCompare(b))
147
+ .map(([k, v]) => [k, [...v].sort((x, y) => x.display.localeCompare(y.display))]),
148
+ )
149
+ }
150
+
151
+ export function renderTable(grouped: Map<string, NodeRecord[]>): string {
152
+ const rows = [...grouped.entries()].map(([concept, nodes]) => {
153
+ const facets = nodes.map((n) => `\`${n.display}\` (${n.facet})`).join(' · ')
154
+ return `| \`${concept}\` | ${facets} |`
155
+ })
156
+ return ['| Concept | Facets |', '|---|---|', ...rows].join('\n')
157
+ }
158
+
159
+ // The full generated section, markers included.
160
+ export function renderSection(grouped: Map<string, NodeRecord[]>): string {
161
+ return [
162
+ BEGIN_MARKER,
163
+ '',
164
+ '## By concept',
165
+ '',
166
+ '> Generated from `concept:` frontmatter by `project-spec/concept-index` — do not edit by hand.',
167
+ '',
168
+ renderTable(grouped),
169
+ '',
170
+ END_MARKER,
171
+ ].join('\n')
172
+ }
173
+
174
+ // ── spec.md block maintenance ──
175
+ // Replace the BEGIN…END block with `section`; insert at the anchor (or append) when absent.
176
+ export function applySection(specText: string, section: string): string {
177
+ const begin = specText.indexOf(BEGIN_MARKER)
178
+ const end = specText.indexOf(END_MARKER)
179
+ if (begin !== -1 && end !== -1 && end > begin) {
180
+ const before = specText.slice(0, begin)
181
+ const after = specText.slice(end + END_MARKER.length)
182
+ return before + section + after
183
+ }
184
+ const anchor = specText.indexOf(ANCHOR_HEADING)
185
+ if (anchor !== -1) {
186
+ return specText.slice(0, anchor) + section + '\n\n' + specText.slice(anchor)
187
+ }
188
+ const trimmed = specText.replace(/\s*$/, '')
189
+ return `${trimmed}\n\n${section}\n`
190
+ }
191
+
192
+ // Extract the current block (BEGIN…END inclusive), or null when absent.
193
+ export function extractSection(specText: string): string | null {
194
+ const begin = specText.indexOf(BEGIN_MARKER)
195
+ const end = specText.indexOf(END_MARKER)
196
+ if (begin === -1 || end === -1 || end < begin) return null
197
+ return specText.slice(begin, end + END_MARKER.length)
198
+ }
199
+
200
+ // ── CLI ──
201
+ function parseArgs(argv: string[]): { specDir: string; mode: 'print' | 'write' | 'check' } {
202
+ let specDir = '.'
203
+ let mode: 'print' | 'write' | 'check' = 'print'
204
+ for (let i = 0; i < argv.length; i++) {
205
+ const a = argv[i]
206
+ if (a === '--spec-dir') specDir = argv[++i] ?? '.'
207
+ else if (a === '--write') mode = 'write'
208
+ else if (a === '--check') mode = 'check'
209
+ }
210
+ return { specDir, mode }
211
+ }
212
+
213
+ export function main(argv: string[]): number {
214
+ const { specDir, mode } = parseArgs(argv)
215
+ const grouped = groupByConcept(scanProjectSpec(specDir))
216
+ const section = renderSection(grouped)
217
+ const specPath = join(specDir, 'spec.md')
218
+ if (mode === 'print') {
219
+ process.stdout.write(`${section}\n`)
220
+ return 0
221
+ }
222
+ if (!existsSync(specPath)) {
223
+ process.stderr.write(`concept-index: no spec.md at ${specPath}\n`)
224
+ return 2
225
+ }
226
+ const specText = readFileSync(specPath, 'utf8')
227
+ if (mode === 'check') {
228
+ const current = extractSection(specText)
229
+ if (current === section) {
230
+ process.stdout.write('concept-index: no drift\n')
231
+ return 0
232
+ }
233
+ process.stderr.write('concept-index: drift — spec.md by-concept block is stale; run --write\n')
234
+ return 1
235
+ }
236
+ // write
237
+ const next = applySection(specText, section)
238
+ if (next !== specText) writeFileSync(specPath, next)
239
+ process.stdout.write(`concept-index: ${next === specText ? 'unchanged' : 'updated'} ${specPath}\n`)
240
+ return 0
241
+ }
242
+
243
+ if (import.meta.url === `file://${process.argv[1]}`) {
244
+ process.exit(main(process.argv.slice(2)))
245
+ }
@@ -0,0 +1,16 @@
1
+ # discover-plans
2
+
3
+ The concrete engine for SDD **plan discovery**. A non-user-invocable skill carrying a self-contained
4
+ `.mts` script that scans `.agents/plans` for `*.plan.md` mission briefs, treats each present brief as
5
+ an unretired/resumable mission, tallies its todos, reads its top-level `status` dispatch flag
6
+ (`active` when unset) and its `## NEXT` resume lead, and emits a TOON list. The plan sibling of
7
+ [`discover-specs`](../discover-specs/README.md).
8
+
9
+ - **Skill contract:** [`SKILL.md`](./SKILL.md)
10
+ - **Script:** [`scripts/discover-plans.mts`](./scripts/discover-plans.mts)
11
+ - **Tests:** [`scripts/discover-plans.test.mts`](./scripts/discover-plans.test.mts) (`node:test`)
12
+
13
+ ```bash
14
+ node scripts/discover-plans.mts --root . --format toon
15
+ node scripts/discover-plans.mts --root . --status approved # the dispatch queue: approved briefs only
16
+ ```
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: discover-plans
3
+ description: "Partial Skill: invoke by name only — intake/plan-discovery's engine that surfaces resumable mission plan briefs — used by the sdd gateway on entry, not triggered by users directly."
4
+ user-invocable: false
5
+ metadata:
6
+ internal: true
7
+ ---
8
+
9
+ # Discover Plans
10
+
11
+ The concrete engine for SDD **plan discovery**. It
12
+ locates the **resumable missions** in a repo — the mission **plan briefs** under `.agents/plans` —
13
+ and returns each one's CR ref, name, todo tally, and `## NEXT` resume lead, **without reading the
14
+ rest of the body**, so a consumer (the **gateway**, on entry) can offer resume cheaply. It carries a
15
+ self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention). It is the **plan** sibling
16
+ of `discover-specs`: that engine finds **specs** by status shape; this one finds **missions** by
17
+ their briefs.
18
+
19
+ ## Recognition — location-bounded and shape-confirmed
20
+
21
+ A file is a mission brief only when **both** hold (`intake/plan-discovery`):
22
+
23
+ - **Location** — it sits directly under the repo's **`.agents/plans/`** directory (the one plans
24
+ location intake scaffolds into).
25
+ - **Shape** — its filename ends `.plan.md` **and** it carries a frontmatter block (the basic plan
26
+ template: `name` + a `todos` list + a `## NEXT` anchor). A `*.plan.md` with no frontmatter is a
27
+ stray and is skipped; a sibling that does not end `.plan.md` (a combat-log `*.log.jsonl`, a loose
28
+ `*.md`) is never a brief.
29
+
30
+ **Present means resumable.** By default there is **no status filter** — a present brief is already
31
+ unretired (the doctrine loop's `plan-retirement` deletes a brief once its CR is done/merged and
32
+ distilled), so every brief the scan finds is a resumable mission. The todo tally lets the caller tell
33
+ a barely-started mission from one near handoff.
34
+
35
+ **The `status` dispatch flag.** Each brief may declare a top-level `status` (the mission dispatch
36
+ flag — `active` by default, `approved` once a human clears it for headless dispatch). The scan
37
+ **reports** it in every row (an unset value reads as `active`), and, **only when a caller passes
38
+ `--status <value>`**, narrows the set to that status — the opt-in filter the gateway's dispatch loop
39
+ uses to build the approved queue. A value no brief carries yields the empty set. The engine reports
40
+ the flag; it never interprets or writes it.
41
+
42
+ ## Run the scan
43
+
44
+ ```bash
45
+ node "<skill>/scripts/discover-plans.mts" [--root .] [--format toon|json] [--status <value>]
46
+ ```
47
+
48
+ - Default `--root` is the current directory; default `--format` is **TOON** (the token-efficient
49
+ tabular form the gateway scans).
50
+ - Emits one row per brief, sorted by CR ref, with columns
51
+ `cr,name,total,completed,inProgress,status,next` — `cr` is the filename slug (`<cr-ref>`), the
52
+ three counts are the todo tally, `status` is the dispatch flag (`active` when unset), and `next` is
53
+ the lead line of the brief's `## NEXT` anchor (the resume hint).
54
+ - `--status <value>` narrows to the briefs at that dispatch status (e.g. `--status approved` for the
55
+ dispatch queue); absent, no status filter is applied.
56
+ - `--format json` emits the same records as a flat JSON array for non-LLM consumers.
57
+
58
+ Example (TOON):
59
+
60
+ ```
61
+ plans[2]{cr,name,total,completed,inProgress,status,next}:
62
+ github-34,github-34: ...,34,21,0,active,"sub-corpus — suites done, impls pending"
63
+ add-auth,add-auth: ...,6,2,0,approved,"build the token exchange unit"
64
+ ```
65
+
66
+ When `node` is absent, an agent performs the same derivation by hand: list `.agents/plans/*.plan.md`,
67
+ keep each file carrying frontmatter, tally its `todos` by `status`, and read its `## NEXT` lead.
68
+
69
+ ## Boundaries
70
+
71
+ Frontmatter + the `## NEXT` section only — it never reads the rest of a brief's body, owns no
72
+ lifecycle state, and writes nothing. It **never resumes** a mission (that is `resume-mission`) and
73
+ **never retires** a brief (that is `plan-retirement`). Ref resolution over the returned list
74
+ (matching a CR ref to a filename slug) is the **caller's** step, not the script's.