Skip to content

Optimize spec-driven workflow: gates, metrics, lessons, complexity triage - #23

Merged
developzoneio merged 50 commits into
mainfrom
feature/optimize-workflow/v1
Jul 23, 2026
Merged

Optimize spec-driven workflow: gates, metrics, lessons, complexity triage#23
developzoneio merged 50 commits into
mainfrom
feature/optimize-workflow/v1

Conversation

@developzoneio

Copy link
Copy Markdown
Owner

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:

  • New commands: /sd:status (read-only metrics/friction reader), /sd:verify (SC/AC
    traceability gate).
  • Gate hardening: case-insensitive protected-path matching, .. traversal fixes in
    spec-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.
  • Metrics + lessons loop: size-capped/rotated .specs/_metrics/events.jsonl, a lesson
    aggregator (scripts/aggregate-lessons.*) that dedupes retro lessons into _lessons/lessons.md,
    and lesson surfacing in subagent-retro gated by session state rather than a clock.
  • /sd:spec validate linter: new SL060/SL070-SL073 rule bands (pattern-refs presence,
    revision-log integrity), fixed drifted required-field and placeholder rules, and a
    linked_specs frontmatter field replacing an older mechanism.
  • Cross-platform conformance: hook fixtures, task-format fixtures, revision-log fixtures, and
    lesson-aggregator fixtures pinned to LF via .gitattributes so PowerShell and bash
    implementations are asserted byte-identical.
  • Several correctness fixes: PowerShell truthiness in hook config reads (SW-22), CRLF handling in
    subagent-retro.ps1 output, debounce state-file contract fixes, stale doc corrections.

See CHANGELOG.md (Unreleased section) for the complete, itemized list with rationale for each
non-obvious decision, and docs/adr/0001-0003 for the three ADRs backing the largest design
choices (validate parses task content, complexity-triage decomposition, adaptive re-plan loop).

Test plan

  • .\install\install.ps1 -DryRun — preview install plan
  • Sandbox install/uninstall round-trip (install.ps1 -BasePath / uninstall.ps1 -Force)
  • bash -n hooks/bash/*.sh — syntax check
  • grep -nP "[^\x00-\x7F]" hooks/powershell/*.ps1 install/*.ps1 — ASCII check (no output)
  • tests/hooks/run-conformance.ps1 — PS/bash hook conformance fixtures
  • Metrics, lessons, task-format, and revision-log fixture corpora (documented, manually verified)

🤖 Generated with Claude Code

developzoneio and others added 30 commits July 17, 2026 16:10
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>
developzoneio and others added 20 commits July 21, 2026 11:05
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.
@developzoneio
developzoneio merged commit 975882d into main Jul 23, 2026
6 checks passed
@developzoneio
developzoneio deleted the feature/optimize-workflow/v1 branch July 28, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant