cyber-sdd 0.0.0 → 0.1.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/.plugin/pins.json +3 -0
- package/LICENSE +21 -0
- package/agents/sdd-automaton.md +13 -2
- package/agents/sdd-scanner.md +85 -0
- package/agents/sdd-spec-judge.md +32 -2
- package/agents/sdd-warden.md +9 -0
- package/package.json +30 -23
- package/skills/align-spec/scripts/align-spec.mts +3 -2
- package/skills/blast-estimate/README.md +3 -5
- package/skills/blast-estimate/SKILL.md +2 -2
- package/skills/blast-estimate/scripts/blast-estimate.mts +7 -4
- package/skills/builder-impl-governance/SKILL.md +9 -1
- package/skills/builder-spec-governance/SKILL.md +13 -1
- package/skills/check-partition-quality/scripts/check-partition-quality.mts +3 -1
- package/skills/check-plan-safety/scripts/check-plan-safety.mts +5 -2
- package/skills/check-project-specs/scripts/check-project-specs.mts +67 -5
- package/skills/check-retired-terms/README.md +18 -0
- package/skills/check-retired-terms/SKILL.md +81 -0
- package/skills/check-retired-terms/scripts/check-retired-terms.mts +293 -0
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +3 -2
- package/skills/check-spec-structure/scripts/check-spec-structure.mts +3 -2
- package/skills/collision-ladder/README.md +3 -5
- package/skills/collision-ladder/scripts/collision-ladder.mts +7 -4
- package/skills/combat-log-governance/SKILL.md +43 -4
- package/skills/concept-index/scripts/concept-index.mts +3 -2
- package/skills/discover-plans/scripts/discover-plans.mts +5 -2
- package/skills/discover-specs/scripts/discover-specs.mts +5 -2
- package/skills/doctrine-loop/README.md +6 -0
- package/skills/doctrine-loop/SKILL.md +136 -2
- package/skills/formation-loop/SKILL.md +21 -1
- package/skills/gate-validation-governance/SKILL.md +2 -2
- package/skills/impl-producer-governance/SKILL.md +10 -1
- package/skills/init/scripts/wire-statusline.mts +5 -2
- package/skills/lifecycle-governance/SKILL.md +1 -1
- package/skills/manage-ignore/scripts/manage-ignore.mts +5 -2
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +5 -2
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +5 -2
- package/skills/mission-graph/README.md +3 -5
- package/skills/mission-graph/SKILL.md +72 -5
- package/skills/mission-graph/scripts/mission-graph.mts +505 -16
- package/skills/place-node/scripts/place-node.mts +3 -2
- package/skills/plan-retirement/README.md +5 -2
- package/skills/plan-retirement/SKILL.md +5 -1
- package/skills/plan-retirement/scripts/retire-plans.mts +5 -2
- package/skills/plugin-contract-governance/SKILL.md +7 -1
- package/skills/remediation-governance/SKILL.md +36 -1
- package/skills/resolve-governances/scripts/resolve-governances.mts +5 -2
- package/skills/resolve-tracking/SKILL.md +2 -2
- package/skills/resolve-tracking/scripts/resolve-tracking.mts +5 -4
- package/skills/sdd/SKILL.md +1 -1
- package/skills/spec-format-governance/SKILL.md +5 -0
- package/skills/spec-gate/SKILL.md +18 -2
- package/skills/spec-gate/scripts/check-spec-state.mts +47 -11
- package/skills/spec-gate/scripts/check-suite.mts +53 -17
- package/skills/spec-gate/scripts/classify-edit-class.mts +10 -7
- package/skills/spec-producer-governance/SKILL.md +2 -2
- package/skills/ssa-lowering/README.md +3 -5
- package/skills/start-mission/SKILL.md +8 -4
- package/skills/suite-format-governance/SKILL.md +43 -4
- package/skills/touch-set-correction/README.md +3 -5
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +7 -5
- package/skills/verify-scenarios/SKILL.md +10 -3
- package/skills/verify-scenarios/scripts/verify-scenarios.mts +92 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 unional
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/agents/sdd-automaton.md
CHANGED
|
@@ -75,6 +75,17 @@ your position **from the artifacts** — `spec.md`, the `.feature`, frontmatter,
|
|
|
75
75
|
survived. The plan brief is the portable handoff: read it on entry, update todo statuses and the
|
|
76
76
|
`## NEXT` anchor as you go, so the next segment (yours or an in-session resume) picks up clean.
|
|
77
77
|
|
|
78
|
+
**At finalize, reconcile the brief — do not rely on having kept it current.** When a mission
|
|
79
|
+
**lands**, set every todo to its true terminal state and rewrite `## NEXT` to say what landed, naming
|
|
80
|
+
no remaining resume action, **in the same change as the work**. This is a backstop, so it runs in one
|
|
81
|
+
pass over the whole brief even if nothing updated it mid-flight — the case a stateless segment is
|
|
82
|
+
most likely to hit. Reconcile means *to the landed state*, never *mark everything done*: a todo whose
|
|
83
|
+
work was held out of scope stays un-completed and rides the follow-up record instead. Write the brief
|
|
84
|
+
and nothing else — no `spec.md` field, and **no terminal value in the plan-level `status` dispatch
|
|
85
|
+
flag** (it stays `active | approved`; terminal-ness is derived). A mission that **halts** is
|
|
86
|
+
checkpointed at its true in-progress state, not reconciled as landed. Full rule: `start-mission`'s
|
|
87
|
+
"Plan-brief durability" bullet.
|
|
88
|
+
|
|
78
89
|
## Spawn depth
|
|
79
90
|
|
|
80
91
|
You realize the conductor's spawns (the impl-producer builder, the cold spec-judge, the cold impl-judge)
|
|
@@ -93,5 +104,5 @@ depth-2 behavior described here.
|
|
|
93
104
|
when a capability is available, prefer its **warm** unit over a cold one-shot, else fall back to a
|
|
94
105
|
portable cold subagent. context-clear a warm judge (`npx cyberlegion@<version> unit clear <ref>`) to a fresh context before **each** judgment; a warm
|
|
95
106
|
impl-producer builder **keeps** its context across the mission. Reset or tear down every warm unit at
|
|
96
|
-
handoff. Full model: `start-mission`'s "Dispatch transport" note and
|
|
97
|
-
|
|
107
|
+
handoff. Full model: `start-mission`'s "Dispatch transport" note and the `design/harness-spawning`
|
|
108
|
+
node of the SDD project spec (repo-only).
|
package/agents/sdd-scanner.md
CHANGED
|
@@ -37,6 +37,26 @@ count** maintained in the ledger.
|
|
|
37
37
|
strategy is draftable from it **alone** for every categorical dimension. Raw `.jsonl` transcripts
|
|
38
38
|
are **optional enrichment**, never the contract — depending on a harness-specific transcript
|
|
39
39
|
format would couple doctrine to a harness, and the transcripts may be **absent post-merge**.
|
|
40
|
+
- **Validate before drafting — a plan/log is a hypothesis, not present truth.** Every candidate
|
|
41
|
+
improvement a persisted plan or combat log surfaces is a **hypothesis about the current codebase**,
|
|
42
|
+
not a fact (*a source-read is a hypothesis, refuted by a repro against current code*). **Before you
|
|
43
|
+
draft**, read the CURRENT code and decide: does the flagged gap still exist, or has it been
|
|
44
|
+
**built / fixed / superseded** since? A candidate current code already resolves is **cut** — draft
|
|
45
|
+
no build-or-fix strategy, emit no issue, and record the cut durably as a `disposition: resolved`
|
|
46
|
+
tombstone carrying the resolving current-code evidence (below). A still-open candidate is drafted
|
|
47
|
+
(`disposition: open`) and emitted as an issue. This gate stops you reinforcing a **stale cache** —
|
|
48
|
+
drafting "build X" for an X a later mission already built. A pure retro **lesson** (not a gap claim)
|
|
49
|
+
carries no gap and needs no check.
|
|
50
|
+
- **Cold-instrument evidence — non-author measurement + ablation before a rule-level recommendation.**
|
|
51
|
+
Draft a recommendation to **adopt a rule, drop a rule, or set a threshold** only when its grounding
|
|
52
|
+
measurement was **produced by, or independently reviewed by, a party other than the proposer, or
|
|
53
|
+
ablated against a freshly and adversarially constructed case** — a measurement on the proposer's
|
|
54
|
+
**own generator/harness alone is withheld** (draft nothing; record that it needs non-author or
|
|
55
|
+
fresh-adversarial evidence). Draft a candidate **reviving a dead measurement dimension** to land only
|
|
56
|
+
when an ablation against a control shows it **loseable**, stating the revived rule **abstractly with
|
|
57
|
+
no worked example** (a lifted probe apparatus is absorption); a Δ=0 revival is **cut as dead weight**.
|
|
58
|
+
A measurement grounding no rule-level decision is unconstrained. (Canonical statement:
|
|
59
|
+
`sdd:doctrine-loop`.)
|
|
40
60
|
- **Detect and draft cheaply and continuously; never block.** You draft strategy without blocking
|
|
41
61
|
any mission in progress — drafting is off the mission's critical path. You **accumulate** strategy
|
|
42
62
|
and surface it **episodically** (a retro, on demand, or when pending strategy piles up at the
|
|
@@ -57,6 +77,18 @@ count** maintained in the ledger.
|
|
|
57
77
|
distinct from the cross-referenced cr-refs in `evidence`. It is the hook `sdd:plan-retirement` keys
|
|
58
78
|
on to confirm a plan was distilled before deleting its combat log. Milestone / drift / token-waste
|
|
59
79
|
strategy has no single subject mission and **omits** `distills`.
|
|
80
|
+
- **Idempotent Ship/Kill — detect an already-distilled mission by parsing the ledger, never grepping.**
|
|
81
|
+
Because you can meet the same terminal transition more than once, the Ship/Kill triggers are
|
|
82
|
+
idempotent: **before drafting**, detect whether the mission was **already distilled** (a prior
|
|
83
|
+
`strategy` entry whose `distills` equals its `<cr-ref>`) and if so **draft nothing**. Decide this by a
|
|
84
|
+
**structured JSONL parse, never a substring/regex text match** — **reuse** the exported
|
|
85
|
+
`distilledCrRefs` engine from `../skills/plan-retirement/scripts/retire-plans.mts` over the project
|
|
86
|
+
`ledger/` directory and membership-test the `<cr-ref>`. It parses each line with `JSON.parse`, so it
|
|
87
|
+
is correct against a **pretty-printed** entry (a space after the colon), a wrapped or reordered
|
|
88
|
+
object — a naive no-space grep (`"distills":"<cr-ref>"`) silently under-counts and re-drafts an
|
|
89
|
+
already-distilled mission (the *grep is blind to wrapped terms* defect class). Reuse inherits its
|
|
90
|
+
semantics: keys on the structured `distills` field only (an `evidence`-only cross-ref never counts),
|
|
91
|
+
tolerates malformed/blank lines, and counts an unratified distilling entry the same as a ratified one.
|
|
60
92
|
- **Detection is yours; keep-or-cut is the Council's.** You detect and draft; the human Council
|
|
61
93
|
holds keep-or-cut. Ratified strategy re-enters as a CR that re-tunes the **doctrine** and grows
|
|
62
94
|
the **corpus** (skills, governances, conventions); unratified strategy does neither.
|
|
@@ -81,6 +113,29 @@ re-scan of many missions' raw logs (those are deleted with each plan at retro).
|
|
|
81
113
|
by `cause`** (the matchable field owned by `combat-log-governance`). A `cause` recurring across the
|
|
82
114
|
corpus is the pattern — draft a strategy to codify it, carrying the recurrence count as its evidence.
|
|
83
115
|
|
|
116
|
+
## Validate-before-draft, the cut disposition, and issue emission
|
|
117
|
+
|
|
118
|
+
Run the validation gate on each candidate a plan or log surfaces, then record and emit:
|
|
119
|
+
|
|
120
|
+
- **The cut disposition (`open | resolved`).** A drafted still-open improvement is `disposition: open`
|
|
121
|
+
(the default; a legacy line without the field grandfathers as open) and **counts toward pending
|
|
122
|
+
strategy**. A cut is `disposition: resolved` — a **tombstone** carrying the resolving current-code
|
|
123
|
+
evidence in `evidence`, emitting **no** issue and **not counted** toward pending strategy (the
|
|
124
|
+
gateway excludes it). Set the disposition **once at write**, never flip it (append-only). The field's
|
|
125
|
+
shape is owned by `combat-log-governance`; the gateway's pending count is `kind: strategy`,
|
|
126
|
+
`ratified: false`, `disposition: open`-or-absent.
|
|
127
|
+
- **Emit each validated-open improvement as a tracked issue** (`gh issue create`) — one titled, bodied
|
|
128
|
+
issue per real improvement, cross-linking its evidence. The issue is the **actionable output**; the
|
|
129
|
+
`strategy` line stays the **provenance**. **Dedupe first** against the forge's existing issues (open
|
|
130
|
+
and closed, ≥2 keyword combinations); on a mixed set file only the unmatched.
|
|
131
|
+
- **Emit is not dispatch.** Emitting an issue leaves the strategy **unratified** and spawns **no**
|
|
132
|
+
mission — it opens no CR and admits nothing to the mission graph. Keep-or-cut stays the Council's; a
|
|
133
|
+
filed issue re-enters SDD only when a **later** mission is started from it.
|
|
134
|
+
- **Outward-publish floor.** Compose the issue body to the same floor the handoff follow-up issues
|
|
135
|
+
meet (owned by the handoff unit, not restated here): **self-contained**, **no production-internal
|
|
136
|
+
artifact reference**, plus the committed-record bans; carry an **agent-filed marker** and name the
|
|
137
|
+
evidence it was distilled from.
|
|
138
|
+
|
|
84
139
|
## Drift / staleness
|
|
85
140
|
|
|
86
141
|
Detect a convention in the doctrine that is **now false**, or a contradiction between governances.
|
|
@@ -114,6 +169,36 @@ the Council re-enters — that is how detection meets keep-or-cut:
|
|
|
114
169
|
|
|
115
170
|
You neither ratify nor prune the corpus yourself — both are the Council's positional act.
|
|
116
171
|
|
|
172
|
+
## Stale plan frontmatter — deriving the retirement clearance set, never autofixing status
|
|
173
|
+
|
|
174
|
+
During your pass, for each brief under `.agents/plans/`, cross-check two independent signals — this
|
|
175
|
+
is separate from strategy-drafting above; you draft nothing and write nothing to the ledger for it:
|
|
176
|
+
|
|
177
|
+
- **`todos-all-done`** — every `todos[].status` in the brief's frontmatter reads `completed`.
|
|
178
|
+
- **`source-closed`** — the brief's declared `source` queried natively, the same way
|
|
179
|
+
`plan-retirement`'s own clearance check queries it (`github-NN` → GH issue, `asana-<gid>` →
|
|
180
|
+
Asana, `local-<slug>` → the local store).
|
|
181
|
+
|
|
182
|
+
- **Both agree terminal** → include the brief's cr-ref in the **retirement clearance set** you pass
|
|
183
|
+
as `plan-retirement`'s existing `--retire` clearance-set input. This is not a new deletion
|
|
184
|
+
mechanism — `plan-retirement` still runs its own gated, idempotent sweep (presence +
|
|
185
|
+
distilled-or-no-log) before anything leaves the tree; you are only supplying its `--retire` set,
|
|
186
|
+
cross-checked rather than source-only.
|
|
187
|
+
- **Both agree non-terminal** → leave the brief alone; no clearance, no finding.
|
|
188
|
+
- **The two signals disagree** (source closed but the brief's own todos are not all done, or the
|
|
189
|
+
reverse) → do **not** autofix anything, and do **not** include the cr-ref in the clearance set.
|
|
190
|
+
Name the brief's cr-ref and the disagreement in your **pass summary** — the only channel you
|
|
191
|
+
have, since you return only your final message. A disagreement is **never** a `kind: strategy`
|
|
192
|
+
entry (it names no doctrine improvement) and **never** a `kind: report` ledger line (reserved to
|
|
193
|
+
the conductor and the gate; you still never write `report` / `correction` / `gate`) — it is a
|
|
194
|
+
distinct, ephemeral finding, re-derived every pass from the two cheap signals above, never
|
|
195
|
+
persisted, and never conflated with the validated-open-improvement finding that becomes a
|
|
196
|
+
tracked issue.
|
|
197
|
+
- There is **no legal terminal value** for a plan brief's `status` field to autofix into — the
|
|
198
|
+
contract's own answer to "this mission is over" is retirement (a tracked deletion), not a status
|
|
199
|
+
flag (`design/provenance-model.md` reserves the plan-level `status` to the two-value dispatch
|
|
200
|
+
flag `active | approved`). You never write a plan brief's `status`.
|
|
201
|
+
|
|
117
202
|
## Boundaries
|
|
118
203
|
|
|
119
204
|
You own the **process** only. Route out-of-loop requests: a build-or-deprecate request → the
|
package/agents/sdd-spec-judge.md
CHANGED
|
@@ -69,6 +69,32 @@ before reading spec.md for content:
|
|
|
69
69
|
3. **A superset raises no finding.** A declared set covering every expected governance — with or
|
|
70
70
|
without extras — passes; report `PREFLIGHT: { result: pass }` and proceed to the lenses below.
|
|
71
71
|
|
|
72
|
+
## Spec-format conformance read — a non-blocking warning
|
|
73
|
+
|
|
74
|
+
`sdd:spec-format-governance` (a fixed-universal you already loaded) sets the required sections of a
|
|
75
|
+
**behavioral** `spec.md`: `## What`, `## Use Cases`, `## Control Flow` (the control-flow graph,
|
|
76
|
+
**CFG**), and `## Scenario map`. Read that bar backward here: for each touched **behavioral**
|
|
77
|
+
`spec.md`, check those sections are present and **emit a conformance warning naming any that are
|
|
78
|
+
missing** — **especially `## Use Cases`, `## Control Flow` (CFG), and `## Scenario map`**, the three
|
|
79
|
+
a backfill most often skips.
|
|
80
|
+
|
|
81
|
+
- **Behavioral only — key off `spec-type`, never a blind heading scan.** A `reference` node carries
|
|
82
|
+
`## Subject` in place of the four sections and a `descriptive` index carries none, so **neither
|
|
83
|
+
raises a conformance warning**: check a node's `spec-type` first and read the required sections
|
|
84
|
+
only for a behavioral one. A reference/descriptive node's result is always `pass`.
|
|
85
|
+
- **A warning, not a lens failure.** The conformance read is **distinct from the three lenses and
|
|
86
|
+
from the deterministic checks**. A missing section is a `warn`, surfaced for the gate to report —
|
|
87
|
+
it does **not** fail a lens, does **not** set `ALIGNED: false`, and does **not** short-circuit the
|
|
88
|
+
lenses the way a failed `PREFLIGHT` does. Grade the lenses regardless.
|
|
89
|
+
- **The normal-flow overlap.** In the gate's normal flow a behavioral node missing `## Use Cases` is
|
|
90
|
+
already caught by the deterministic `check-spec-state` fail-closed **before you are spawned**, so
|
|
91
|
+
the warning's **load-bearing** contribution is the CFG and the scenario map — but name `## Use
|
|
92
|
+
Cases` too whenever you do read a behavioral `spec.md` that lacks it.
|
|
93
|
+
|
|
94
|
+
Carry the result on the `CONFORMANCE` output field: `{ result: pass | warn, missing: [ <each
|
|
95
|
+
required behavioral spec-format section absent from a touched behavioral spec.md> ] }`. All sections
|
|
96
|
+
present (or a reference/descriptive node) ⇒ `{ result: pass, missing: [] }`.
|
|
97
|
+
|
|
72
98
|
## Split the work
|
|
73
99
|
|
|
74
100
|
- **Optional deterministic step** — two NodeJS static-analysis CLIs for the mechanical checks
|
|
@@ -207,6 +233,7 @@ narrowing that fires **Clearance**, so never demand it of one.
|
|
|
207
233
|
```
|
|
208
234
|
STATUS: complete | needs-input | blocked
|
|
209
235
|
PREFLIGHT: { result: pass | fail, finding-kind: governance-preflight-missing | null, missing: [ ... ] }
|
|
236
|
+
CONFORMANCE: { result: pass | warn, missing: [ <required behavioral spec-format sections absent — e.g. Use Cases, Control Flow, Scenario map> ] }
|
|
210
237
|
LENS: { oracle: pass | fail, builder: pass | fail, architect: pass | fail }
|
|
211
238
|
ALIGNED: true | false # false ⇒ which artifacts are out of sync
|
|
212
239
|
SCENARIOS_PASSING: [ titles ]
|
|
@@ -218,7 +245,10 @@ OBSERVATIONS: [ { owner: architect | strategist, note, evidence } ]
|
|
|
218
245
|
```
|
|
219
246
|
|
|
220
247
|
`PREFLIGHT.result: fail` short-circuits everything below it — `LENS` is omitted, `ALIGNED` is `false`,
|
|
221
|
-
and `BLOCKER` names the missing governances (see "Governance pre-flight check" above).
|
|
222
|
-
`
|
|
248
|
+
and `BLOCKER` names the missing governances (see "Governance pre-flight check" above).
|
|
249
|
+
`CONFORMANCE.result: warn` **short-circuits nothing** — the lenses still run, and it never on its own
|
|
250
|
+
sets `ALIGNED: false` or blocks the advance (see "Spec-format conformance read" above); the gate
|
|
251
|
+
surfaces it as a warning. Otherwise `ALIGNED` is `true` only when all three lenses pass and no open
|
|
252
|
+
marker remains. The conductor
|
|
223
253
|
synthesizes the gate verdict and the leash from this rollup — never advance with any lens failing,
|
|
224
254
|
any open marker, a failed preflight, or `ALIGNED: false`.
|
package/agents/sdd-warden.md
CHANGED
|
@@ -40,6 +40,15 @@ marker shape you leave — their fields and schema are owned there; never restat
|
|
|
40
40
|
a scenario-overlap candidate you judge (`@rubric`) whether it is **real** behavioral overlap and
|
|
41
41
|
**assign a single owning node** (one behavior = one scenario in one node). A finding **names** the
|
|
42
42
|
nodes or artifacts it concerns.
|
|
43
|
+
- **Check a split's organizing axis, not just its granularity.** An oversized node is a granularity
|
|
44
|
+
signal, but a split carves it along a proposed **organizing axis** — and the wrong axis produces a
|
|
45
|
+
split CR that gets superseded rather than landed. Before you propose a split (self-clear it or
|
|
46
|
+
escalate its CR), **check the proposed axis against a real capability/command boundary**: each side
|
|
47
|
+
must map to a **distinct capability, command, or lifecycle phase**, not merely an internal
|
|
48
|
+
implementation grouping sharing one boundary. An axis that passes → the split is **proposed on that
|
|
49
|
+
axis**; an axis that only regroups internal implementation → **fails**, so you do **not** carve
|
|
50
|
+
sub-nodes on it and instead raise the oversize as a **wrong-axis reorganization**. An oversize can
|
|
51
|
+
be a symptom of the wrong axis, not just wrong granularity.
|
|
43
52
|
- **Judge against the declared strategy.** Read each project spec's root `spec.md` **placement map**
|
|
44
53
|
for the layout strategy it chose, and judge structural fit against *that*, never a default or the
|
|
45
54
|
shape the tree happens to have. Inferring the strategy makes the audit circular. A map naming no
|
package/package.json
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
"name": "cyber-sdd",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Spec-Driven Development. Scaffold, validate, and maintain behavioral specs (spec.md + .feature files) for software features.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/cyberuni/cyberplace.git",
|
|
8
|
+
"directory": "plugins/sdd"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"sdd-check-specs": "./skills/check-project-specs/scripts/check-project-specs.mts"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"skills",
|
|
17
|
+
"agents",
|
|
18
|
+
".plugin",
|
|
19
|
+
".codex-plugin",
|
|
20
|
+
".claude-plugin",
|
|
21
|
+
"!skills/**/*.test.mts"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"gherkin-cli": "0.0.2"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"check:spec": "sdd-check-specs",
|
|
28
|
+
"test": "node --test \"skills/*/scripts/*.test.mts\"",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
// No dependencies (the repo's node-≥23.6 / no-deps convention). Pure functions are exported for
|
|
25
25
|
// node:test; running the file directly drives the CLI.
|
|
26
26
|
|
|
27
|
-
import { existsSync, readdirSync } from 'node:fs'
|
|
27
|
+
import { existsSync, readdirSync, realpathSync } from 'node:fs'
|
|
28
28
|
import { isAbsolute, join, relative } from 'node:path'
|
|
29
|
+
import { pathToFileURL } from 'node:url'
|
|
29
30
|
import { type NodeRecord, scanProjectSpec } from '../../check-spec-structure/scripts/check-spec-structure.mts'
|
|
30
31
|
import { classifyFile } from '../../spec-gate/scripts/classify-edit-class.mts'
|
|
31
32
|
|
|
@@ -182,6 +183,6 @@ export function main(argv: string[]): number {
|
|
|
182
183
|
return 0
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
if (import.meta.url ===
|
|
186
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
186
187
|
process.exit(main(process.argv.slice(2)))
|
|
187
188
|
}
|
|
@@ -24,11 +24,9 @@ node scripts/blast-estimate.mts --root <corpus> --touch-set sdd/mission-graph,sd
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Read-only, deterministic, reports-never-writes — the mission-graph's single writer records the
|
|
27
|
-
computed level. See [`SKILL.md`](./SKILL.md) for the full contract
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
[`blast-estimate.feature`](../../../../.agents/specs/sdd/blast-estimate/blast-estimate.feature) for
|
|
31
|
-
the frozen 21-scenario contract.
|
|
27
|
+
computed level. See [`SKILL.md`](./SKILL.md) for the full contract; the `blast-estimate` node of
|
|
28
|
+
the SDD project spec (in the cyberplace repository, not shipped in this package) carries the
|
|
29
|
+
authoritative behavior description and the frozen 21-scenario contract.
|
|
32
30
|
|
|
33
31
|
Fan-in counts the reference forms the corpus really uses — the bare id, a `sdd:spec-gate` skill ref,
|
|
34
32
|
a path under any **declared root** at any depth, and a same-project relative link — not the bare id
|
|
@@ -12,8 +12,8 @@ The concrete engine for **blast-estimate** — a read-only derivation that works
|
|
|
12
12
|
project a Mission could disturb, instead of trusting the hand-typed guess. Given a Mission's
|
|
13
13
|
**touch-set** (the work areas it names) and the project corpus, it computes a **blast** level
|
|
14
14
|
(`low` / `medium` / `high`) from three measured inputs and lines that level up against the Mission's
|
|
15
|
-
hand-typed **declared** blast (`agrees` / `under-called` / `over-called`).
|
|
16
|
-
|
|
15
|
+
hand-typed **declared** blast (`agrees` / `under-called` / `over-called`). The `blast-estimate` node
|
|
16
|
+
of the SDD project spec (repo-only) carries the full contract and vocabulary.
|
|
17
17
|
|
|
18
18
|
## The three inputs
|
|
19
19
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// blast-estimate — the concrete engine for blast-estimate's derivation: compute a Mission's BLAST
|
|
3
3
|
// (low/medium/high) from its touch-set + the project corpus instead of trusting the hand-typed guess,
|
|
4
|
-
// then line the computed level up against the declared one (agrees / under-called / over-called).
|
|
5
|
-
//
|
|
4
|
+
// then line the computed level up against the declared one (agrees / under-called / over-called). The
|
|
5
|
+
// blast-estimate node of the SDD project spec (repo-only) carries the full contract this mirrors.
|
|
6
6
|
//
|
|
7
7
|
// Three inputs, each measured — never inferred — from the corpus:
|
|
8
8
|
// - count — how many of the touch-set's areas resolve to a known work area
|
|
@@ -32,8 +32,9 @@
|
|
|
32
32
|
// mission-graph's single writer records it. Pure functions are exported for node:test; running the
|
|
33
33
|
// file directly drives the CLI.
|
|
34
34
|
|
|
35
|
-
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
35
|
+
import { readdirSync, readFileSync, realpathSync, statSync } from 'node:fs'
|
|
36
36
|
import { join, relative } from 'node:path'
|
|
37
|
+
import { pathToFileURL } from 'node:url'
|
|
37
38
|
import {
|
|
38
39
|
discoverLayouts,
|
|
39
40
|
fileToNode,
|
|
@@ -580,4 +581,6 @@ export function main(argv: string[]): number {
|
|
|
580
581
|
return result.error ? 1 : 0
|
|
581
582
|
}
|
|
582
583
|
|
|
583
|
-
if (import.meta.
|
|
584
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
585
|
+
process.exit(main(process.argv.slice(2)))
|
|
586
|
+
}
|
|
@@ -25,6 +25,13 @@ unbound.
|
|
|
25
25
|
paths that matter, **boundary** (the external mocked) as the honest substitute where e2e is
|
|
26
26
|
infeasible or unsafe. **Record the level and why.** The suite's overall pyramid shape is the
|
|
27
27
|
architect's call (`sdd:architect-impl-governance`).
|
|
28
|
+
- **Instrument subject → mutation-sweep-first.** When the **subject is itself a measurement or
|
|
29
|
+
verification instrument** — a fixture, mutation set, ablation generator, falsifier, judge, or check —
|
|
30
|
+
a **mutation sweep is the default verification method** and reading is **supplementary**. This
|
|
31
|
+
**overrides the verify-as-high default above for an instrument subject only**; a non-instrument
|
|
32
|
+
subject keeps that default. Reading an instrument's own blind spot back to itself is a weak oracle —
|
|
33
|
+
a "cannot-fail" defect survives every read and dies to the first mutation the instrument fails to
|
|
34
|
+
catch (the cold-instrument doctrine, stated in full on the impl-producer node).
|
|
28
35
|
- **A graded subject still yields a boolean.** Reach the per-scenario boolean through a rubric +
|
|
29
36
|
threshold over N runs; the rubric stays out of the `.feature`.
|
|
30
37
|
- **No green-by-tampering.** Passing means the behavior holds, not that a check was edited to pass;
|
|
@@ -40,7 +47,8 @@ unbound.
|
|
|
40
47
|
1. **The bar is not self-set** — checks derive from the frozen suite, one per scenario; the judge
|
|
41
48
|
re-derives the oracle independently.
|
|
42
49
|
2. **Verify as high as it doesn't hurt** — cheap base, thin e2e cap, boundary as substitute where e2e
|
|
43
|
-
is infeasible/unsafe; record level and why.
|
|
50
|
+
is infeasible/unsafe; record level and why. **An instrument subject inverts this: mutation-sweep-first,
|
|
51
|
+
reading supplementary.**
|
|
44
52
|
3. **No green-by-tampering** — passing is the behavior holding, never an edited check or a modified
|
|
45
53
|
suite.
|
|
46
54
|
4. **Deterministic combinatorics go to units** (the pyramid base); missing that coverage withholds the
|
|
@@ -19,12 +19,22 @@ plugin may bind its own, and this loads when the registry leaves `builder`/`spec
|
|
|
19
19
|
|
|
20
20
|
- **Every branch of the capability is covered.** Each edge of its control-flow graph (CFG) has its
|
|
21
21
|
scenario, and every guard/negative edge is paired with a positive companion. The **scenario map
|
|
22
|
-
is 1:1** — no orphan scenario, no uncovered edge (`sdd:suite-format-governance`).
|
|
22
|
+
is 1:1** — no orphan scenario, no uncovered edge (`sdd:suite-format-governance`). For a **fold**
|
|
23
|
+
node whose rule combines two or more interacting sub-conditions, the CFG is drawn from that rule
|
|
24
|
+
stated in **closed form** (single-condition folds may be by example — demanding closed form of one
|
|
25
|
+
is over-firing), and its coverage is backed by a **mutation sweep** and a **safety dual**
|
|
26
|
+
(`sdd:suite-format-governance`).
|
|
23
27
|
- **Every scenario is testable.** Each asserts an observable outcome a check can confirm — a boolean,
|
|
24
28
|
no "sometimes". A behavior the capability cannot expose cannot be specced.
|
|
25
29
|
- **A graded subject is still a boolean.** For a non-deterministic capability the contract reaches a
|
|
26
30
|
per-scenario boolean through a rubric + threshold over N runs; the rubric form stays out of the
|
|
27
31
|
boolean `.feature`, carried as a judge-only `@rubric` scenario.
|
|
32
|
+
- **A dimension or cut is grounded on non-author evidence.** When a `@rubric` dimension or its cut is
|
|
33
|
+
justified by a **measurement** (an ablation Δ, a discrimination count), that measurement is admissible
|
|
34
|
+
only if it is **not solely the author's own** — independently produced/reviewed by a non-author, or a
|
|
35
|
+
fresh-adversarial ablation. An author's own instrument silently assumes the property under test. The
|
|
36
|
+
standard is stated canonically at `sdd:doctrine-loop`; this bar requires it be met, not re-listed
|
|
37
|
+
(the cold-instrument doctrine).
|
|
28
38
|
|
|
29
39
|
## Key points (read-check)
|
|
30
40
|
|
|
@@ -34,3 +44,5 @@ plugin may bind its own, and this loads when the registry leaves `builder`/`spec
|
|
|
34
44
|
expose cannot be specced.
|
|
35
45
|
3. **A graded subject still reaches a per-scenario boolean** via rubric + threshold; the rubric stays
|
|
36
46
|
out of the `.feature`.
|
|
47
|
+
4. **A dimension or cut is grounded on non-author evidence** — a measurement justifying it must be not
|
|
48
|
+
solely the author's own (canonical standard: `sdd:doctrine-loop`); the cold-instrument doctrine.
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// node:test; running the file directly drives the CLI.
|
|
21
21
|
|
|
22
22
|
import { execFileSync } from 'node:child_process'
|
|
23
|
+
import { realpathSync } from 'node:fs'
|
|
24
|
+
import { pathToFileURL } from 'node:url'
|
|
23
25
|
|
|
24
26
|
/** Below this many usable multi-file commits, a rate would be noise — report, never score. */
|
|
25
27
|
export const DEFAULT_FLOOR = 20
|
|
@@ -331,6 +333,6 @@ export function main(argv: string[], context: Context = { readHistory }): number
|
|
|
331
333
|
return 0
|
|
332
334
|
}
|
|
333
335
|
|
|
334
|
-
if (import.meta.url ===
|
|
336
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
335
337
|
process.exit(main(process.argv.slice(2)))
|
|
336
338
|
}
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
// Default output is TOON (the token-efficient tabular form); --format json for a flat array.
|
|
19
19
|
// --check is the CI guard: exit non-zero iff any leak is found.
|
|
20
20
|
|
|
21
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs'
|
|
22
22
|
import { join } from 'node:path'
|
|
23
|
+
import { pathToFileURL } from 'node:url'
|
|
23
24
|
|
|
24
25
|
export interface Leak {
|
|
25
26
|
/** Repo-relative path of the file the leak sits in. */
|
|
@@ -142,4 +143,6 @@ export function main(argv: string[]): number {
|
|
|
142
143
|
return check && leaks.length > 0 ? 1 : 0
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
if (import.meta.
|
|
146
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
147
|
+
process.exit(main(process.argv.slice(2)))
|
|
148
|
+
}
|
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
// `plugins/cyberfleet` is governed by `.agents/specs/cyberfleet-plugin`.
|
|
11
11
|
|
|
12
12
|
import { execFileSync } from 'node:child_process'
|
|
13
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
13
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
14
14
|
import { dirname, join, relative, resolve } from 'node:path'
|
|
15
|
-
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import {
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
16
|
+
import {
|
|
17
|
+
collectSpecs,
|
|
18
|
+
discoverSpecFiles,
|
|
19
|
+
parseFrontmatter,
|
|
20
|
+
type SpecRecord,
|
|
21
|
+
} from '../../discover-specs/scripts/discover-specs.mts'
|
|
17
22
|
|
|
18
23
|
const SKILLS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
|
19
24
|
|
|
@@ -129,6 +134,36 @@ export function findCoverageGaps(
|
|
|
129
134
|
return gaps
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
/**
|
|
138
|
+
* A spec.md sitting at a recognized location that discovery DROPPED (its status is not
|
|
139
|
+
* in the lifecycle enum) and that belongs to `projectRel` — by the `project-path` it
|
|
140
|
+
* declares, or by sitting inside the project (`<project>/.agents/spec/spec.md`, which
|
|
141
|
+
* survives frontmatter corruption because it is location-derived).
|
|
142
|
+
*
|
|
143
|
+
* Without this, a per-project run resolves such a spec to `none` and prints "no spec
|
|
144
|
+
* governs <project> — skipped" with exit 0: a status typo silently exempts the whole
|
|
145
|
+
* project from every engine. A spec that exists but cannot be classified is escalated,
|
|
146
|
+
* not exempted — the same call the corpus-level `--check-coverage` guard makes.
|
|
147
|
+
*/
|
|
148
|
+
export function findDroppedSpecFor(
|
|
149
|
+
specFiles: string[],
|
|
150
|
+
specs: SpecRecord[],
|
|
151
|
+
projectRel: string,
|
|
152
|
+
readText: (rel: string) => string | null,
|
|
153
|
+
): { file: string; status: string }[] {
|
|
154
|
+
const recognized = new Set(specs.map((s) => (s.path === '' ? 'spec.md' : `${s.path}/spec.md`)))
|
|
155
|
+
const out: { file: string; status: string }[] = []
|
|
156
|
+
for (const f of specFiles) {
|
|
157
|
+
if (recognized.has(f)) continue
|
|
158
|
+
const text = readText(f)
|
|
159
|
+
const fm = text === null ? null : parseFrontmatter(text)
|
|
160
|
+
const dir = f.replace(/(^|\/)spec\.md$/, '')
|
|
161
|
+
const nested = /^(.+)\/\.agents\/spec$/.exec(dir)?.[1] ?? ''
|
|
162
|
+
if (fm?.projectPath === projectRel || nested === projectRel) out.push({ file: f, status: fm?.status ?? '' })
|
|
163
|
+
}
|
|
164
|
+
return out
|
|
165
|
+
}
|
|
166
|
+
|
|
132
167
|
const REASON_TEXT: Record<CoverageGap['reason'], string> = {
|
|
133
168
|
unrecognized:
|
|
134
169
|
'sits at a spec location but its status is not in the lifecycle enum, so discovery drops it and nothing checks it',
|
|
@@ -137,6 +172,15 @@ const REASON_TEXT: Record<CoverageGap['reason'], string> = {
|
|
|
137
172
|
'no-check-script': 'names a project that defines no `check:spec` script',
|
|
138
173
|
}
|
|
139
174
|
|
|
175
|
+
/** Read a file, or null when it cannot be read. */
|
|
176
|
+
function readTextOrNull(path: string): string | null {
|
|
177
|
+
try {
|
|
178
|
+
return readFileSync(path, 'utf8')
|
|
179
|
+
} catch {
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
140
184
|
function checkCoverage(root: string): number {
|
|
141
185
|
const gaps = findCoverageGaps(root, discoverSpecFiles(root), collectSpecs(root), (p) => {
|
|
142
186
|
try {
|
|
@@ -179,9 +223,25 @@ function checkProject(argv: string[]): number {
|
|
|
179
223
|
}
|
|
180
224
|
|
|
181
225
|
const projectRel = relative(repoRoot, projectDir) || '.'
|
|
182
|
-
const
|
|
226
|
+
const specs = collectSpecs(repoRoot)
|
|
227
|
+
const res = resolveSpecFor(specs, projectRel)
|
|
183
228
|
|
|
184
229
|
if (res.kind === 'none') {
|
|
230
|
+
// "No spec" is only legal when there is genuinely no spec file. A spec.md that
|
|
231
|
+
// exists but was dropped by the status filter is unclassifiable, not absent.
|
|
232
|
+
const dropped = findDroppedSpecFor(discoverSpecFiles(repoRoot), specs, projectRel, (rel) =>
|
|
233
|
+
readTextOrNull(join(repoRoot, rel)),
|
|
234
|
+
)
|
|
235
|
+
for (const d of dropped) {
|
|
236
|
+
process.stderr.write(
|
|
237
|
+
`check-project-specs: \`${d.file}\` sits at a spec location and governs \`${projectRel}\` but ` +
|
|
238
|
+
(d.status === ''
|
|
239
|
+
? 'declares no lifecycle status'
|
|
240
|
+
: `its status \`${d.status}\` is not in the lifecycle enum`) +
|
|
241
|
+
' (draft | approved | implemented | deprecated) — discovery drops it, so it is checked by nothing\n',
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
if (dropped.length) return 1
|
|
185
245
|
process.stdout.write(`check-project-specs: no spec governs \`${projectRel}\` — skipped\n`)
|
|
186
246
|
return 0
|
|
187
247
|
}
|
|
@@ -214,4 +274,6 @@ function checkProject(argv: string[]): number {
|
|
|
214
274
|
return failed === 0 ? 0 : 1
|
|
215
275
|
}
|
|
216
276
|
|
|
217
|
-
if (import.meta.
|
|
277
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
278
|
+
process.exit(main(process.argv.slice(2)))
|
|
279
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# check-retired-terms
|
|
2
|
+
|
|
3
|
+
Internal SDD skill — the concrete guard engine for the **retired-terms registry and its
|
|
4
|
+
corpus-wide sweep**. Scans every git-tracked file for a literal, case-sensitive occurrence of a
|
|
5
|
+
term registered in `.agents/sdd/retired-terms.toml` as retired by a design decision.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node scripts/check-retired-terms.mts --root . # the verify-time sweep
|
|
9
|
+
node scripts/check-retired-terms.mts --root . --list # what is registered
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Reports every survivor as `file:line:term — replace with: <replacement>`, then a count, and exits
|
|
13
|
+
non-zero. A malformed registry exits non-zero and names the parse error rather than reporting
|
|
14
|
+
clean. Built-in exclusions (the registry, the engine's own source/test, this node's own
|
|
15
|
+
README/`.feature`, every `ledger/` directory, `.agents/plans/`) are always applied and never
|
|
16
|
+
configurable; per-entry `scope` and `allow` narrow further. Read-only; writes nothing. See
|
|
17
|
+
[`SKILL.md`](./SKILL.md) for the full contract; the `corpus/retired-terms` node of the SDD project
|
|
18
|
+
spec (repo-only) carries the frozen spec. Not user-invocable.
|