Optimize spec-driven workflow: gates, metrics, lessons, complexity triage - #23
Merged
Conversation
Reconcile published docs against disk (SW-1). README.md listed /sd:setup at no hard gates; commands/setup.md has two approval gates (lines 116, 187). The column counts total approval gates, as refactor's 6 and perf's 8 likewise include non-HARD gates. README.md's sd-evidence-citation "Used by" omitted sd-docs-writer -- the skill has four consumers, not three, per the agents' frontmatter. docs/architecture.md already had this right. Stale MSSQL references left over from the stack-agnostic mcp.mssql -> mcp.database rename: docs/architecture.md named a hardcoded MSSQL tool in sd-debugger's tool surface and an mssql server in the project-scope MCP table; README.md named MSSQL in the MCP-friendly summary and MCP table; docs/troubleshooting.md had an MSSQL-titled section. All now describe the project-provided database MCP, matching agents/debugger.md and templates/project-config.template.json. Covers the documentation half of REVIEW-TODO.md item 5. The engine defect it also names -- debugger.md's body prescribes a database MCP tool while its allowlist has none -- remains open. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The validators derived asset counts from disk but never checked the numbers
published in the docs, so SW-1's inventory drift reached main with CI green.
A discipline tool that misdescribes itself has no standing to lecture anyone
about specs.
Add specwright.manifest.json as the canonical inventory contract: it declares
where assets live (areas) and where the docs claim numbers about them
(docClaims). It stores no counts - they are derived from disk at runtime, so a
new command/agent/skill/template means adding the file and nothing else. A
manifest holding hardcoded numbers would be a third place to update and would
reintroduce the drift it exists to prevent.
Check 7 in validate.{ps1,sh} verifies 46 published claims across six files. It
also fails on a vacuous claim (pattern matches nothing - a reworded doc that
silently disabled its own check) and an undeclared claim (a number no docClaims
entry covers), since either would let the check rot into a no-op that still
reports green. Historical docs keep their superseded counts by design and are
excluded.
selftest-docs.{ps1,sh} proves the check bites, by corrupting a throwaway repo
copy across four scenarios. CI runs it on Ubuntu, macOS and Windows.
Scope per the updated SW-3: extends the existing validator pair and ci.yml
rather than adding tools/check-consistency.* and a second workflow, which would
duplicate both for one concern. Not blocked on SW-2's plugin.json, which does
not exist yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-4 seams 1 and 2. Reconciles the validate rules with the templates
they check, then adds the four structural checks the ticket asks for.
Seam 1 - reconcile the rules. Two real defects, not cleanup:
- Required fields had drifted from sd-spec-templates/SKILL.md, the
skill that authors the specs. validate checked only id/type/status/
created, so it passed specs missing target_metric, smell, jira and
incident_resolved.
- The placeholder rule contradicted the templates: "no <<placeholder>>
at >= approved" failed a correct perf spec, whose baseline field must
still be empty at approved per the cross-phase rule in CLAUDE.md. The
conflict spanned 20 fields across four templates in three marker
forms.
Adds the <<PHASE-N: ...>> token so the two forms are distinguishable and
have opposite, checkable rules: author-fill must be gone by approved,
phase-deferred must still be there. Pre-filling a field from memory is
now caught, not merely discouraged.
Seam 2 - structural checks. linked_specs becomes real frontmatter on all
five templates, replacing a body section only feature.template.md had -
/sd:spec link accepted any ID but had nowhere to write on the other four
types. Adds index<->folder symmetry, transition replay against the
05-retro.md log, link resolution and link symmetry.
The link inverse map was incoherent: 9 accepted relations, 5 defined
inverses, and depends-on/blocked-by asserting the same edge in two
spellings - which made symmetry undecidable. blocked-by is now an input
alias normalized to depends-on, leaving a total, closed map.
Behavior change: /sd:spec link X blocked-by Y now records depends-on.
Not yet verified by execution - validate is a prompt spec and there is no
.specs/ fixture to run it against. Seam 3 adds one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-4 seam 3, completing the ticket. Severity output. validate now reports through sd-severity-taxonomy with a stable rule table (SL001-SL054). BLOCK is reserved for a registry that lies about itself (its contents contradict each other, so list/stats and downstream agents read something untrue) or evidence that was fabricated (SL011). WARN is a real but recoverable problem that leaves the registry truthful. Findings cite file:line per sd-evidence-citation; a finding about something absent cites the line that creates the obligation rather than a bare directory, which that skill rejects. The command reads both SKILL.md files from disk at runtime. Only agents load skills via frontmatter, and the ticket forbids a subagent here, so runtime read is the only way to reuse the rules rather than fork them. Taxonomy anchors. BLOCK/WARN still requires an anchor, but the legal anchor now depends on the target: a constitution section or acceptance criterion for code, a lint rule ID for the .specs/ tree. The code row stays strict - a code finding with no section is still not a finding. Fixture. examples/spec-lint-fixture/ holds a clean tree that must report all-PASS and a seeded-broken tree covering 18 of the 26 rules, each violation self-documented with a SEEDED comment. The two perf specs are a matched pair guarding the seam-1 regression: the correct one (unfilled baseline at approved) must PASS, the fabricated one must BLOCK. Under the old rule those verdicts were exactly inverted. Also relaxes the seam-1 phase-token rule from verbatim match to a per-phase count. Verbatim would false-BLOCK an author who trimmed an example out of a token description; filling a field deletes the token, which the count still catches. Known limits, both documented in the fixture README rather than left to be discovered: the fixture is run by hand because the linter is a prompt and no script can execute it - automating it in CI would mean a second copy of the rules, the drift SW-1 and SW-3 exist to prevent. And 8 rules are unseeded, listed individually with reasons. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seed the six rules the fixture left unwritten (SL004, SL005+SL043, SL021, SL041, SL044), taking coverage from 18 of 26 rules to 24. The two that remain, SL001 and SL013, need the fixture or the engine install itself to be broken, so they need a corrupting harness rather than another seeded spec. The transition rules SL040-SL044 overlap enough that a linter can collapse several and still look correct, so each new seed is built to make exactly one fire and documents in-file which neighbours must stay silent - PERF-BROKEN-012 separates SL021 (retro exists but is empty) from SL043 (no retro at all), and REF-BROKEN-013 isolates SL041 behind two legal edges, a matching last entry and a present retro. Running the linter against the tree, rather than reasoning about it, surfaced two defects inspection had missed: - BUG-BROKEN-001, BUG-BROKEN-008 and RCA-BROKEN-005 undershot their template's phase-token count at draft, raising an SL011 the expected- findings table does not list. Resolved in favour of the strict count reading of SL011 by fixing the fixture, not the rule. - Placeholder tokens spelled out inside SEEDED comments are indistinguishable from real ones to a linter that scans line-wise, so the comments explaining the placeholder rules were themselves seeding phantom findings. Worst of these sat in PERF-BROKEN-002, half the matched pair guarding the seam-1 regression. This is the first end-to-end execution of the SW-4 acceptance criterion: clean/ returns all-PASS and broken/ surfaces its 25 listed findings at the stated severities. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jq's // operator treats an explicit JSON false as absent, so prompt-router, spec-gate, and subagent-retro ran even when disabled in project-config.json. Use an if/then/else jq expression instead.
Windows jq.exe emits CRLF for join("\n") output, leaving a trailing
\r on each protected path so exact-match comparison never succeeded
and protected paths were never blocked. Mirrors the existing CR strip
in prompt-router.sh's keyword loop.
Collapse .. and . segments in spec-gate.sh before comparing against paths.protected, matching spec-gate.ps1's GetFullPath resolution; add 3 conformance fixtures covering the traversal, a dot segment, and a benign non-protected case.
spec-gate.ps1 resolved a relative file_path against the process cwd instead of the payload's cwd, and GetFullPath preserved a trailing separator - both let an edit to a protected path slip through silently. ConvertTo-RelativePath now collapses dot segments with pure string processing mirroring spec-gate.sh's normalize_rel, closing both bypasses without depending on filesystem resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add /sd:verify to every command list, count claim, and usage-docs section; exclude the gitignored .superpowers/sdd/ task-brief scratch directory from the docClaims undeclared-claim scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FEAT-CLEAN-001 has no 02-tasks.md, so Step 2 (Covers lines) is skipped for this spec; 06-verify.md's task citations (T01/T02) are illustrative, matching the fixture's existing fictional file/path citations elsewhere (e.g. BUG-CLEAN-003). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scopes spec-gate Rule 0 to FEAT- rows only so bug/refactor/perf/rca close-outs no longer dead-end at VF002; adds an atomic-safe registry pre-check, an AC-evidence step in Phase 6, and PS ordinal-sort parity. Doc alignment across usage.md, project-config template, spec.md, and verify.md; two new conformance fixtures pin the scoping and the documented bundled-edit limitation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The scoping fix landed in the same unreleased branch as the gate itself, so a separate Fixed entry misread as a released regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SW-10 (FEAT-spec-metrics). spec-gate and subagent-retro now append metadata-only events to .specs/_metrics/events.jsonl, giving the retro loop (SW-7) its quantitative input. prompt-router stays read-only. Event kinds: gate (verify / protected / code-edit decisions), spec_transition (index.md lifecycle changes), subagent_stop. Privacy: the log records file extensions only, never paths, and no code content - the schema is shareable as-is. A metrics path that is rooted or escapes cwd via '..' is rejected, so the config value cannot become an arbitrary-write primitive. "Phase" is sourced from spec lifecycle status in index.md, not a command's internal Phase N, which hooks cannot observe. The transition scan is deliberately separate from the verify-gate extraction, whose FEAT-only narrowness is load-bearing. Every metrics failure path is a silent no-op, emitted after the gate decision is computed - metrics can never alter or block a decision. Gated by hooks.metrics.enabled, default true: existing installs begin writing on upgrade. No rotation in v1; the file grows unbounded. Conformance: harness captures events.jsonl and normalizes the wall-clock ts; all 46 existing goldens gained an events key, 6 metrics cases added. 52/52 pass, -SelfTest still detects an injected divergence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SW-17, part 1 of the closed learning loop (epic SW-7).
New sd-retro-lessons skill: a 10-tag enum derived from a mined corpus
of real retros rather than authored up front, the one-line lesson
record, and the abstraction discipline that turns a retro note into a
rule portable to another codebase. Two of the three tags proposed in
SW-7 were confirmed by that corpus; pattern-violation was retired as
overlapping sibling-repo-assumption and precedent-conflict.
New scripts/validate-lessons.{ps1,sh} enforce grammar, the closed
tag/severity/scope sets, a 120-char ceiling, and the privacy contract
(no paths, extensions, backticks, line citations, or Pascal/camel/
snake_case identifiers). They are separate from scripts/validate.*
on purpose: that one checks this repo's own invariants and specwright
has no .specs/ tree. These take a file argument and default to
.specs/_lessons/lessons.md in the current directory so a consumer repo
can run them directly.
Paired fixtures assert both directions in CI. The leaky fixture failing
is what proves the checks still fire; a validator rotted into a no-op
would report the clean fixture green forever.
No hook is modified. Aggregation (SW-18) and surfacing (SW-19) follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-20. Scenarios 2 and 3 planted their corruption by string-replacing the literal "**11 slash commands**". The repo ships 12, so the pattern matched nothing, the sandbox copy was never corrupted, the validator correctly passed, and the scenario reported THE CHECK DID NOT BITE. Scenario 2's setup guard could not catch this: it only checked that the planted text was present, and the planted value had since become the true value already in README.md, so it found the real line and passed vacuously. Scenario 3 had no guard at all. Both counts are now derived from disk and the corruption plants true + 1, which can never collide with reality. Both scenarios assert the transition rather than the destination and report a fixture-setup failure when the pattern does not match. Check 7 itself was never broken - only the proof that it still bites, absent since the 12th command landed on a branch CI has not run. A hardcoded count in the selftest was the last instance in the repo of the anti-pattern specwright.manifest.json exists to abolish. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-18, part 2 of the closed learning loop (epic SW-7).
scripts/aggregate-lessons.{ps1,sh} collect tagged lesson lines from
every <spec-dir>/*/05-retro.md, dedupe them, and render
<spec-dir>/_lessons/lessons.md. --check writes nothing and exits
non-zero on drift, which is how idempotence is asserted in CI.
Two decisions differ from the SW-18 description. The retros are
append-only and lessons.md is a derived file regenerated on every run:
the ticket called lessons.md itself append-only, but dedupe-with-a-count
requires rewriting the line, so append-only and idempotent are mutually
exclusive. And abstraction stays in the sd-retro-lessons skill, so the
aggregator makes no judgement calls and its output is reproducible.
Dedupe is on (tag, scope, normalised rule). A repeat adds a count and
never raises severity; the surviving wording is resolved independently
of severity so a sloppier phrasing cannot win by carrying a lower one.
All ordering is byte-wise - LC_ALL=C in bash, CompareOrdinal plus an
ordinal dictionary comparer in PowerShell, whose culture-aware defaults
would otherwise diverge - and PowerShell writes UTF-8 without BOM and
LF endings rather than going through Set-Content. A committed corpus
and expected output pin both implementations to the same bytes; the
corpus includes retros holding only transition lines, which must
contribute zero lessons, and an out-of-enum tag, which must be skipped.
No hook is modified. Surfacing (SW-19) follows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-18 acceptance requires both aggregator implementations to land on identical bytes. The repo had no .gitattributes, so with core.autocrlf=true - the Git default on Windows and on the windows-latest CI runner - expected-lessons.md would be checked out as CRLF while both implementations render LF, and the comparison would fail for a reason unrelated to the code under test. Scoped to tests/lessons/ only. Every .sh in the repo currently checks out as CRLF on Windows too; that is a wider change with a noisy first checkout and is tracked separately as SW-21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-19, part 3 and the close of the learning loop (epic SW-7).
subagent-retro.{ps1,sh} emit a <retro-lessons> block when a subagent
finishes work on an in-progress spec, gated by
hooks.subagentRetro.injectLessons and maxLessons.
Placement is load-bearing. The emit sits beside the existing metrics
call site, before the staleness early-exit and before the debounce
window. Moved down to the reminder block it would have surfaced lessons
only to users already behind on their retros - the population that
needs them least. The one gate it keeps is the in-progress-spec check,
and that gate is the relevance filter: the workflow type of the spec in
flight selects the scope, so there is no ranking, no scoring, and no
tie-break that could diverge between implementations. This replaces the
prompt-router placement named in the SW-7 epic.
Repetition is bounded per session, not by a clock. A new shownLessons
key in the hook state file records what has been surfaced, so
maxLessons caps how many NEW lessons appear at one stop and a session
converges to silence. A time debounce was rejected: it would suppress a
lesson the user has never seen because a different one was shown
recently.
Also fixed, both pre-existing and surfaced by the byte comparison:
subagent-retro.ps1 terminated its emitted block with WriteLine, which
appends CRLF on Windows and left its output one byte from the bash
twin's; and the bash state read did not strip the trailing CR that
Windows jq.exe emits, which made every already-shown comparison fail
and every lesson repeat forever.
Four conformance fixtures cover surfacing, scope filtering, pre-seeded
already-shown state and the disabled flag. Their goldens were derived
from an independent implementation of the selection rule rather than
from the hook output. The decision object captures lessons in emission
order - sorting would hide the selection-order divergence the fixtures
exist to catch.
Two further PowerShell/bash config divergences found while testing are
filed as SW-22 rather than folded in here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make Pattern refs mandatory on every atomic task and give validate a tolerant field-label grammar so it can read task-block content without false-failing the live corpus (canonical labels have 0 occurrences). - sd-atomic-task-format: add Field label grammar section; 11 required fields; Pattern refs required on every task, explicit `none` allowed. - commands/spec.md: SL060 (WARN) for a task block with no Pattern refs field; reserve SL061-SL069 for task-block content rules; add Task-block checks section. - sd-pattern-discipline + reviewer: missing field is a spec-authoring defect reported as SL060 (WARN), not a code defect / BLOCK. - implementer/spec-architect/README/architecture/feature/refactor: field count -> 11, including Pattern refs. - docs/adr/0001: record that validate may now parse artifact content; WARN decision, reserved band, Context refs rename declined. - tests/task-format/fixtures: conformance fixtures for the grammar (canonical, plain, colon-inside-emphasis, missing negative case); pin to LF via .gitattributes. No runner yet (documented in ADR). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The architect self-assesses whole-spec complexity; above threshold a HARD gate forces decomposition into medium child specs instead of one oversized plan. Directly addresses external-user feedback that spec quality degrades non-linearly on complex, multi-subsystem work. - feature.template + sd-spec-templates: spec-level `complexity: S|M|L` frontmatter (create-time estimate + rationale), distinct from a task's `Estimated complexity`; rubric, and decompose thresholds (> 8 tasks, > 2 production layers excl. Tests/Config, > 8 files, open question). Thresholds set from the asian-sportsbook-v2 corpus canyon. - spec-architect: Mode create emits the field; Mode plan measures the plan and returns a decompose-proposal or no-split flag via needs-input. Tasks counted with the tolerant sd-atomic-task-format grammar, not naive regex. - commands/feature.md: Gate 2 gains a conditional HARD Gate Complexity face (over-threshold -> split into FEAT-<arg>-<slug> children via /sd:spec link spawns + depends-on; parent -> immutable archived umbrella). Under threshold = normal plan approval, zero added friction. Create-time L also escalates explorer -> sonnet and architect -> opus (aliases only). - docs/adr/0002: record the decision; SW-4 owns linting the field + split integrity (deferred). README/usage/CHANGELOG updated; still 3 hard gates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FEAT-adaptive-replan: a gated, append-only mid-execution re-plan path so the Planning pattern's adaptivity never violates spec immutability or skips a gate. - skills/sd-replan-loop: HARD Gate Re-plan, append-only ## Revisions log in 01-plan.md, Revised-by task marker. Shared by /sd:feature and /sd:refactor (read at runtime) so the format is defined once, not a 12th command. - Gate Re-plan wired into feature.md (Phase 4 execute + Phase 5 review) and refactor.md (Phase 5 + Phase 6). Conditional like Gate Complexity, so the hard-gate counts (3 / 6) are unchanged; never re-plans a done spec. - spec-architect TASK=plan gains REPLAN_SCOPE/REVISION (no new mode). - Revised-by conditional field in sd-atomic-task-format. - SL070-073 revision-log integrity band in /sd:spec validate (BLOCK x3 + WARN), cross-checking the log against Revised-by markers; runs only when a record exists. Honest boundary: a static linter cannot diff-detect an unmarked edit. - Scope corrected from the ticket on corpus evidence: bug/rca have no task list and perf has its own revert loop, so only feature+refactor qualify. - ADR 0003, doc counts 7->8 skills, LF-pinned cross-artifact fixtures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bound the metrics log: a new hooks.metrics.maxSizeKb (default 1024 KB) rolls .specs/_metrics/events.jsonl to events.jsonl.1 once the live file reaches the cap, single generation. Added to all four metrics writers (spec-gate + subagent-retro, PowerShell + bash) so both platforms roll at the same raw-byte boundary ((Get-Item).Length / wc -c). Inherits every SW-10 invariant: rotation is best-effort and never stops the append (a silent stop reads as "metrics working" while dropping data), a failed roll (locked file, read-only dir) is a silent no-op, and it never alters a gate decision or exit code. Absent maxSizeKb is treated as 1024 so pre-SW-15 configs stay bounded with no edit; explicit 0/negative disables, and any non-number is invalid and also disables (SW-22 type-strictness). events.jsonl.1 is a grace buffer, not a read contract - there is no consumer of the log today. Docs (architecture, troubleshooting) updated; four conformance fixtures prove PS and bash rotate identically, and the harness now verifies the rolled .1 preserves the old lines byte-for-byte and that no non-rotation case leaves a .1 behind. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SW-10 accumulated .specs/_metrics/events.jsonl with no consumer. Adds a read-only 13th command that summarises the live log plus .specs/index.md: in-progress specs, gate activity by kind and decision, lifecycle transitions, and a friction section ranking where work is actually stuck. jq is an oracle, not a runtime dependency: the schema is flat and written in fixed key order so substring counting is deterministic, and jq aborts on a partially-written line - which would lose the whole report to one interrupted append, exactly what the ticket forbids. Every degrade path is a labelled state (ST001-ST005); malformed lines are skipped and counted, never silently dropped. Reads the live file only - events.jsonl.1 stays a grace buffer per SW-15. Also fixes the architecture.md description of the metrics `stale` field (a 0/1 flag, not a count - found by reading the writer) and a stale "seven reusable skills" claim in README that Check 7 cannot see because spelled-out numbers escape claimPhrases. Verification corpus at tests/metrics/ - manual by design: status.md is a prompt file and CI cannot execute it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Check 7 guards published inventory counts against disk, but its detection vocabulary could not see three whole classes of claim - and each class had let a real, wrong number sit in a tracked doc through many green runs. 1. Spelled-out numbers. Every pattern was anchored on [0-9]+, so README's intro line said "seven reusable skills" from the moment an eighth shipped in SW-17, through every 7/7 green run since. 2. Capitalisation. A lowercase word alternation is not enough: a spelled out count in prose is usually sentence-initial, so "Three hooks ship in cross-platform pairs" escapes it. POSIX ERE has no inline case flag, so each word carries an explicit [Tt] class rather than a flag only .NET supports. 3. Bare nouns. Only decorated forms were listed, so "Five commands invoke no subagent" matched nothing - a line SW-16 added one commit ago, while filing the ticket about invisible claims. 4 real claims surfaced across the tracked tree, 0 false positives. Resolved under a policy now recorded in the manifest: a number derivable from an area is written in digits and declared; a number nothing derives is removed and the names carry the meaning. Both untracked counts already listed every item by name. selftest-docs grows 4 -> 6 scenarios, one per escape, kept separate so a partial fix cannot pass. Verified by sabotage in both directions, not by reading green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a repo-root .gitattributes with `* text=auto` and `*.sh`/`*.ps1` pinned to `eol=lf`. Without it, core.autocrlf=true checks scripts out as CRLF on Windows, putting a trailing CR on `#!/usr/bin/env bash` shebangs (bad interpreter) and mis-parsing heredocs and `[[ ... ]]`. The first checkout after this lands renormalizes line endings in existing Windows working trees - a one-time large diff, not a change. SW-21 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…low/v1 # Conflicts: # .gitattributes # CHANGELOG.md
Make four PowerShell hook config reads type-strict so they stop diverging from their bash twins on the same project-config.json. An absent enabled key was disabling subagent-retro, spec-gate, and prompt-router in PowerShell only (-not $null / [bool]$null); an explicit 0 for retroStaleMinutes/debounceMinutes was ignored as falsy. Bash already used == false and // N, so no .sh changes - the fix converges the pair. Add five conformance fixtures that omit enabled or set a numeric option to 0; each fails if its read is reverted, which the prior fixture set could not. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The four subagent-retro lessons-* conformance fixtures expected a fresh retro but shipped no setup.json, so the harness kept 05-retro.md at its on-disk mtime and the cases failed on any checkout older than retroStaleMinutes (30). Add a setup.json touching each retro to ageMinutes 5, mirroring remind-stale-retro. Test-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- validate.sh Check 7: replace declare -A (bash 4+) with bash 3.2-safe parallel-array lookups so macOS's stock /bin/bash doesn't crash. - ci.yml: reset LASTEXITCODE after the lesson validator's intentional non-zero assertion, since Actions appends its own exit-code check. - run-conformance.ps1: catch broken-pipe on stdin write/close for stub processes that exit without reading input.
declare -A was fixed but Check 7 also used mapfile, which macOS's stock /bin/bash 3.2 doesn't have either. Replaced with a while-read loop into a plain array.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch is a large hardening/extension pass on specwright's engine (commands, hooks, skills,
templates) developed across SW-3 through SW-24. Highlights from CHANGELOG.md's Unreleased section:
/sd:status(read-only metrics/friction reader),/sd:verify(SC/ACtraceability gate).
..traversal fixes inspec-gate(bash + PowerShell), complexity triage + forced decomposition in/sd:feature(Gate Complexity), a sanctioned mid-execution re-plan loop (Gate Re-plan) for
/sd:feature//sd:refactor..specs/_metrics/events.jsonl, a lessonaggregator (
scripts/aggregate-lessons.*) that dedupes retro lessons into_lessons/lessons.md,and lesson surfacing in
subagent-retrogated by session state rather than a clock./sd:spec validatelinter: new SL060/SL070-SL073 rule bands (pattern-refs presence,revision-log integrity), fixed drifted required-field and placeholder rules, and a
linked_specsfrontmatter field replacing an older mechanism.lesson-aggregator fixtures pinned to LF via
.gitattributesso PowerShell and bashimplementations are asserted byte-identical.
subagent-retro.ps1output, debounce state-file contract fixes, stale doc corrections.See
CHANGELOG.md(Unreleased section) for the complete, itemized list with rationale for eachnon-obvious decision, and
docs/adr/0001-0003for the three ADRs backing the largest designchoices (validate parses task content, complexity-triage decomposition, adaptive re-plan loop).
Test plan
.\install\install.ps1 -DryRun— preview install planinstall.ps1 -BasePath/uninstall.ps1 -Force)bash -n hooks/bash/*.sh— syntax checkgrep -nP "[^\x00-\x7F]" hooks/powershell/*.ps1 install/*.ps1— ASCII check (no output)tests/hooks/run-conformance.ps1— PS/bash hook conformance fixtures🤖 Generated with Claude Code