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.
Files changed (63) 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/blast-estimate/README.md +3 -5
  10. package/skills/blast-estimate/SKILL.md +2 -2
  11. package/skills/blast-estimate/scripts/blast-estimate.mts +7 -4
  12. package/skills/builder-impl-governance/SKILL.md +9 -1
  13. package/skills/builder-spec-governance/SKILL.md +13 -1
  14. package/skills/check-partition-quality/scripts/check-partition-quality.mts +3 -1
  15. package/skills/check-plan-safety/scripts/check-plan-safety.mts +5 -2
  16. package/skills/check-project-specs/scripts/check-project-specs.mts +67 -5
  17. package/skills/check-retired-terms/README.md +18 -0
  18. package/skills/check-retired-terms/SKILL.md +81 -0
  19. package/skills/check-retired-terms/scripts/check-retired-terms.mts +293 -0
  20. package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +3 -2
  21. package/skills/check-spec-structure/scripts/check-spec-structure.mts +3 -2
  22. package/skills/collision-ladder/README.md +3 -5
  23. package/skills/collision-ladder/scripts/collision-ladder.mts +7 -4
  24. package/skills/combat-log-governance/SKILL.md +43 -4
  25. package/skills/concept-index/scripts/concept-index.mts +3 -2
  26. package/skills/discover-plans/scripts/discover-plans.mts +5 -2
  27. package/skills/discover-specs/scripts/discover-specs.mts +5 -2
  28. package/skills/doctrine-loop/README.md +6 -0
  29. package/skills/doctrine-loop/SKILL.md +136 -2
  30. package/skills/formation-loop/SKILL.md +21 -1
  31. package/skills/gate-validation-governance/SKILL.md +2 -2
  32. package/skills/impl-producer-governance/SKILL.md +10 -1
  33. package/skills/init/scripts/wire-statusline.mts +5 -2
  34. package/skills/lifecycle-governance/SKILL.md +1 -1
  35. package/skills/manage-ignore/scripts/manage-ignore.mts +5 -2
  36. package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +5 -2
  37. package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +5 -2
  38. package/skills/mission-graph/README.md +3 -5
  39. package/skills/mission-graph/SKILL.md +72 -5
  40. package/skills/mission-graph/scripts/mission-graph.mts +505 -16
  41. package/skills/place-node/scripts/place-node.mts +3 -2
  42. package/skills/plan-retirement/README.md +5 -2
  43. package/skills/plan-retirement/SKILL.md +5 -1
  44. package/skills/plan-retirement/scripts/retire-plans.mts +5 -2
  45. package/skills/plugin-contract-governance/SKILL.md +7 -1
  46. package/skills/remediation-governance/SKILL.md +36 -1
  47. package/skills/resolve-governances/scripts/resolve-governances.mts +5 -2
  48. package/skills/resolve-tracking/SKILL.md +2 -2
  49. package/skills/resolve-tracking/scripts/resolve-tracking.mts +5 -4
  50. package/skills/sdd/SKILL.md +1 -1
  51. package/skills/spec-format-governance/SKILL.md +5 -0
  52. package/skills/spec-gate/SKILL.md +18 -2
  53. package/skills/spec-gate/scripts/check-spec-state.mts +47 -11
  54. package/skills/spec-gate/scripts/check-suite.mts +53 -17
  55. package/skills/spec-gate/scripts/classify-edit-class.mts +10 -7
  56. package/skills/spec-producer-governance/SKILL.md +2 -2
  57. package/skills/ssa-lowering/README.md +3 -5
  58. package/skills/start-mission/SKILL.md +8 -4
  59. package/skills/suite-format-governance/SKILL.md +43 -4
  60. package/skills/touch-set-correction/README.md +3 -5
  61. package/skills/touch-set-correction/scripts/touch-set-correction.mts +7 -5
  62. package/skills/verify-scenarios/SKILL.md +10 -3
  63. package/skills/verify-scenarios/scripts/verify-scenarios.mts +92 -8
@@ -0,0 +1,3 @@
1
+ {
2
+ "cyberlegion": "0.2.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.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 === `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
  }
@@ -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,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 === `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
+ }
@@ -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 { collectSpecs, discoverSpecFiles, type SpecRecord } from '../../discover-specs/scripts/discover-specs.mts'
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 res = resolveSpecFor(collectSpecs(repoRoot), projectRel)
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.main) process.exit(main(process.argv.slice(2)))
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.