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,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.
|