Repository navigation
v1.6.0
Added
-
## Quickstartsection inREADME.md(SW-8) - a numbered path (install ->/sd:setup->
/sd:feature <slug>, with the bundled fixture as the fallback for readers with no project handy)
so a new reader reaches their first spec-approval gate without piecing the flow together from
separate sections. See the README restructure under Changed below for where this section
finally landed - it absorbed the install commands outright. Also adds a star / "using this at
work" call-to-action to## Support, and nine GitHub topics plus a repo description fix (the
command count had drifted from what's on disk) viagh repo edit. -
## Spawned specsin the feature, bug, refactor, and perf spec templates (SW-42) - follow-up
work discovered mid-spec previously had nowhere to land except prose, where it evaporated. The
RCA template's reserved-ID table (Reserved ID | Type | Title | Owner) is now the one convention
across every spec type, not two. Each affected workflow's close-out prompts for the section
when the retro names deferred work - a prompt, not a gate: gate counts are unchanged, since
hard-gating hygiene would tax every spec for a minority's benefit.- The section ships with no
<<...>>token. It is filled at close-out, i.e. after
approved, so an author-fill placeholder there would be anSL010BLOCK on every spec that
deferred nothing. Header + separator is the empty state, and it is alsoSL090's trigger. SL090, the first 🟡 SUGGEST rule - adonespec whose body names deferred work with an
empty spawned-specs table. Advisory, never a failure, and its trigger vocabulary is a closed
phrase list rather than a judgement call, because an advisory that fires on a hunch is noise.
SL091-SL099open the close-out-hygiene band.- A reserved ID is not a registry entry. Documented alongside the index consistency rules:
it gets an.specs/index.mdrow only once its directory exists. Writing the row first
manufactures the ghost rowSL032exists to catch - the fourth of the four real-world
follow-ups that motivated this change was exactly that.
- The section ships with no
-
/sd:port- the fidelity-first port pipeline (SW-41) - the orchestration story that wires the
rest of the port epic (SW-37 skill, SW-38 template/snapshot layout, SW-39 extraction mode, SW-40
parity gate) into one command: bridge/extract -> freeze -> host survey -> fidelity tables -> pin
behavior -> plan -> execute batched -> justified-diff parity -> close-out. Ten phases, six gates,
four of them HARD (donor set frozen, fidelity tables complete, behavior pinned, justified-diff
parity) with no override path; the other two (plan approval, per-batch tests) are ordinary
approvals.--scopeis always explicit - Phase 0 asks when it is omitted rather than inferring
it, because scope selects the Phase 5 pinning mechanism.--fromselects topology (a bridged
cross-repo contract artifact vs an in-repo path/symbol) without changing anything downstream.- Port policy stays Layer 2. Phase 0 reads a
Port policyheading from the host's
.specs/constitution.mdand always states the effective policy in its output, including the
fallback (structural mirror, persd-port-fidelity) when the host declares nothing - the
engine supplies the mechanism, never a hardcoded posture. - Behavior pinning is scope-dependent and gate-verified.
endpointgets a contract test
suite runnable against both donor and host;modulegets characterization tests through an
interface-typed construction seam, so re-pointing donor -> host changes exactly one factory
method and assertion bodies stay byte-identical;featureuses whichever the surface allows;
patternskips pinning (no donor instance) but the gate still proves the host production tree
is unmodified via an emptygit diff/git status --porcelain, not a good-faith claim. - A port-specific complexity metric. The existing decompose thresholds count impacted files
and layers, which trip on nearly every port by construction (a port's file count equals the
donor's). Phase 6 instead counts deviation-table rows requiring adaptation - the quantity that
actually scales with how much judgment the work needs - and records the rationale in
01-plan.md. - The anti-drift mechanism lives in the task block, not the gate. Every port task's
Pattern refscites a snapshot member range (04-artifacts/source/<path>:<first>-<last>), never prose
and never a host sibling, andAcceptancecarries the licensed-deviation ID list. Anything not
on that list is reproduced as-is. A task missing either is a planning defect, refused before
execution rather than caught only at the parity gate. - Lifecycle divergence, deliberate. Unlike
/sd:featureand/sd:refactor,abortnever
jumps a port spec toarchived- it leaves the spec at its current state so re-invoking resumes
exactly there, since a partially-frozen or partially-pinned port has no clean "give up" shortcut
the way an unstarted feature does. WORKFLOW_TYPE = portadded tosd-implementer- neitherfeature(allows new public API
freely) norrefactor(forbids new public API, requiresINVARIANTS) was the right constraint
set for reproducing a donor's structure under a licensed-deviation list, so this is a genuine
fifth mode, not a reuse of an existing one.portjoins the prompt-router keyword map (backport,port from,port the,donor repo,mirror from,replicate from- deliberately multi-word phrases; a bare"port"would
fire on "support", "report", "portal"), shipped in both hook implementations plus the
project-config.template.jsondefault.contractLint.budgets.skillsBytesraised 12377 -> 12412 -skills/sd-port-fidelity/SKILL.md
picked up two small cross-references to/sd:port's phases (the freeze step, the fidelity-table
author) replacing prose that pointed at "a documented manual step until the port pipeline
lands"; the ratchet moves with it.- Deliberately NOT built:
--sync/ re-port drift detection, multi-donor ports, and editing a
host project's build/lint/coverage configuration to exclude the snapshot - the command warns
about tooling that globs.specs/, it never edits. - Known gaps carried forward: no
SL06xrule machine-checks the port task-block contract
(Pattern refs range + licensed-deviation list) - it is enforced by Phase 6 refusing to execute a
defective block, not by/sd:spec validate; andspec-gate/subagent-retrostill do not
recognize thePORT-prefix (onlyprompt-router's keyword routing landed this round) - see
docs/troubleshooting.md.PROJECT-SNAPSHOT.mddoes not exist in this repo and never has (see
the historical note below); nothing in this change introduces it.
- Port policy stays Layer 2. Phase 0 reads a
-
Port parity adjudication:
port-parityTASK_TYPE onsd-reviewer, the parity diff artifact,
and the parity gate (SW-40) - the enforcement half of the port epic and the only mechanism in
it that can see logic drift, structural mismatch, or silent simplification; test-green and
contract compliance are blind to all three, which left the fidelity rules from SW-37 as
honour-system prose. The main thread writes04-artifacts/parity/: one unified diff per
non-omitpath mapping row, an all-deletion diff for a row whose host file is absent, an
all-addition diff for a changeset file with no row at all, plusINDEX.mdlisting them.
sd-reviewerconsumesINDEX.mdasDIFF_REFand classifies every hunk with
sd-port-fidelity's vocabulary, now five classes rather than four.overreachedis the new one -
a deviation row covers the hunk but the hunk changes more than that row'sHost formstates -
and it is the class a rubber stamp hides in. Two whole-artifact checks join it: member
completeness, reported as a count with each absent row named, and path conformance, one BLOCK per
unmapped changeset file. Ajustifiedhunk is a PASS and is deliberately NOT written up, so a
real BLOCK cannot drown in a list of accepted diffs.templates/specs/port.template.md's fixed
AC-1 gainsoverreachedand aHost form-covers-the-hunk clause: reworded to track the skill's
vocabulary, never renumbered, because the number is what every fidelity finding anchors to.- Diff generation stays on the main thread, enforced by the tool allowlist -
sd-reviewer
gains noBashand no write tool and stays incontractLint.readOnlyAgents(CL201). The
reviewer that cannot produce the diff also cannot fix what the diff shows; adjudicating from a
file it did not write is the entire structural guarantee. - The
/sd:verifyoverlap, decided before any checking logic was written - member
completeness and path conformance stay with the reviewer and/sd:verifyis untouched, no new
VF0xxrule. Rationale recorded indocs/architecture.md:/sd:verifydecides everything from
00-spec.mdand02-tasks.mdwith fixed regex shapes, and neither check can be decided that
way - one needs a member boundary recognized in an arbitrary host language, the other needs a
changeset input/sd:verifydoes not take. - Deliberately NOT built: semantic equivalence checking, which is the behavior-pinning
phase's job rather than the diff's, and auto-generation of deviation rows from unexplained
hunks, which would let the diff justify itself and turn the gate into a rubber stamp. The gate
stays HARD with exactly two resolutions - revert the host toward the snapshot, or add a
deviation row whose group and citation hold up and re-run - and no override. - Known gap: the pipeline command that would generate the parity artifacts and host the gate
is SW-41. Until it lands, diff generation is a documented manual step (AC-1 asks for it
documented, not automated) and the new mode is invoked by no command, so contract-lint reports
aCL101WARN for it, joining the onessd-code-explorerandsd-revieweralready carry.
Recorded indocs/troubleshooting.md.
- Diff generation stays on the main thread, enforced by the tool allowlist -
-
examples/port-parity-fixture/(SW-40) - matchedclean/andbroken/port trees over a
toolchain-free plain-text donor, followingspec-lint-fixture's convention: a README table of
expected findings and<!-- SEEDED: ... -->comments naming each defect.broken/seeds one
defect per BLOCK class plus both whole-artifact checks, and carries deviations that are correctly
cited and applied inclean/but exceeded inbroken/-overreached, not a fourth unrelated
deviation, demonstrates the negative. Seed markers live in the spec rather than in the ported
files, because a comment line inside a host file is itself anextrahunk and would seed a
defect the table does not claim. Members are separated by an unchanged padding block so every
seed lands in its own diff hunk under the "at least 3 lines of context" convention, rather than
merging adjacent changes into one hunk that would need two classifications. Not run in CI, for
the same reasonspec-lint-fixtureis not: the adjudicator is a prompt, and a script able to run
it would be a second copy of the rules. -
contractLint.budgets.skillsBytesraised 10656 -> 12377 -skills/sd-port-fidelity/SKILL.md
absorbed the parity artifact layout, the fifth hunk class, both whole-artifact checks and the
gate, and takes over as its area's ratchet-setter fromsd-spec-templates.agentsBytesis
unchanged on purpose:agents/reviewer.mdgrows to roughly 10.5k against the 15232 ceiling
agents/spec-architect.mdstill sets, so the sixth task type needed no headroom. -
port-extractTASK mode onsd-code-explorer, invoked from/sd:explore --port(SW-39) -
the donor-side extraction half of the port workflow, consumingsd-port-fidelity(SW-37) and
feedingPORTspec authoring (SW-38). Eight fixed sections (Entry surface, Output surface,
Member closure, Complement set, Collaborators, Non-obvious invariants, Dead paths on this entry
point, Precedent conventions), eachfile:line-cited or explicitlyNone found (searched: ...)- replacing the prior free-form/sd:exploreprose contract whose gaps a host
implementer filled by invention. Member closure'sDonor path/Ordinal/Membercolumns carry
over verbatim into the host's Member manifest table. Explorer's tool allowlist is unchanged (no
write tool added) -/sd:exploreitself, not the agent, computessource_commit(git rev-parse HEAD, with an explicitdirtysentence instead of a misleading sha whengit status --porcelainis non-empty), hashes, and copies donor files into
.specs/_explorations/<slug>-<timestamp>/source/plus aMANIFEST.mdin the exact
sd-port-fidelity"Snapshot artifacts" format when--snapshot contract+sourceis passed -
matching howimpact-map's output is appended by the caller rather than written by the agent.
Output is stack-agnostic and donor-only; the host side of the bridge (copying the produced
folder into aPORTspec's04-artifacts/source/) stays a manual/scripted step, and reading a
donor from a host-rooted session stays out of scope - both per SW-39. The consumer that turns
this into an end-to-end pipeline is SW-41. -
PORTspec prefix,port.template.md, and snapshot artifact layout (SW-38) - the authoring
half of the port workflow, consumingsd-port-fidelity(SW-37).PORT-<slug>-<YYYYMMDD>joins
spec.prefixesand every enumeration site (commands/spec.md,commands/release.md,
agents/spec-architect.md,sd-retro-lessons, docs).templates/specs/port.template.mdadds six
mandatory sections (Behavioral contract, Behavioral invariants, Path mapping table, Member
manifest, Deviation table, Spawned specs) plus five provenance frontmatter fields (scope,
source_repo,source_commit,source_license,snapshot); the three table schemas are reused
verbatim fromsd-port-fidelityrather than the ticket's own prose, which described a different,
stale external precedent (FEAT-details-translation-builder, not present anywhere in this repo).
Snapshot layout (04-artifacts/source/+MANIFEST.md, per-file donor path/commit/hash/member
ranges) is documented insd-port-fidelity's new "Snapshot artifacts" section, which is also the
defined input a later drift check can consume./sd:spec validategains theSL080-SL083
port-integrity band. PORT is release-eligible, maps to CHANGELOGAdded, and triggers a MINOR
bump like a feature.- Deviation from the ticket's "adds a glob" wording:
paths.protectedmatching in both
spec-gatehooks is exact-string only, with no glob engine on either platform. Freezing a
snapshot instead enumerates every file under04-artifacts/source/plusMANIFEST.mdas
individual literalpaths.protectedentries - the mechanism is reused exactly as shipped, with
zero hook changes (AC-9 is satisfied to the letter). - Known gap, deferred: the
(FEAT|BUG|REF|PERF|RCA)prefix regex is hardcoded across
spec-gate,subagent-retro, andprompt-router(both platforms) and does not read
spec.prefixes. APORT-spec is therefore invisible to in-progress-spec detection, lesson
scoping, and context injection until those hooks are updated - out of scope here (no AC in this
story requires a working end-to-end port pipeline; that pipeline is SW-41). Documented in the
new template, insd-port-fidelity, and indocs/troubleshooting.md. - No
.ps1or.shfile was modified (AC-9).
- Deviation from the ticket's "adds a glob" wording:
-
contractLint.budgetsraised for the SW-38 wiring -commandsBytes25978 -> 29664
(commands/spec.mdgained the port-spec validate rules and subsection, including a follow-up fix
soSL080also catches a literalnonevalue),agentsBytes14671 -> 15232
(agents/spec-architect.mdgained the sixth workflow type and port-specific inputs),
skillsBytes9134 -> 10656 (sd-spec-templatesandsd-port-fidelityboth grew; the latter is
now the ratchet-setter for its area). Real, reviewed growth from a new spec type landing across
three files that each already sat at their prior ceiling, not a reflex to a red run. -
sd-port-fidelityskill (SW-37) - cross-project port policy, promoted into a skill because
two agents need the same rule body:sd-spec-architectauthors the deviation table and the port
task blocks,sd-reviewerjudges whether a diff hunk is justified. Defines structural mirror as
the default posture, the four-group deviation allowlist (compiler/namespace/assembly, host
constitution, host precedent, agreed behavior-parity fix) with a required citation per group, the
five anti-simplification rules that reach the implementer through taskAcceptancerather than
through the skill, completeness conditions for the three gate tables (path mapping, member
manifest, deviation table) phrased as counting and matching predicates a gate can evaluate
without judgement, and the closed four-class hunk vocabulary (justified/unjustified/
missing/extra). Wired intoagents/spec-architect.mdandagents/reviewer.mdonly; the
consumers that enforce it are SW-38 (port spec template), SW-39 (donor extraction), SW-40
(justified-diff artifact and reviewer adjudication) and SW-41 (the pipeline itself). Fidelity
findings anchor to the port spec's mandatory fidelity acceptance criterion, which is already a
legal code anchor -sd-severity-taxonomy's Anchors table is deliberately left untouched. -
contractLint.budgets.agentsBytesraised 14454 -> 14671 -agents/spec-architect.mdwas the
ratchet-setter and sat at the ceiling exactly, so the two-line SW-37 wiring (oneskills:entry,
onePattern refs protocolitem) could not land without moving it. Real, reviewed growth on the
repo's largest agent, not a reflex to a red run; CL500 is unsuppressible on line 1. -
Threshold calibration machinery (SW-31) -
spec-gate(hooks/bash/spec-gate.sh,
hooks/powershell/spec-gate.ps1) now infers a completed Gate Complexity split fromindex.md's
own state (aFEAT-Xrow archived alongside a registeredFEAT-X-<slug>child) and records it as
a newgate:"complexity"/decision:"split"metrics event - this repo's first metric for a gate
that is otherwise decided as model-executed prose. Recorded only when theindex.mdedit is
actually allowed through, never on ablockexit, so the count under-reports on any project that
leavesindex.mdprotected (the default)./sd:status --calibration(new optional flag,
default invocation's read contract unchanged) reports task/layer/file distributions from spec
artifacts alongside the new split count, framed asinsufficient data (n=<n>)below the
CONTRIBUTING re-calibration trigger.docs/adr/0004-threshold-calibration.mdrecords this run's
verdict (insufficient data on every threshold at the current n=1 corpus) and the deliberately
declined scope (full trip-rate instrumentation);templates/project-config.template.jsonnow
marksretroStaleMinutes,debounceMinutes, andmaxLessonsas unmeasured judgement calls,
matching the existingmaxSizeKbcaveat. CONTRIBUTING names the re-calibration ritual (every 20
closed specs, or each minor release). -
Install-time version stamp (SW-29) -
install/install.ps1/install/install.shnow write
specwright-version.txtinto every installed<area>/sd/root, parsed at install time from the
newest dated## [x.y.z] - <date>heading inCHANGELOG.md- the same sourceversionClaims
already treats as canonical - so an installed engine can finally report which version it is
without a second version literal anywhere in the installer. LF, no BOM, US-ASCII, byte-identical
whichever installer writes it; a repeat install reports the stampidenticaland skips it, and
uninstall.ps1/uninstall.shremove it for free since it lives inside thesd/directory they
already delete recursively. Check 5 inscripts/validate.ps1/scripts/validate.shnow asserts
the stamp's presence, encoding, content and no-op/refresh behavior; the CI round-trip's install ->
uninstall step now verifies no engine-written file survives anywhere under the base path, not just
that thesd/directories are gone. -
/sd:setupreads the version stamp and reports engine/config drift (SW-29). Phase 0 now
loads the installedspecwright-version.txt; Phase 1.5's batch drift-check compares it against
.claude/project-config.json'sversionfield and generalizes the missing-field check into a
full template diff, so a project scaffolded under an older engine sees every gap, not just the
two fields the check used to hardcode.versionis the one field the Apply step is allowed to
overwrite outside the project-specific preserve-list, since it is engine-tracked. Fresh scaffolds
(Phase 6) now stamp the real installed engine version instead of the template's literal1.0.0.
Also removes the dead$schemaURL fromproject-config.template.json- no schema was ever
published at that path, and the org name in the URL didn't even match the real repo
(Developzonevsdevelopzoneio); the drift-check now flags any leftover$schemakey for
removal instead of a rewrite. -
tests/e2e/: headless behavioral eval harness for commands and gates (SW-27). Where the rest
ofscripts//tests/proves the engine's assets reference each other correctly, this drives
realclaude -p(headless) sessions against a throwaway copy ofexamples/fixture-project/
examples/spec-lint-fixture/brokenand asserts on produced artifacts (files, frontmatter,
status values) rather than transcript wording - the first mechanism that proves the engine
behaves correctly end-to-end. 5 scenarios:/sd:setupfresh-scaffold,/sd:featurehappy path
to a passing06-verify.md, spec-gate denying a code edit with no in-progress spec, spec-gate's
verify-gate denying an unverified close-out, and/sd:spec validatesurfacing the seededSL0xx
corpus.run-e2e.ps1(single pwsh runner, same posture astests/hooks/run-conformance.ps1/
tests/contract-lint/run-selftest.ps1) sandboxes each run via a fresh "fake home" with the engine
installed into it through the installer's own-BasePathflag;-SelfTestre-runs the two
negative scenarios against a neutered spec-gate hook and asserts the harness notices. Not wired
into per-PRci.yml- runs nightly / on manual dispatch via.github/workflows/e2e-nightly.yml.
tests/e2e/README.mddocuments the isolation model, prerequisites, cost, and two findings from
building it:--permission-mode acceptEditssilently overrides aPreToolUsehook's deny (only
dontAskwith no--allowedToolsoverride actually respects one), and spec-gate's matcher covers
Edit/Write/MultiEditonly, notBash-mediated file writes. -
examples/fixture-project/(SW-30) - a tiny, runnable, non-.NET (plain Node.js) example
project: pre-scaffoldedCLAUDE.md,.specs/constitution.md, and.claude/project-config.json,
plus.specs/FEAT-todo-priority/, a complete, real/sd:featurerun (spec through verify)
committed as the worked example. Proves stack-agnosticism by demonstration instead of assertion
alone - the first non-.NET spec run through the engine. RootREADME.mdQuickstart, compatibility
matrix, and Documentation list updated to point at it;examples/README.mdand
docs/walkthrough.mdreconciled to stop promising a fixture that didn't exist yet. Closes SW-8's
overlapping "runnable examples/ fixture" acceptance criterion by reference. -
Check 8: cross-file contract lint (
scripts/contract-lint.ps1/scripts/contract-lint.sh,
SW-26 wave 1). Where Check 7 guards inventory, Check 8 guards the relationships between commands,
agents and skills. 17 rules across three bands:CL0xxreference resolution,CL3xxgate
integrity,CL9xxsuppression hygiene. Deterministic file ops, no subagent, TSV on stdout, exit
2when it cannot run. Wired into both validators and into CI on all three OSes. -
contractLintsubtree inspecwright.manifest.json: scan scope, the rule registry (the single
source of every rule's severity, so a BLOCK/WARN divergence between the twins is structurally
impossible), declared gate contracts, spec artifact names, skill-consumer escapes and the CL305
override vocabulary. Each linter carries a registry parity guard that exits2when the rules it
dispatches and the registry disagree. -
Gate counts are now published claims:
contractLint.gates.<file>.hardseeds Check 7 quantities,
givingREADME <- manifestthere andmanifest <- diskin CL302, hence transitively
README == disk. 21 newdocClaimsplus aN hard gatesclaim phrase. -
tests/contract-lint/fixture suite - a minimal valid mini-engine plus one overlay per rule, five
false-positive guards and one must-still-bite case. Goldens pin a seed marker, never a line
number.run-selftest.ps1drives both implementations in one process so parity is asserted, and
-SelfTestproves the harness detects a linter that reports nothing. -
docs/contract-lint.md- rule catalogue, suppression syntax, manifest surface, and why a declared
gate count belongs in a manifest that otherwise stores no counts.CONTRIBUTING.mdgained a
matching section. -
Machine-readable
Inputs (required): .../Inputs (optional): ...declarations under every
TASK/mode/workflow-type heading inagents/*.md(22 sections acrosscode-explorer,debugger,
implementer,reviewer,spec-architect;docs-writerhas no mode dispatch) - prerequisite
for the invocation-contract validator in SW-26 (SW-25). Format documented inCONTRIBUTING.md
under "Agents". No agent behaviour changed.
Changed
README.mdcut from 390 to 282 lines (-28%) with no claim dropped. The restructure below
fixed the order of the page but not its volume; a reader still scrolled past a lot of
repetition to reach the call to action. The redundancy was concentrated, not scattered:## Architecture highlightswas a near-copy of## Why spec-driven?- three of its four
bullets restated hard gates, the cost model, and stack-agnosticism verbatim. The section is
gone; its one distinct idea (the three layers) is now a sentence in## Why spec-driven?, and
the 18-line ASCII diagram was dropped becausedocs/architecture.mdalready carries it.## Featureswas a table of contents for the two tables beneath it - all 14 command names
printed there and again in## Commands, all 6 agent names there and again in
## Agents and skills. The rows stay (they anchordocClaims); only the duplicated cell
contents were replaced with a pointer.- Fifteen
---rules cost ~30 lines to draw a line GitHub already draws under everyh2.
All removed. - Also: the spec-folder tree showed five spec types where one plus a note conveys the same shape,
and the YAML frontmatter snippet was dropped (it duplicatesdocs/architecture.md). - The install and uninstall blocks stay split per platform, on purpose. Collapsing each pair
into a single fenced block was tried and reverted: it put PowerShell inside a```bashfence,
forced the reader to parse trailing comments to find their own line, and - worst - chained the
preview into the real run with&&, so--dry-runoutput scrolled past and the install
happened anyway. A dry run you cannot read is not a dry run. Four blocks, ~19 lines more,
correct.
README.mdrestructured to lead with evidence rather than inventory. The page opened with a
feature count and asked the reader to take 330 lines of tables on trust before showing a single
line of output. It now opens with## What it looks like- a real Gate 1 spec-approval STOP and
the real Gate 3 reviewer verdict (0 BLOCK / 0 WARN / 5 SUGGEST / 7 PASS,18/18tests), both
transcribed from the committedFEAT-todo-priorityrun inexamples/fixture-project/, not
invented for the README.## How this differs from prompt-level disciplinenames the four structural properties -
gates halt the phase,spec-gatedeniesEdit/Writeat thePreToolUselayer rather than in
the prompt, the reviewer's allowlist contains no write tools, and specs are subagent inputs
rather than write-ups. Previously the README asserted discipline without saying what enforces it.- The two install sections are one.
## Quickstartnow carries the install commands inline
plus an explicit requirements line; the old## Quick installbecomes
## Install options and uninstalland keeps only the advanced path. The reader no longer
bounces between two sections that pointed at each other. - The agent and skill tables shrank to a summary plus a link. Tool allowlists, the
command -> agent routing map, and the nine-row skill catalogue already lived in
docs/architecture.mdverbatim; the README kept a duplicate that could drift. Only the role
and model columns stay. - Adds a release badge, surfaces
examples/port-parity-fixture/andexamples/spec-lint-fixture/
(previously unmentioned anywhere in the README), and replaces the unsourced "typical feature run
~$2-3" with a pointer to/sd:status, which reports the reader's own cost from their metrics log.
- Two new
docClaimsentries for the README's prose subagent and skill counts. Both new
sentences tripped Check 7's undeclared-claim scan, which is the intended behaviour - the numbers
are now derived from disk like every other published count.
Fixed
README.md's BMAD acknowledgement pointed athttps://github.com/- a placeholder URL that
had shipped since the section was written. Now links tobmad-code-org/BMAD-METHOD.README.md's compatibility matrix claimed "Latest as of Jan 2026" for the Claude Code CLI row,
seven months stale. The roadmap's "Planned - nothing queued right now" bullet read as a
stalled project against a repo that had shipped through v1.5.0; the section now leads with the
shipped release and drops the empty bucket.- Two em dashes in the README's skills table, the only two in a file that uses
-roughly 200 times. docs/architecture.mdlisted four items against a gate count of three for/sd:feature, one of
them naming a per-task review gate removed when the workflow moved to batch review. Found by
writing CL302, fixed before the linter landed.commands/setup.md's detected-facts gate had no literalSTOP(the nearest one belonged to the
migration gate above it), and neither setup gate offered a machine-readable option set. Both were
real CL300/CL301 violations on disk.- Audit of every
commands/*.mdinvocation site against the new declarations turned up three
drifted contracts, now corrected:/sd:bug's hypothesis-verify loop omittedEVIDENCE_DIRfrom
itssd-debuggerTASK = verifycall, so verification evidence had nowhere to be saved
(commands/bug.md);/sd:rcapassesMODE = incidenttosd-debugger'senumeratetask, a
token the agent never declared (agents/debugger.md);/sd:feature's batch review passes
PLAN_REFtosd-reviewer'sholistictask, likewise undeclared (agents/reviewer.md).
agents/spec-architect.md'screatemode also documented anINCIDENT_DETAILSinput no command
has ever set - removed, since/sd:rcafills those fields interactively, not via a token. - Check 8 wave 2:
CL1xxinvocation contract (SW-26 wave 2 / SW-32), making the SW-25Inputs (required|optional):declarations load-bearing. Five rules:CL100(BLOCK) an invocation sets
TASK/WORKFLOW_TYPE/TASK_TYPEto a mode the target agent never declared;CL101(WARN) an
agent declares a mode no command ever invokes;CL102(BLOCK) an invocation omits a required
input;CL103(WARN) an invocation passes a token the mode declares nowhere;CL104(BLOCK) two
agent files share a frontmattername:. Two new indices in both linters - a mode-declaration
table built fromagents/*.mdmode headings, and an invocation-token table built by scanning each
commands/*.mdinvocation forward to the next heading, the next invocation, or the next
top-level numbered step, whichever comes first. Five new fixture cases plus a row each in
tests/contract-lint/README.mdanddocs/contract-lint.md.
Fixed
commands/refactor.md's characterization-test sub-loop invokedsd-implementerwith
WORKFLOW_TYPE = refactorbut never passedINVARIANTS, the one field that mode's constraint set
actually reads - a live instance of the exact defect classCL1xxexists to catch (found while
building it, per SW-32).agents/code-explorer.md'sTASK = standalonemode never declared theGITNEXUS_AVAILABLEinput
its own "Always do first" step reads and/sd:explorealways passes.- Two documented
sd-spec-architectTASK = planscoped-re-plan invocations
(commands/feature.md,commands/refactor.md) legitimately passREPLAN_SCOPE/REVISION
instead of the mode'sSPEC/IMPACT- suppressed withCL102reasons citing the "Scoped
re-plan" sub-path inagents/spec-architect.md, since the declaration has no syntax for an
either/or required set. - Check 8 wave 3a:
CL2xxrole and tool integrity (SW-33), four rules over the agent role
contract:CL200(BLOCK) an agent with no write tool is
instructed to write, append or create;CL201(BLOCK) an agent listed in the new
contractLint.readOnlyAgentsdeclares a write tool anyway;CL202(WARN) anmcp__*name in
scan scope is absent from the newcontractLint.knownMcpTools;CL203(WARN) an agent's own
frontmatter declares a tool its own body never mentions. A write tool is exactly
Write/Edit/MultiEdit-Bashdeliberately does not count, a scope decision recorded in
docs/contract-lint.md. Two new indices in both linters: a per-agenttools:frontmatter table
(feedsCL200/CL201/CL203) and anmcp__*token scan across scan scope (feedsCL202).
readOnlyAgentsseeded with the three agents already lacking a write tool
(sd-code-explorer/sd-reviewer/sd-debugger);knownMcpToolsseeded with the twelve real
mcp__*names currently declared acrossagents/*.md. Five new fixture cases (one per rule plus
a false-positive guard provingCL200ignores negated and third-person uses of its verbs) plus a
row each intests/contract-lint/README.mdanddocs/contract-lint.md.
Fixed
tests/contract-lint/fixtures/_base/agents/keeper.mddeclaredGrepin itstools:line but
never mentioned it in its own body - would have trippedCL203on the fixture suite's own base
tree the moment the rule shipped. Five older fixture overlays (cl002,cl007,cl101,cl102,
cl103) had the same latent gap in their own copies of the demo agent, found the same way.- Check 8 wave 3b:
CL4xxstack-agnostic prose +CL306(SW-34), four rules closing the two
promises wave 1 deferred:CL400(BLOCK) a hardcoded stack command token outside a
<<placeholder>>, a fenced example, or a suppression comment;CL401(WARN, permanent) the same
for a language/framework name - a language name in prose is often legitimate, so this one never
promotes;CL402(BLOCK) a hardcoded absolute filesystem path in scan scope;CL306(BLOCK) a
HARD gate's prose
describes an escape hatch with nocontract-lint: allow CL306comment nearby - the prose half of
CL305that wave 1 deferred for exactly this reason.CL306deliberately excludes whatever
CL305already governs (the option-set parenthetical and backtick-led option bullets), so the two
rules cover disjoint territory instead of double-firing on the same line. No new manifest surface
for per-gate exceptions:CL306reuses the existing suppression-comment convention rather than
adding a second mechanism that would say the same thing. Two new hand-maintained vocabulary lists
inspecwright.manifest.json(contractLint.stackTokens.{commands,languages}and
gateProseEscapeTokens), same category asoverrideOptionTokens. The hardcoded MSSQL/C#/
TypeScript references this ticket originally described were already genericized by an earlier
commit; the real findings this wave turned up in the current tree were all illustrative or
multi-stack-heuristic uses of stack vocabulary (enumerated manifest-filename lists in
commands/setup.md, forbidden-example prose incommands/verify.md,agents/spec-architect.md
andskills/sd-retro-lessons/SKILL.md, and a settings.json path-pattern description in
commands/setup.md) - each annotated with acontract-lint: allowcomment rather than rewritten,
since rewriting them would have deleted correct stack-agnostic design, not fixed a bug. Nine new
fixture cases (four must-fire, five false-positive guards) plus a row each in
tests/contract-lint/README.mdanddocs/contract-lint.md. - Check 8 wave 4:
CL5xxfile budgets (SW-35), one rule closing the "prompt files only ever
grow" gap:CL500(WARN, permanent) a file exceeds the newcontractLint.budgets.<area>Bytes
ceiling for its scan-scope area. The finding reports how far over budget the file is, not a bare
"over budget". The byte count is normalized (sum of each line's byte length off the same per-line
cache every other rule reads, plus one separator per boundary), never a raw disk read - scan-scope
*.mdistext=autoand checks out CRLF on Windows but LF on Linux CI, so a raw byte count would
makeCL500disagree with itself across platforms for identical content (confirmed:
commands/spec.mdis 25979 bytes as a git blob, 26521 bytes on a native Windows checkout). Three
new manifest keys (contractLint.budgets.commandsBytes/agentsBytes/skillsBytes), each ratcheted
to today's largest file in that area so the repo passes clean by construction and every later hit
is real growth. Two new fixture cases (one must-fire, one false-positive guard at the budget
boundary) plus a row each intests/contract-lint/README.mdanddocs/contract-lint.md.
Changed
- SW-26 promotion:
CL200/CL306/CL400WARN -> BLOCK. All three shipped WARN with an
explicit "promotes to BLOCK in a follow-up commit once it has run clean for a release" clause.
As of 2026-07-31 the engine tree has zero findings for all three under both implementations, so
the promotion inspecwright.manifest.jsonis now live (severity is registry-driven, so no rule
logic changed).CL201/CL402were already BLOCK;CL202/CL203/CL401/CL500stay WARN by
design and do not promote. Five existing<!-- contract-lint: allow -->suppressions change from
silencing a WARN to being load-bearing for a green CI:commands/bug.mdand
commands/release.md(CL306),commands/setup.md(two) andcommands/verify.md(CL400).
Fixed
- Check 7 could not see version/release-state claims at all - its entire vocabulary
(docClaims/claimPhrases) is built around integer counts derived from disk, so a stale
version string had nothing to trip it (SW-28).ROADMAP.mdhad saidCurrent released version: **1.3.0**since before the1.4.0release, unnoticed through every green Check 7 run since,
and its## Plannedsection claimed "nothing queued right now" while[Unreleased]carried
eight real entries - a release's worth of built-but-uncut work. A newversionClaimsarray in
specwright.manifest.jsoncloses this: entries are{file, pattern}(noequals- there is no
disk-derived quantity for a version, so the expected value is always the newest dated
## [x.y.z] - <date>heading inCHANGELOG.md, computed once per run).scripts/validate.sh/
.ps1gained a matching check, deliberately independent of Check 6's existing
next_header/$nextHeadervariables - those resolve to whatever line sits directly below
[Unreleased], which is the first bullet rather than a heading in the normal (non-just-released)
state, so reusing them would have passed on bash and silently done nothing on PowerShell.
ROADMAP.mdnow reads1.5.0and its## Plannedsection points at[Unreleased]instead of
claiming an empty queue.selftest-docs.{sh,ps1}grow from 6 scenarios to 7: the new one plants
a wrong version in a sandboxedROADMAP.mdand asserts the validator names both the wrong value
and the true one fromCHANGELOG.md, using the same derive-don't-hardcode discipline SW-20
established for corruption targets. The ticket's secondary finding (a hand-maintained
PROJECT-SNAPSHOT.mdneeding generation or trimming) does not apply - that file does not exist
in this repo and never has.