cyber-sdd 0.0.0 → 0.2.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 (71) hide show
  1. package/.plugin/pins.json +3 -0
  2. package/LICENSE +21 -0
  3. package/agents/sdd-automaton.md +13 -2
  4. package/agents/sdd-scanner.md +85 -0
  5. package/agents/sdd-spec-judge.md +32 -2
  6. package/agents/sdd-warden.md +9 -0
  7. package/package.json +30 -23
  8. package/skills/align-spec/scripts/align-spec.mts +3 -2
  9. package/skills/architect-spec-governance/README.md +1 -0
  10. package/skills/architect-spec-governance/SKILL.md +12 -1
  11. package/skills/blast-estimate/README.md +3 -5
  12. package/skills/blast-estimate/SKILL.md +2 -2
  13. package/skills/blast-estimate/scripts/blast-estimate.mts +7 -4
  14. package/skills/builder-impl-governance/SKILL.md +9 -1
  15. package/skills/builder-spec-governance/README.md +1 -0
  16. package/skills/builder-spec-governance/SKILL.md +31 -3
  17. package/skills/check-partition-quality/scripts/check-partition-quality.mts +3 -1
  18. package/skills/check-plan-safety/scripts/check-plan-safety.mts +5 -2
  19. package/skills/check-project-specs/scripts/check-project-specs.mts +67 -5
  20. package/skills/check-retired-terms/README.md +18 -0
  21. package/skills/check-retired-terms/SKILL.md +81 -0
  22. package/skills/check-retired-terms/scripts/check-retired-terms.mts +293 -0
  23. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +3 -2
  24. package/skills/check-spec-structure/scripts/check-spec-structure.mts +3 -2
  25. package/skills/collision-ladder/README.md +3 -5
  26. package/skills/collision-ladder/scripts/collision-ladder.mts +7 -4
  27. package/skills/combat-log-governance/SKILL.md +43 -4
  28. package/skills/concept-index/scripts/concept-index.mts +3 -2
  29. package/skills/discover-plans/scripts/discover-plans.mts +5 -2
  30. package/skills/discover-specs/scripts/discover-specs.mts +5 -2
  31. package/skills/doctrine-loop/README.md +6 -0
  32. package/skills/doctrine-loop/SKILL.md +136 -2
  33. package/skills/formation-loop/SKILL.md +21 -1
  34. package/skills/gate-validation-governance/SKILL.md +2 -2
  35. package/skills/impl-producer-governance/SKILL.md +10 -1
  36. package/skills/init/scripts/wire-statusline.mts +5 -2
  37. package/skills/lifecycle-governance/SKILL.md +1 -1
  38. package/skills/manage-ignore/scripts/manage-ignore.mts +5 -2
  39. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +5 -2
  40. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +5 -2
  41. package/skills/mission-graph/README.md +3 -5
  42. package/skills/mission-graph/SKILL.md +72 -5
  43. package/skills/mission-graph/scripts/mission-graph.mts +505 -16
  44. package/skills/oracle-spec-governance/README.md +7 -2
  45. package/skills/oracle-spec-governance/SKILL.md +21 -4
  46. package/skills/place-node/scripts/place-node.mts +3 -2
  47. package/skills/plan-retirement/README.md +5 -2
  48. package/skills/plan-retirement/SKILL.md +5 -1
  49. package/skills/plan-retirement/scripts/retire-plans.mts +5 -2
  50. package/skills/plugin-contract-governance/SKILL.md +7 -1
  51. package/skills/remediation-governance/SKILL.md +36 -1
  52. package/skills/resolve-governances/scripts/resolve-governances.mts +5 -2
  53. package/skills/resolve-tracking/SKILL.md +2 -2
  54. package/skills/resolve-tracking/scripts/resolve-tracking.mts +5 -4
  55. package/skills/sdd/SKILL.md +1 -1
  56. package/skills/spec-format-governance/README.md +1 -1
  57. package/skills/spec-format-governance/SKILL.md +76 -8
  58. package/skills/spec-gate/SKILL.md +18 -2
  59. package/skills/spec-gate/scripts/check-spec-state.mts +47 -11
  60. package/skills/spec-gate/scripts/check-suite.mts +53 -17
  61. package/skills/spec-gate/scripts/classify-edit-class.mts +10 -7
  62. package/skills/spec-producer-governance/README.md +1 -1
  63. package/skills/spec-producer-governance/SKILL.md +7 -3
  64. package/skills/ssa-lowering/README.md +3 -5
  65. package/skills/start-mission/README.md +1 -1
  66. package/skills/start-mission/SKILL.md +9 -5
  67. package/skills/suite-format-governance/SKILL.md +43 -4
  68. package/skills/touch-set-correction/README.md +3 -5
  69. package/skills/touch-set-correction/scripts/touch-set-correction.mts +7 -5
  70. package/skills/verify-scenarios/SKILL.md +10 -3
  71. package/skills/verify-scenarios/scripts/verify-scenarios.mts +92 -8
@@ -0,0 +1,3 @@
1
+ {
2
+ "cyberlegion": "0.3.0"
3
+ }
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.
@@ -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
- `.agents/specs/sdd/design/harness-spawning.md`.
107
+ handoff. Full model: `start-mission`'s "Dispatch transport" note and the `design/harness-spawning`
108
+ node of the SDD project spec (repo-only).
@@ -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
@@ -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). Otherwise
222
- `ALIGNED` is `true` only when all three lenses pass and no open marker remains. The conductor
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`.
@@ -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
- "name": "cyber-sdd",
3
- "version": "0.0.0",
4
- "type": "module",
5
- "bin": {
6
- "sdd-check-specs": "./skills/check-project-specs/scripts/check-project-specs.mts"
7
- },
8
- "files": [
9
- "skills",
10
- "agents",
11
- ".plugin",
12
- ".codex-plugin",
13
- ".claude-plugin",
14
- "!skills/**/*.test.mts"
15
- ],
16
- "dependencies": {
17
- "gherkin-cli": "0.0.2"
18
- },
19
- "scripts": {
20
- "check:spec": "sdd-check-specs",
21
- "test": "node --test \"skills/*/scripts/*.test.mts\"",
22
- "typecheck": "tsc -p tsconfig.json"
23
- }
24
- }
2
+ "name": "cyber-sdd",
3
+ "version": "0.2.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 === `file://${process.argv[1]}`) {
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
  }
@@ -22,6 +22,7 @@ asked of the implementation instead of the spec.
22
22
  | **Placement matches the declared layout** | The project declares its layout strategy in its root `spec.md` placement map; placement is judged *within* that declaration, not against a preferred one. Under the screaming-architecture default a capability lives in a folder named for its intent; a project that declared `mirror-source` is correctly placed when it mirrors its source. |
23
23
  | **…and the layout preserves the partition** | The declaration is not a licence. Layouts are ranked by whether they keep node ↔ capability one-to-one, because the mission scheduler cuts one mission per node and a scattered capability degrades the schedule toward serial (ADR-0025). **One capability per node, never smeared across nodes** holds under every strategy, and a layered / framework-first top level stays discouraged however it is declared. |
24
24
  | **A well-formed CFG** | The capability's control-flow graph connects — every decision reachable, no dangling branch — and the suite's sections mirror it. |
25
+ | **The CFG reaches every stated extension** | A use case's **extensions** are its divergence paths (`spec-format-governance`), so each is an edge the graph must actually contain. An extension named in `## Use Cases` with no path to it in `## Control Flow` is a **dangling branch read from the other side** — the prose claims a divergence the drawn graph cannot take. A **forbidden combination** is the same defect in guard form: the graph carries the decision that refuses the pair, or the constraint is unenforceable and the prose is decoration. Judge it **both ways** — an edge with no extension is the ordinary uncovered-edge case; an extension with no edge is this one. |
25
26
  | **An orthogonal axis** | Structural fit judges a property the builder was not optimizing — a real independent check even from the same hand. |
26
27
  | **Structural concerns are deferred** | A structural problem in *another* capability is an observation that spawns a new spec — never a marker in the one being built. |
27
28
 
@@ -37,6 +37,15 @@ unbound.
37
37
  level stays discouraged however it is declared.
38
38
  - **A well-formed CFG.** Its control-flow graph connects — every decision reachable, no
39
39
  dangling branch — and the suite's sections mirror it.
40
+ - **The CFG reaches every stated extension.** A use case's **extensions** are its divergence paths
41
+ (`sdd:spec-format-governance`), so each is an edge the graph must actually contain. An extension
42
+ named in `## Use Cases` with no path to it in `## Control Flow` is a **dangling branch read from
43
+ the other side** — the prose claims a divergence the drawn graph cannot take. A **forbidden
44
+ combination** of surface elements is the same defect in guard form: if two elements may not be
45
+ combined, the graph carries the decision that refuses them, or the constraint is unenforceable and
46
+ the prose is decoration. Judge the graph against the stated extensions in **both** directions —
47
+ an edge with no extension is the ordinary uncovered-edge case; an extension with no edge is this
48
+ one.
40
49
  - **An orthogonal axis.** Structural fit judges a property the builder was not optimizing — a real
41
50
  independent check even from the same hand.
42
51
  - **Structural concerns are deferred.** A structural problem in another capability is an observation
@@ -55,5 +64,7 @@ from `spec.md` + the suite only — the solution is out of view (grader independ
55
64
  boundaries.
56
65
  2. **Placement matches the *declared* layout** (`sdd:spec-structure-governance`), not a preferred
57
66
  one; one capability per node either way, never smeared across nodes.
58
- 3. **A well-formed CFG** the suite's sections mirror.
67
+ 3. **A well-formed CFG** the suite's sections mirror — and it reaches every stated extension;
68
+ an extension with no edge is a dangling branch read from the prose side, a forbidden combination
69
+ with no guard is unenforceable.
59
70
  4. **Structural concerns in another capability are deferred** — an observation that spawns a new spec.
@@ -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 and
28
- [`.agents/specs/sdd/blast-estimate/README.md`](../../../../.agents/specs/sdd/blast-estimate/README.md)
29
- for the authoritative behavior description and
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`). See
16
- `.agents/specs/sdd/blast-estimate/README.md` for the full contract and vocabulary.
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). See
5
- // .agents/specs/sdd/blast-estimate/README.md for the full contract this mirrors.
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.main) process.exit(main(process.argv.slice(2)))
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,6 +19,7 @@ contract?" at the impl gate.
19
19
  | Requirement | What it means |
20
20
  | --- | --- |
21
21
  | **Every branch is covered** | Each edge of the capability's CFG has its scenario, and every guard/negative edge is paired with a positive companion. The scenario map is 1:1 in both directions — no orphan scenario, no uncovered edge. |
22
+ | **Every stated extension is a path in the CFG** | A use case's **extensions** are its divergences (`spec-format-governance`), and the CFG is the **single source** scenarios derive from — so an extension earns its scenario by being a path in the graph, never as a second rule beside the edge coverage above. An extension with no path is a hole in the *graph*: fix it there, and the 1:1 edge coverage supplies the scenario. Never derive a scenario from the prose directly — a suite drawn from a stated list is 1:1 with that list by construction and can no longer surface a hole. `extensions: none` is a claim to judge; a forbidden combination is the same rule in guard form. |
22
23
  | **Every scenario is testable** | Each scenario asserts an observable outcome a check can confirm — a boolean, no "sometimes". A behavior the capability cannot expose cannot be specced. |
23
24
  | **A graded subject is still a boolean** | A non-deterministic capability (one whose output varies run to run) still reaches a per-scenario boolean, through a rubric plus a threshold over N runs. The rubric form stays out of the boolean `.feature`, carried as a judge-only `@rubric` scenario. |
24
25
 
@@ -19,18 +19,46 @@ 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`).
27
+ - **Every stated extension is a path in the CFG.** A use case's **extensions** are its divergences
28
+ (`sdd:spec-format-governance`), and the CFG is the **single source** the scenarios derive from —
29
+ so an extension earns its scenario **by being a path in the graph**, never as a second rule
30
+ alongside the edge coverage above. The check is therefore: does the CFG contain a path to each
31
+ stated extension? An extension with no path is a hole in the **graph** — fix it there, and the
32
+ standing 1:1 edge coverage supplies the scenario. **Never derive a scenario from the prose
33
+ directly**: a suite drawn from a stated list is 1:1 with that list by construction and can no
34
+ longer surface a hole, which is the retrofit shape that has diverged in this corpus before. A use
35
+ case declaring `extensions: none` asserts nothing can diverge — judge that claim against the
36
+ graph. A **forbidden combination** is the same rule in guard form: it is a decision the CFG must
37
+ carry, and its refusal scenario comes from that guard's edge.
23
38
  - **Every scenario is testable.** Each asserts an observable outcome a check can confirm — a boolean,
24
39
  no "sometimes". A behavior the capability cannot expose cannot be specced.
25
40
  - **A graded subject is still a boolean.** For a non-deterministic capability the contract reaches a
26
41
  per-scenario boolean through a rubric + threshold over N runs; the rubric form stays out of the
27
42
  boolean `.feature`, carried as a judge-only `@rubric` scenario.
43
+ - **A dimension or cut is grounded on non-author evidence.** When a `@rubric` dimension or its cut is
44
+ justified by a **measurement** (an ablation Δ, a discrimination count), that measurement is admissible
45
+ only if it is **not solely the author's own** — independently produced/reviewed by a non-author, or a
46
+ fresh-adversarial ablation. An author's own instrument silently assumes the property under test. The
47
+ standard is stated canonically at `sdd:doctrine-loop`; this bar requires it be met, not re-listed
48
+ (the cold-instrument doctrine).
28
49
 
29
50
  ## Key points (read-check)
30
51
 
31
52
  1. **Every branch of the capability is covered** — every edge has its scenario, guards paired with
32
53
  positives, the scenario map 1:1.
33
- 2. **Every scenario is testable** — an observable boolean outcome; behavior the capability cannot
54
+ 2. **Every stated extension is a path in the CFG** — the graph is the single source scenarios derive
55
+ from, so an extension earns its scenario by being a path, never as a second rule alongside edge
56
+ coverage; a divergence with no path is a hole in the *graph*. Never derive a scenario from the
57
+ prose directly. `extensions: none` is a claim to judge; a forbidden combination is a guard the
58
+ graph carries.
59
+ 3. **Every scenario is testable** — an observable boolean outcome; behavior the capability cannot
34
60
  expose cannot be specced.
35
- 3. **A graded subject still reaches a per-scenario boolean** via rubric + threshold; the rubric stays
61
+ 4. **A graded subject still reaches a per-scenario boolean** via rubric + threshold; the rubric stays
36
62
  out of the `.feature`.
63
+ 5. **A dimension or cut is grounded on non-author evidence** — a measurement justifying it must be not
64
+ 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 === `file://${process.argv[1]}`) {
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.main) process.exit(main(process.argv.slice(2)))
146
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
147
+ process.exit(main(process.argv.slice(2)))
148
+ }