claude-sprint-gate
v9.10.1
Published
Claude Code Sprint Gate — autonomous sprint lifecycle manager with pluggable scorecards. v8.3 introduces multi-agent mode foundation — master is the default role; /sprint-role slash command + role file mechanism + per-session NATO nickname auto-assignment
Downloads
5,046
Maintainers
Readme
CCSG — Claude Code Sprint Gate
Current version: 9.10.1 · npm · GitHub
What's new — v9.10.1: review-sprint detection regex tighten (hotfix)
Dogfood-surfaced false-positive in the v9.10.0 review-sprint detection. The fallback filename heuristic at plugin/hooks/ccsg.sh:311 matched any sprint slug containing the word "review" (case-insensitive) — including v9.10.0's own ship sprint about the reviewer feature. The Stop hook then demanded a review_status on what was actually a normal feature-ship sprint.
Fix. Tightened the filename fallback regex from grep -qi 'review' to grep -qE 'review-correction-for' — the canonical infix produced by ccsg.sh's review-sprint generator (sprint-S<N>-review-correction-for-S<SOURCE>-<slug>.md). The type: review frontmatter check remains the primary signal and is unchanged; hand-authored review sprints with non-canonical filenames should declare it explicitly.
Impact.
- Generator-produced review sprints: identical detection (still match by canonical filename + frontmatter).
- Hand-authored review sprints: identical if frontmatter set; over-broad slug matching no longer trips false-positives.
- Non-review sprints with "review" in the slug: bug → fixed.
No new features, no test surface changes, no behavior change for any other code path. 59/59 integration tests still GREEN.
What's new — v9.10.0: reviewer-role parity with CSG (Claude/Codex cross-tool collaboration)
CCSG and Codex Sprint Gate (CSG) now share a common reviewer-role contract over the same sprint-docs/.ccsg/ runtime state. The goal: a Claude session executes a sprint, a Codex session reviews it (or vice versa), with hard-gated separation between execution and audit even across tool boundaries. Existing CCSG functionality preserved bit-for-bit; reviewer mechanics are additive and signal-gated.
Reviewer role — first-class third role alongside master/worker:
- Audit context, not development context. Cannot create execution claims, write product code, or modify development sprint files.
- Uses separate claim namespace
sprint-docs/.ccsg/review-claims/(NOTsprint-docs/.ccsg/claims/). Hard-gated byclaim-guard.shreviewer-lane gates. - Source sprint creator/executor blocked from reviewing that source. Feedback author blocked from fixing the resulting correction sprint. Both gates enforced by
claim-guard.sh. - Reviewer write-lane: only to
sprint-docs/.ccsg/review-claims/*.lockand claimedsprint-open/*review*.md. Enforced bysprint-ownership-guard.shreviewer recognition. - New scheduler token
CCSG_ARM_REVIEWERjoins the v9.9.2 four-token model (sentinel-only, no master claim).prompt-guard.shalso acceptsCSG_*token prefixes for cross-tool compatibility in mixed CCSG/CSG projects. - New slash command
/sprint-reviewfor the reviewer claim + feedback workflow./sprint-roleacceptsreviewer./sprint-start --role=reviewerarms reviewer-flavored loop withCCSG_ARM_REVIEWERtoken.
Review sprint lifecycle in ccsg.sh (signal-gated, opt-in):
When a verification sprint completes, CCSG checks for ANY reviewer signal:
sprint-docs/.ccsg/reviewer-requiredflag file exists- Registered
agents/*.jsonwithrole: reviewer - Source sprint frontmatter
review_required: true
If ANY signal: CCSG generates a pending review sprint at sprint-open/sprint-S<NEW>-review-correction-for-S<SOURCE>-<slug>.md with full frontmatter (type: review, review_status: pending, source creator/executor session_ids). Stop is blocked until a reviewer claims and resolves. No signal → no review sprint created — solo and master/worker projects with no reviewer signal see ZERO behavior change from v9.9.x.
State machine: pending → pass (archive + release reviewer claim) | findings (handoff to master/worker for correction; reviewer can stop with findings even if review-checklist incomplete — findings IS the handoff signal) → correction-ready (re-review required by separate reviewer session).
Hook coverage drift remedy:
CCSG hook files sprint-ownership-guard.sh, feature-charter-guard.sh, evidence-resolver.sh, scenario-witness-guard.sh existed but were missing from PreToolUse matcher registrations. v9.10 closes the drift:
Edit/Write/MultiEditmatchers now also firesprint-ownership-guard.sh+feature-charter-guard.shBashmatcher now also firesevidence-resolver.sh+scenario-witness-guard.sh- (
scenario-witness-runner.shstays as command-style runner for/sprint-witness, NOT registered as PreToolUse hook)
deploy.sh, bin/ccsg.js, plugin/scripts/install-hooks.py, and plugin/hooks/hooks.json all updated to register these in the canonical matchers.
Library additions (plugin/lib/ccsg_agents.py):
_review_claims_dir(root)— helper for.ccsg/review-claims/path resolution_read_front_matter(path)— minimal YAML frontmatter reader for review-sprint type/status detectionfind_claimable_review_sprint(root, session_id)— reviewer scan with source/feedback-author exclusiontry_atomic_review_claim(root, review_sprint_n, source_sprint_ref, session_id, nickname)— atomic claim withO_EXCLrelease_review_claim(root, review_sprint_n, session_id)— release owned-by-session- CLI dispatches:
find-review-claimable,review-claim,release-review
Cross-tool collaboration substrate:
CCSG and CSG share sprint-docs/.ccsg/ runtime state. Reviewer-role mechanics enforce separation-of-concerns regardless of which tool armed which role. A typical cross-tool workflow:
- Claude (CCSG) session armed as master → executes sprint S
- Reviewer signal active (
reviewer-requiredfile or registered Codex reviewer agent) - CCSG
ccsg.shcreates pending review sprint when verify archives - Codex (CSG) session armed as reviewer → claims review, audits source/proof/verify, writes pass/findings
- If findings: master (Claude or Codex) corrects, sets
correction-ready, separate reviewer re-reviews
Token compatibility: prompt-guard.sh accepts both CCSG_* and CSG_* token prefixes. CCSG-generated prompts always use CCSG_*; CSG-generated prompts use CSG_*; both sides recognize both for cross-tool tick continuity.
Preservation guarantees (production-critical):
- All v9.9.2 token-arm semantics preserved:
CCSG_ARM_SESSION(master claim),CCSG_ARM_WORKER/CCSG_TICK_SESSION(sentinel-only). NewCCSG_ARM_REVIEWERjoins as fourth sentinel-only token. - All v9.9.2 ownership-verification semantics preserved on
/sprint-stop. - All v9.9.0 anti-deflection / operator-input-as-directive doctrine preserved.
- Master/worker workflows are bit-for-bit identical to v9.9.2 in all projects without reviewer signals.
- Newly-registered hooks (
sprint-ownership-guard,feature-charter-guard,evidence-resolver,scenario-witness-guard) ALREADY default to passthrough when their preconditions don't apply — silent no-ops for non-reviewer workflows.
What's new — v9.9.2: hotfix — master-claim session-binding + tick self-recognition
Empirical incident 2026-05-06: a cron-fired loop-tick session hijacked master role from the active master, then a subsequent operator /sprint-stop in the tick session cascadingly nuked the prior master's state files (cron cancelled, .ccsg-active-session deleted, role file deleted, agent file deleted). Root cause: the v9.7 loop-prompt template carried the CCSG_ARM_SESSION token (the same token /sprint-start uses for master claim), so every cron-fired tick session triggered loop-detect.sh's master-claim logic and overwrote .ccsg-active-session with its own session_id. Combined with /sprint-stop master-path keying off the role file (not authoritative session), any tick session that briefly held master could nuke the real master's state.
v9.9.2 fixes the bug at three layers:
- New token:
CCSG_TICK_SESSION— used by the loop-prompt template (cron + ScheduleWakeup re-fires) instead ofCCSG_ARM_SESSION.loop-detect.shrecognizes the new token as sentinel-only — it does NOT claim.ccsg-active-session, does NOT overwrite master ownership. Master role belongs only to whoever last did/sprint-start. Three-token model now:CCSG_ARM_SESSION(operator-explicit master claim),CCSG_ARM_WORKER(worker, sentinel-only),CCSG_TICK_SESSION(loop tick, sentinel-only). - TICK PRE-FLIGHT in the loop prompt — the Step 2 loop-prompt template now opens with a probe-and-compare block that probes the tick session's
session_id(via the v8.4.11 nonce mechanism), readssprint-docs/.ccsg-active-session, and exits cleanly withSTALE_TICK:if the tick session is NOT the current master. Stale ticks from superseded masters self-disqualify; they don't even attempt sprint planning. No token waste, no accidental work, no opportunity for cascading state damage. /sprint-stopmaster-path ownership verification — before any destructive op (cron cancel, role file delete,.ccsg-active-sessionremoval, per-agent file delete),/sprint-stopverifies the calling session_id matches.ccsg-active-session. Mismatch → REFUSED with explicit error message naming the actual current master. Prevents the hijack-cascade pattern where a stale role file lets a superseded session nuke the live master's state./sprint-startsemantics preserved — operator's explicit/sprint-startinvocation still claims master and overwrites any prior claim unconditionally (per operator directive: "a new agent should claim master by default and overwrite the last master agent-id"). The fix is targeted at AUTOMATED tick sessions; operator-explicit commands are honored.- Backward-compatible —
prompt-guard.shaccepts all three tokens; existing in-flightCronCreatejobs that still carryCCSG_ARM_SESSION(legacy v9.7-v9.9.1 prompt templates) continue to fire without breaking. Their tick sessions will trigger the v8.4.3 master-claim path, but the new ownership-verification in/sprint-stopand the existing sprint-ownership-guard prevent damage. Within 7 days (cron auto-expiry) all in-flight legacy crons expire and the new template takes over fully. - Version + marker bumped 9.9.1 → 9.9.2. No new hooks added; no
deploy.shchanges required. Three files touched inplugin/:loop-detect.sh,prompt-guard.sh,commands/sprint-start.mdStep 2,commands/sprint-stop.mdStep 0.5.
Why ship this hotfix immediately: the bug actively corrupts master state on every operator's machine that runs a cron-tick + /sprint-stop sequence. The empirical incident produced 4+ minutes of degraded enforcement and required manual recovery (clear stop flag + re-arm). Without the fix, every long-running CCSG-armed project is exposed.
What's new — v9.9.1: hotfix — ccsg --repair-hooks for settings.json restore
Empirical incident on 2026-05-05: a third-party agent-harness plugin's install-hooks step overwrote the PreToolUse Edit|Write|MultiEdit|Bash matcher block in ~/.claude/settings.json, dropping every CCSG hook (claim-guard, sprint-first-guard, spec-guard, loop-detect, prompt-guard, verify-structure-guard, custom-mode-citation-guard, safe-push-guard, storyrun-discipline-guard) silently. Active CCSG-armed sessions kept running but with the entire enforcement layer dead — no sprint-first BLOCK, no claim-ownership BLOCK, no verify-doc structure check, no probe-nonce response. Master sessions could /sprint-start and appear to arm but .ccsg-active-session would never get written because the hook that responds to the CCSG_ARM_SESSION token wasn't registered.
v9.9.1 ships a recovery path:
- New:
plugin/scripts/install-hooks.py— idempotent merge installer. Reads existing~/.claude/settings.json, adds CCSG's required hook entries to PreToolUse / UserPromptSubmit / Stop, preserves every non-CCSG entry (other harness plugins, custom user hooks). Skips duplicates by hook-filename match. Safe to run repeatedly. - New:
ccsg --repair-hooks— operator-facing wrapper command. Invokes the merge installer with sane defaults. Optional--dry-runflag previews the diff without writing. Run after any time another tool's installer might have stomped on CCSG's hooks. - Backup before write — every merge run creates
<settings>.pre-ccsg-merge-backupso any operator can roll back. - Version + marker bumped 9.9.0 → 9.9.1. Block-template teaching unchanged from v9.9.0; this is purely a recovery / install-resilience patch.
Why ship this: in a multi-tool agent ecosystem (Claude Code + CCSG + goldmem + Cursor + …), settings.json is shared state that every tool's installer touches. Tools that overwrite matchers instead of merging will silently disable other tools. CCSG can't prevent third-party tools from doing this, but it CAN provide a one-command recovery path so the operator's reaction is ccsg --repair-hooks instead of "edit the JSON by hand."
What's new — v9.9.0: operator-weight-inversion strip
Empirical evidence from a 2026-05-03/04 cold-start session (Pulscale UI/UX migration arc + goldmem standalone bootstrap, ~13 hours of operator wall-clock) showed CCSG was treating operator-typed slash commands and stated directives as conversation openers rather than as directives. Six instances of the same failure pattern surfaced in one day; the operator had to re-state the same directive multiple times to override it.
v9.9 strips the deflection across the framework:
/sprint-startScenario B auto-chains/sprint-setup— when CCSG infrastructure is missing, the framework runs setup automatically as the first phase of/sprint-startexecution, then continues to arming. The "Path A (full setup) vs Path B (skip CCSG)" menu is removed; the slash command IS the directive./sprint-startSteps 3 + 4 are now mandatory —ScheduleWakeup+CronCreatefire as part of arm completion. Agent must not ask "should I fire the loop?" after operator already invoked the command that fires it. If either tool call fails, agent reports the specific error and fails loudly, never offering "would you like to retry / skip / fire only the cron."/sprint-setupPhase 0 — Absorb existing requirements doc — when the project already hasSPEC.md/PRD.md/ISSUES.md/REQUIREMENTS.md/ any*.mdwith## Requirements/## Goals/## Mission/## User Stories/## Out of Scope/## Stop Conditionstyle headings, derivemission.md/strategic-intents.md/stop-condition.mdfrom it with one confirmation prompt — instead of running a from-scratch interview that re-extracts content the operator already wrote down. From-scratch interview is now the fallback only when no absorbable doc exists or operator declines absorption.- Past-version banner stamps stripped from user-facing hook output —
CCSG v7.0 ADVISORY (non-blocking)→CCSG ADVISORY (non-blocking);CCSG ARCHIVE-CLEANLINESS (v9.0 ADVISE)→CCSG ARCHIVE-CLEANLINESS (advisory); etc. The framework is at v9.9 — banners that announce themselves as "v7.0" feature versions erode trust by suggesting outdated framework. The live framework version stamp inclaude-md-block-template.mdBEGIN marker stays — that's the anti-stacking marker. - Past-version annotations stripped from command-doc headings —
## Invocation: /sprint-feature --amend FT<NN> (v7.7.0)→## Invocation: /sprint-feature --amend FT<NN>;(v8.4.1 LOAD-BEARING)→(LOAD-BEARING); etc. The agent reads command docs as instruction; "(v7.7.0)" history annotations contribute nothing to current execution and look like outdated guidance. - New CLAUDE.md block teaching: "Operator input is the highest-priority signal — never re-extract via deflection" — explicit doctrine documenting the failure pattern (Path A/B menus, "would you like" questions, "Recommendation:" blocks re-framing executed directives) and the principle that replaces it (the slash command is the directive; the framework executes; halt only with a single named question, never a menu).
Why it matters: the operator is one, finite, irreplaceable. The agent is fungible, scales with compute. Every menu the agent offers extracts operator life-minutes. The framework's job is to convert operator input into agent constraint mechanically, never to ask for the same answer twice.
Not in this ship (deferred to v9.10): heartbeat side-channel corrupt-file recovery, .ccsg-active-session auto-claim leak fix, regression test for v9.8.1 hotfix, per-agent-file current_phase updates, ccsg skills doctor command. v9.9 is purposely thematic — one root cause, six surface fixes.
What's new — v9.8.0: block compaction (version archaeology stripped, duplicates removed)
User caught a regression: the deployed CCSG block in CLAUDE.md was 166k chars (4× the Claude Code 40k performance-warning threshold) — and tonight's v9.4-v9.7 ships ADDED ~95 lines without extracting the duplicates the glossary system was supposed to remove. The block grew when it should have shrunk.
v9.8 does the extraction pass:
1. Verbatim duplicate removed. The Visual Verification Depth Standard section had ~46 lines duplicated VERBATIM (per-work-type minimums + "no observable surface" rejection + "why this exists" — all appeared twice). One copy retained.
2. Version archaeology stripped. ~72 instances of (vX.Y — LOAD-BEARING) style parentheticals in headers, plus inline narrative version mentions ("v7.8.0 introduced...", "v8.6 replaced...", etc.) — all pure historical reference, useless in the deployed block. Deployed CCSG block only needs to know "this is v" via the marker line; in-content version archaeology is dead weight.
3. Implementation code blocks elided. A 22-line React/JSX implementation example for the capture-mode URL banner pattern compacted to a 1-sentence description. Implementation details belong in project skills/docs, not framework block.
4. Historical-justification narrative cut. "Why this earns a major version", "Migration from v7.9.x", "Pre-v8.5 sprints produced..." paragraphs removed — these documented WHEN the rule changed, not WHAT the rule is.
Honest framing: ~10k chars cut (~6% reduction). Far short of the 50% target proposed earlier — the block is mostly LOAD-BEARING teaching that can't safely be removed without architectural redesign (lazy-loadable extension files, auto-skill activation, etc., for v10.0). The 6% cut is the safe extraction; further reduction needs structural change, not just stripping.
v9.8 still doesn't get the block under 40k chars. That's a v10.0 architectural problem, not a v9.x compaction problem. v9.8 makes the block ~6% smaller; v10.0 will require splitting the block into core + lazy-loaded extensions.
Tests: 40/40 GREEN. All 10 spot-checked load-bearing rules confirmed surviving cuts (CCSG_ARM_SESSION, Phase 1, Gate 18, Three-pillar scoring, fanout, honesty drops, post-cleanup, Try harder, byte-identical to goal, etc.).
What's new — v9.7.0: minimal loop-tick prompt (fixes the copy-paste flood regression)
Pre-v9.7 /sprint-start.md taught master to copy a ~2,000-char multi-paragraph instruction block VERBATIM into every ScheduleWakeup + CronCreate call. The "COPY VERBATIM" rule was load-bearing for the CCSG_ARM_SESSION token (prompt-guard requires it), but the rest of the block — WHAT-TO-DO-NOW step list, mode-driven planning protocol, intent-filter walk, CUSTOM-mode arc resolution, AT-END-OF-TURN protocol — accumulated over versions because each new teaching seemed safer to embed than to trust to CLAUDE.md being read.
Result: every wakeup fire echoed ~2,000 chars as <command-args> in the user-facing turn. Operator chat steering only added to it (CURRENT STATE / OPEN OPTIONS observed accumulating to ~2,400 chars across this session) — copy-paste flood every 4-5 minutes.
v9.7 fixes this:
1. /sprint-start.md Step 2 — minimal loop prompt (~500 chars). Required content:
- ARM bash block (preserves
CCSG_ARM_SESSIONtoken; satisfies prompt-guard) - One-line directive: "Continue per CCSG block teaching in CLAUDE.md"
- End-of-turn protocol (stop check + ScheduleWakeup forward)
Everything else — WHAT-TO-DO-NOW, mode protocol, MATURITY/USECASE/CUSTOM definitions — moved to the CCSG block teaching, which is auto-loaded from CLAUDE.md at every session start.
2. CCSG block — new "Loop-tick discipline (v9.7 — LOAD-BEARING)" section. Codifies:
- The wakeup prompt is sacred; never augment with state
- State lives in files (
.active-scorecard,chain-queue.json,sprint-open/,.next-decision) - Operator chat steering becomes a memory rule (S1057 template), not prompt augmentation
- Why the runtime
<<autonomous-loop-dynamic>>sentinel does NOT apply (sentinel expansion would loseCCSG_ARM_SESSION; prompt-guard would then block) - Recovery for bloated prompts mid-session:
/sprint-stop+/sprint-start
3. Note on the runtime sentinel. The Claude Code harness has a <<autonomous-loop-dynamic>> sentinel for /loop autonomous mode. It does NOT work for CCSG because runtime expansion drops CCSG_ARM_SESSION and prompt-guard blocks the call. CCSG's own minimal prompt (with ARM token preserved) is the equivalent solution.
Adoption (existing master sessions running on bloated v8.x-v9.6 prompts):
- Old loops continue with bloated prompts until
/sprint-stop+/sprint-start - After re-arm, master picks up the v9.7 minimal prompt template
- For Pulscale specifically:
/sprint-stop(master alpha), then/sprint-start <roadmap>to re-arm with v9.7
Tests: 39/39 GREEN (38 prior + 1 new asserting /sprint-start.md Step 2 prompt block is < 800 chars and contains CCSG_ARM_SESSION).
What's new — v9.6.0: proof depth signals (advisory hook + canonical reference)
Empirical analysis across the project's sprint-proof history surfaced a real degradation curve:
Era CCSG ver PNGs Total KB Pattern
─────────────────────────────────────────────────────────────────
Apr 21-25 v7.2.3 7-45 2-7 MB story-UC-<section>-<persona>-<variant>-<element>.png
multi-format (PNG + JSON + DER + TXT + MP4)
single-spec-many-phases
Apr 28+ v8.0+ 0-1 5-137 KB collapse — 1 PNG or text-only
May 2-3 v9.0-9.5 5-7 1.4-1.8MB partial recovery, no naming conventionThe v8.x rollout doubled CCSG block size (multi-agent foundation, claim discipline, archive cleanliness, 18 gates, three modes, glossary system) and the storyrun depth discipline got crowded out. By v9.5 master was capturing PNGs but applying the depth standard loosely — sub-50KB viewports, no light/dark variance, no storyrun naming, captions that didn't match PNG content.
v9.6 closes the loop with advisory mechanical signals that direct the LLM to self-correct:
1. depth-validation.sh (NEW Stop hook, ADVISORY). Scans recently-modified sprint-proof/sprint-N/ directories and surfaces named gaps:
- Light/dark pair file-size similarity > 95% → "theme may not be implemented for the captured surface"
- PNG file size < 50KB on UI routes → "clipped or sparse?"
- PNG count vs work-type — storyrun rows expect >= 8 PNGs, UX proof >= 5
- Missing
story-<UC>-<section>-...filenames when storyrun row cited - SHA-duplicate PNGs → "padded duplicate; captures must show different DOM state"
Always exits 0. Output is informational. The LLM reads, decides whether to re-shoot, mark partially-met, file a follow-up sprint, or override with reasoning written into the verify doc.
2. CCSG block: canonical proof-shape reference. New section "Proof depth — canonical reference" pointing at sprint-proof/sprint-554/ as the depth target. Codifies:
- Filename convention:
story-<UC>-<section>-<persona>-<variant>-step-NN-<element|full|clip>.png - Multi-format expectation (PNG + JSON + DER + TXT + MP4 + webm)
- Single Playwright spec covering all phases
- Inline-transcription rule for verify docs (read PNG before grading; transcribe representative content)
- Honest-failure framing — PNG capturing a known bug = honest evidence, not failure; mark partially-met + file follow-up, don't false-PASS around the bug
3. Design rule codified (saved memory). All quality dimensions are ADVISORY hooks. BLOCKING is reserved for binary-verifiable mandatory things (sprint file on disk, claim ownership). Quality is multi-dimensional and judgment-dependent — mechanical pass/fail kills the LLM's ability to apply context. Pattern matches v9.5 glossary-lint exactly.
Adoption: npx claude-sprint-gate installs the new hook + registers it in the Stop chain (after glossary-lint). Existing global installs need a manual settings.json append OR a re-run of npx claude-sprint-gate. Pulscale's CLAUDE.md picks up the v9.6 block at next /sprint-model-rebuild or via direct block swap.
Tests: 37/37 GREEN (33 prior + 4 new for v9.6).
What's new — v9.5.0: glossary system complete (auto-create + auto-grow + lint hook)
v9.4 introduced the glossary convention. v9.5 closes it out end-to-end:
1. Auto-create on setup. npx claude-sprint-gate now caches glossary-ccsg-template.md + glossary-project-template.md at sprint-docs/.ccsg/. The new /sprint-setup Phase 6.6 copies both to repo root if missing — glossary-ccsg.md (framework vocabulary, ready-to-use) + glossary-project.md (scaffold with empty sections + auto-detected project terms stubbed).
2. Auto-grow on /sprint-start. When the operator arms a session via /sprint-start, the agent scans the active scorecard / roadmap docs for project-specific vocabulary (bracketed tags, persona names, caveat IDs, repeated product nouns) and adds stub entries to glossary-project.md if not yet defined. Stubs are 1-line placeholders the agent expands as planning surfaces more meaning. Marked with <!-- stub — fill in --> so future passes can find and complete them.
3. Auto-grow during planning. New CCSG block teaching: when the agent encounters a project term in a roadmap or sprint plan that isn't in the glossary yet, it ADDS THE STUB THEN-AND-THERE rather than inline-defining (which would re-create the drift trap v9.4 was built to prevent).
4. Lint hook (ADVISORY). glossary-lint.sh registered as a Stop hook. Scans recently-modified files in sprint-open/, sprint-closed/, docs/, and project root for <term> references. Warns when a term lacks a glossary definition. Excludes content inside ``` fenced code blocks to avoid false positives. Always exits 0 — pure advisory in v9.5; promotion to BLOCKING is queued for v10.0 once the convention proves out in production.
5. Block substitutions continue from v9.4 — additional sections of the CCSG block migrated to <term> references (the <custom>-mode section, mode comparisons, etc.). Targeted, not exhaustive — full block migration to <term>-only is gradual; first-occurrence definitions are preserved everywhere.
6. CCSG block teaching update. The "Glossary" section now reflects auto-grow behavior, references both glossary files (project too, not just framework), and points at the lint hook as a drift signal.
Adoption:
- New projects:
npx claude-sprint-gate+/sprint-setup— both glossary files seeded automatically. - Existing projects:
/sprint-model-rebuildpulls v9.5 block; manually copy templates from~/.claude/glossary-ccsg-template.mdand~/.claude/glossary-project-template.mdto repo root if not yet present (or re-run/sprint-setupto trigger Phase 6.6). - Pulscale (already adopted): no action — v9.5 block teaching activates on next compaction.
Tests: 32/32 GREEN (29 prior + 3 new for v9.5 lint hook + glossary auto-copy paths).
What's new — v9.4.0: glossary convention (<term> references for shared vocabulary)
The CCSG block in CLAUDE.md is loaded every session start + after every compaction. Measured redundancy: 12 inline re-explanations of Class A/B/C, 14 of mode names, 12 of "claim file", 7 of "strategic intent" — each carrying 3-5 lines of context. The same vocabulary drift hits projects: tonight's [OPERATOR]-tag doctrine got two corrections in 12 hours because the definition lived in three places (memory, roadmap notes, sprint plans).
v9.4 introduces a single source of truth for vocabulary:
glossary-ccsg-template.mdbundled in the npm package. Copy into your repo root asglossary-ccsg.mdto adopt. ~30 entries:<sprint>,<plan>,<verify>,<archive>,<arc>,<capability>/<integration>/<witness>for evidence classes,<usecase>/<maturity>/<custom>for modes,<claim>,<liveness>,<heartbeat>,<intent>,<gate>,<row>, etc.CCSG block teaching update: a new "Glossary (v9.4 — LOAD-BEARING — READ FIRST ON SESSION START)" section instructs the agent to read
glossary-ccsg.mdon session start. Project-specific vocabulary (your personas, product nouns, tags) lives inglossary-project.md— same repo root, project-owned. Whenever a<term>(in prose) or[TAG](in roadmap rows) appears, it resolves to a glossary entry. Doctrine corrections become single-file glossary edits — every doc using<term>inherits the new definition automatically.Targeted block substitutions: high-frequency inline definitions in the block template now use
<term>markup. Example: the canonical mode definition section says "Three modes (full definitions:<maturity>,<usecase>,<custom>inglossary-ccsg.md)" instead of repeating the def at every reference. The Three-class evidence rule uses<capability>/<integration>/<witness>tags. Targeted, not exhaustive — full block migration to<term>-only is queued for v9.5.
Tokenizer rationale. Single common-English words inside <> (<operator>, <capability>) tokenize to ~3 BPE tokens — fragmenting a hyphenated key like <atomic-temp-rename> would cost ~7. The <term> reference cost is constant; savings come from removing inline re-explanations.
Adoption (existing projects). Run /sprint-model-rebuild to pull the v9.4 block into your CLAUDE.md, then copy glossary-ccsg-template.md from the framework install into your repo root as glossary-ccsg.md. Optionally create glossary-project.md for your project-specific vocabulary. Pulscale is the reference implementation as of 2026-05-02.
Backwards compatible. Projects without glossary-ccsg.md still work — the block teaching degrades gracefully (no crash, just no term-lookup).
What's new — v9.3.0: heartbeat side-channel (mechanical liveness)
Master demonstrably skipped the per-tick update_own_agent({'last_heartbeat': ...}) instruction across long-running sessions — observed: zero agents/<nickname>.json mtime updates across 9 sprints / 2h+ in a v9.2 production run, while claim-guard.sh fired hundreds of times in the same window. Same pattern as the v9.1 → v9.2 depth-standard episode: when teaching does not move behavior, mechanical enforcement does.
v9.3 moves heartbeat publication out of agent responsibility entirely:
claim-guard.shnow refresheslast_heartbeaton the per-agent file matching the firing session'ssession_id, on every tool call (Bash/Edit/Write/Read/etc.), regardless of TOOL_NAME or sprint-relatedness.- The update fires BEFORE the v9.0 solo-mode passthrough so it works in both solo and multi-agent (heartbeat is observability, not enforcement).
- Atomic temp+rename; lookup is by session_id match; failures are silent so the hook chain is never broken.
- Per-tick
update_own_agent({'last_heartbeat': ...})instruction is REMOVED from CLAUDE.md teaching (no longer agent's job). Other fields (current_sprint,current_phase) remain agent-managed.
Net effect: agents/<nickname>.json mtime now tracks actual session liveness with ≤30s freshness during active work. Multi-signal liveness (heartbeat + git-activity + PID) returns alive on signal 1 instead of needing the git-activity fallback. Worker-spawned sessions checking master see fresh state without a roundtrip.
Trade-off documented: crash-recovery delay grows from "immediate on first wakeup tick" to "≤ 30 min (heartbeat threshold) + hourly cron" worst case. Acceptable: hourly cron always recovers, and the prior fast-reclaim path only mattered in multi-agent traffic that has not yet been observed.
What's new — v9.2.0: Depth Standard tightened with mechanical path triggers
v9.1 restored the depth standard to CLAUDE.md but master continued shipping thin proof for UI-touching sprints (S1024, S1025, S1031, S1033 — 4 of 7 had ≤3 KB total proof, no PNG, despite touching cloud/src/app/ route components and visible UI surfaces). The "MANDATORY full-viewport PNG" language was too soft — interpreted as "preferred."
v9.2 sharpens to mechanical, path-based triggers:
Path-triggered MANDATORY rules — no judgment call
If the impl commit touches ANY file matching the patterns below, a .png (or video) under sprint-proof/sprint-N/ is MANDATORY regardless of sprint size or LOC count:
cloud/src/app/** → full-viewport PNG with browser chrome
cloud/src/components/** → PNG showing component rendered in context
cloud/src/lib/uc01/** → if effect renders in UI listing → PNG of that listing
web/** → PNG with browser chrome
src/components/**, src/app/** → PNG required
**/*.tsx, **/*.jsx (route/page/component) → PNG requiredEffect-aware trigger — the rule fires not only on direct UI file changes but on backend changes whose effect surfaces in UI. Editing cloud/src/lib/uc01/registry-backfill.ts (backend) to fix a UI listing requires a PNG of that listing, even though no .tsx was touched.
Light AND dark variants required when change affects appearance. Two PNGs minimum: 01-feature-light.png + 02-feature-dark.png.
State coverage required: empty + populated for lists; pristine + invalid + submitted for forms; working + broken for guards.
"No observable surface" phrase becomes a HARD STOP rejection. Writing it means the agent didn't look hard enough. Every change has an effect. The standard now lists 9 effect categories with their proof class:
- Test added →
terminal-transcriptof test exit - Type fix →
terminal-transcriptoftsc --noEmitclean - Log emitted →
log-line-capturegrep - DB mutated →
db-query-resultSELECT - HTTP shape →
curl-response - IaC →
terminal-transcriptofhelm template+kubectl --dry-run=server - CI workflow →
terminal-transcriptofactionlint - Doc-only →
terminal-transcriptof link/path-resolution check - ...
Anti-pattern catalog added — explicit examples of NOT acceptable proof:
- "I tested locally" (claim not proof)
- Element-local crops without browser chrome
- Single-theme PNG when colors changed
- Markdown spec-mapping (restates code)
- Source-diff
.txt(already in git) - Tiny
.txtfor UI-touching sprint
Audit-replay closure test — before archiving, ask: "can a stranger reading only sprint-proof/sprint-N/ (no verify doc, no code) verify the change works?" If no, re-archive after capturing missing proof.
Sprint size does not relax depth requirements. A 12-line UI bug fix needs the same screenshot a 200-line feature does. Effort scales to the surface, not to the LOC.
No hook changes. Pure teaching tightening — the standard is mechanical so master can apply it without judgment calls about "small fix vs big feature."
What's new — v9.1.0: Visual Verification Depth Standard restored to teaching block
A 13-hour Pulscale dev session at v9.0 revealed that proof output regressed dramatically from pre-v8.5 levels: 4.5× fewer PNGs, 19× less proof data per sprint (~258 KB → ~15 KB average). Master shipped MINIMUM viable proof to satisfy the existence-only proof-gate, not BROAD coverage that proves the work.
Root cause traced: the depth standard (light/dark variants, working/broken states, full-viewport PNGs with browser chrome, 6 proof classes per the v6.7.1 catalog) lived inside verify-structure-guard.sh's error messages — fired only when a narrow Bash command pattern matched. When master used PowerShell / glob / two-step archive flows, the hook didn't fire. The standard disappeared from the model's context. v8.6 replaced the hook's depth check with existence-only. v9 made even existence advisory.
Meta-lesson: teaching that lives only in hook error messages is fragile — it reaches the model only when the hook fires. The CLAUDE.md teaching block is read on every tick + every compaction, so durable standards belong there.
v9.1 fix: Visual Verification Depth Standard added directly to the CLAUDE.md teaching block, with explicit per-work-type minimums:
- UI surface changes — MANDATORY full-viewport PNG with browser chrome (URL bar visible). Light AND dark variants when appearance affected. State coverage (empty / loading / populated / error). Sequence proof for flows.
- Backend / API changes — before-and-after db-query-result. curl-response for happy + error paths. log-line-capture when feature emits events.
- Workflow / journey sprints — continuous capture (.webm trace OR sequenced PNGs). Full-viewport at every navigation. db/log proof of persisted side-effects.
- Code-only / non-UI — test-passing transcript + compile-clean transcript + at least one downstream-effect proof.
"No observable surface" is explicitly named as a dead escape. Every change has an effect — find it, capture it.
No hook changes. Pure teaching restoration. The standard is back where the model reads it every tick. Hooks stay advisory. Master is expected to produce broad proof from the start, not be enforced into it.
What's new — v9.0.0: Autonomy Restoration
A 13-hour Pulscale dev session at v8.6 revealed that v8.4.6→v8.6.2 cumulatively traded autonomy for predictability. Each patch added a hook to defeat one observed failure; cumulative rigidity stopped master from self-healing. ~7 hours of solo-master downtime across hallucinations, dropouts, proof bypass.
v9.0 keeps mechanisms that solved real corruption classes and removes the rest. Three moves:
1. Solo-mode passthroughs. claim-guard.sh + sprint-ownership-guard.sh check agent count: if ≤1 agent registered, race protection is moot — passthrough. Restores pre-v8.4.6 solo-master cadence. Multi-agent mode keeps full discipline.
2. Advisory conversion. proof-gate-stop-hook.sh + archive-cleanliness-guard.sh flipped from exit 2 (block) to exit 0 + stderr warning. Master/operator sees the issue, decides whether to address. No more Q&A turns blocked on historical proof gaps.
3. chain-queue mechanism removed. The static parser requiring ## Sprint queue markdown table format is gone. CUSTOM mode restored to original contract: drop ANY doc with goals into /sprint-start custom <path>, master reads it with Read tool at plan-time, master interprets goals into sprints. No intermediate chain-queue.json, no symbol substitution at arm time.
Closure verification teaching added. Master explicitly counts sprint-closed/sprint-N-*-verify.md files matching the doc's stated scope BEFORE declaring an arc complete. Mechanical count, not prose interpretation. Prevents "35/35 closed" hallucination class without static parser.
Kept from v8.5/v8.6:
reclaim_ownership()— closes 5h+ dropout class- Per-agent files (
agents/<nickname>.json) — eliminates registry race - Multi-signal liveness (heartbeat OR git activity OR PID)
safe-push-guard.sh— force-push prevention (still BLOCKS — corruption class)sprint-first-guard.sh— sprint file before code edit- All v8.5 Python helpers
Removed/relaxed:
- chain-queue parser requirement at arm time
$ROADMAP_PATHSplaceholder substitution- "Master MUST read chain-queue.json first" teaching
- Blocking behavior on proof-gate + archive-cleanliness (now ADVISE)
- Solo-mode multi-agent overhead (claim-guard + ownership-guard skip when ≤1 agent)
Migration from v8.6.x:
chain-queue.jsonfiles become orphan but harmless (master no longer reads them)- Solo-master flows revert to pre-v8.4.6 simplicity
- Multi-agent flows keep all protections
- No breaking changes; drop-in compat
The litmus test for every line of v9 code: "would removing this break master's ability to ship in a way master couldn't recover from?" Most v8.6.x rules failed that test.
What's new — v8.6.1: proof-gate activity gate (UX fix)
v8.6.0 proof-gate Stop hook fired on EVERY Stop, including non-sprint turns. Real example tonight: user asked an interactive question about a UC, hit Stop, got blocked with the historical S999/S1000/S1002 proof-gap message. Gap is real but enforcing it on every conversation turn breaks normal interactive use.
Fix: activity-gate at the top of the hook. Pass through if either:
.sprint-stop-requestedflag exists (sprint mode paused), ORgit status --porcelainshows no pending changes undersprint-open/,sprint-closed/,sprint-docs/,sprint-proof/(no sprint work this turn)
Result: proof-gate enforces only when the current turn actually touched sprint state. Q&A, docs reads, framework chats, etc. pass through silently.
What's new — v8.6.0: coherent resilience (chain-queue + ownership reclaim + proof-gate)
A 13-hour Pulscale dev session at v8.5 revealed three structural failures that v8.5 didn't address: ~7 hours of solo-master downtime across two arc-boundary dropouts, one hallucinated arc closure ("35/35 done" — actually 0/35), and two zero-proof sprints (S1000, S1002) that shipped clean. v8.6 closes all three.
The three structural fixes:
chain-queue.json— flat sprint queue across the chain, built at/sprint-starttime by parsing each roadmap's## Sprint queuesection. Sprint numbers automatically renumbered to avoid collision with already-archived sprint numbers. Master walks this file mechanically as the next-sprint authority — does NOT re-read prose roadmaps at arc boundaries (which is where the hallucination class lives). Closure check is mechanical:all entries.status == "closed". Cannot be hallucinated from prose interpretation.Wakeup-tick ownership reclaim (
reclaim_ownership()) — when a wakeup-spawned fresh session arrives and finds.ccsg-active-sessionreferences a session whose per-agent file is stale (>30 min) OR missing, the new session ATOMICALLY takes over. Closes the long-arc-boundary dropout class — wakeups spawned in fresh sessions auto-recover instead of triggering the kill-switch loop and requiring manual/sprint-start.Proof-gate Stop hook (
proof-gate-stop-hook.sh) — universal scan ofsprint-closed/sprint-N-*-verify.mdfiles at every Stop. Verifies each## Visual Verificationsection cites at least onesprint-proof/sprint-N/path AND that path exists on disk. Replaces the v6.x archive-time check, which was Bash-pattern-fragile (bypassed by PowerShell, glob, two-step archive flows). Universal — fires regardless of how the archive happened. Default cutoff: enforce on the last 30 archived sprints; operator-overridable viaproof-gate-config.json.
Adjacent improvements:
is_synthetic_session()— heuristic detection of test session_ids (test-,wrong-,someone-,sid-, etc.) so synthetic test fires can route to a separate log namespace. Cleans up signal/noise in production log diagnosis.sync_chain_queue_from_filesystem()— refresheschain-queue.jsonstatuses from filesystem ground truth on every master tick. Catches manual archive operations that bypass the helper.- New CLI:
ccsg_agents.py {build-chain-queue, next-chain-sprint, update-chain-status, chain-complete, sync-chain-queue, reclaim-ownership, is-synthetic}. - All hooks:
proof-gate-scan.pyseparated from the Bash hook for testability and clarity. deploy.shupdated: installs proof-gate Stop hook + lib/proof-gate-scan.py, registers Stop hook insettings.jsontemplate.
Why v8.6 fixes what v8.5 didn't:
v8.5 ironed the multi-agent dispatch path (per-agent files, atomic claims, multi-signal liveness, fanout semantics, teaching presentism). All correct work, but gated on "when a worker arrives." v8.6 fixes the solo-master flow that was actually pain-shipping every day: arc transitions stay smooth, hallucinations are mechanically prevented, proof can't be silently bypassed. Solo master at v8.6 = pre-v8.5 cadence + v8.5 multi-agent readiness.
What's new — v8.5.3: teaching presentism (strip v-comparison narratives from operational sections)
User observation: agents reading the CLAUDE.md teaching block reproduced version-comparison summaries ("v8.5 changes from v8.4.x", "v8.4.4 bug: master pre-claimed every authored goal", "Solo master is identical to v8.2.4 peak") and used that vocabulary to misdiagnose their own bugs as framework version mismatches. Cause: the v8.5.0/v8.5.1/v8.5.2 edits to claude-md-block-template.md larded operational sections with migration-context — "v8.5 SSOT replaces v8.4.x", "v8.5 removed (auto-migrated by ...)", "the v8.4.x bug fix", "anti-patterns observed in production".
v8.5.3 strips the version-narrative. State-files table, Each-tick discipline, Fanout semantics, and Worker tick sections now read as present-tense operational rules. Stable LOAD-BEARING version-stamps on long-established rules (v7.9.4, v8.0, v8.1.x) preserved as audit-trail anchors. History/changelog content stays in this README — agents reading the per-tick teaching no longer recite migration scaffolding.
Why this matters operationally: when an agent fails (e.g., used a non-hex probe nonce), version-narrative teaching trains it to frame the failure as "this version doesn't fix that issue, must be a different layer's bug" instead of "I violated R8.0a, retry per spec." Present-tense teaching closes the misdiagnosis loop.
No code/helper/hook changes. Pure teaching cleanup.
What's new — v8.5.2: probe-nonce teaching fix (literal hex, no shell expansion)
v8.5.1 R8.0a teaching example used ${NONCE} shell substitution. This trained agents to use creative non-hex nonces. Master alpha hit it on a real re-arm at 12:00 local: produced ccsg-probe-test-arm-$(date +%s), which the hook regex [a-f0-9]{8,64} rejected (because hooks see the literal command BEFORE bash expansion). No sentinel got written; agent's ls /tmp/...*.id matched stale test fixtures from earlier framework-session testing; agent concluded "install is in test-mode" — wrong diagnosis, master entered the kill-switch loop.
v8.5.2 fixes:
- R8.0a teaching rewritten — strong
⚠️ READ THIS BEFORE WRITING THE PROBE COMMANDwarning at top: hooks fire BEFORE bash expansion; literal hex required; same exact hex string in both echo and cat paths - Probe code template uses literal hex (
a3f7c2b8d1e9example) — no${NONCE}placeholder for agents to mis-substitute - Anti-pattern catalog added — explicit "do NOT do these" list:
$(date +%s),${VAR}, wildcard*.id - NEW CLI:
python3 ~/.claude/lib/ccsg_agents.py probe-nonce— prints a fresh 12-char hex string (Pythonsecrets.token_hex(6)). Agents that prefer dynamic nonces can call this in one Bash, capture stdout, then embed THE EXACT value as a literal in the next Bash probe
Verification: the probe mechanism itself was never broken — integration test 7 (in 25/25 suite) confirms it works. v8.5.2 closes the teaching trap that caused agents to misuse it.
No code changes to claim-guard.sh, helper internals, or hook chain. Pure teaching + one new CLI helper.
What's new — v8.5.1: CLAUDE.md teaching aligned with v8.5 layout
v8.5.0 shipped helpers + hooks correctly (25/25 integration tests green) but the claude-md-block-template.md teaching block still had operational references to the legacy agents-registry.json + sprint-queue-state.json. The filesystem was v8.5; master's in-memory mental model was v8.4.x. This would have silently re-introduced the shared-file race the moment a worker armed.
v8.5.1 fixes the teaching:
- State files table — replaced
agents-registry.jsonrow with per-agentagents/<nickname>.jsonrow; removedsprint-queue-state.jsonrow; updated claim file schema to v8.5intent/statusfields - Each-tick discipline — heartbeat via
update_own_agent()(atomic temp+rename) instead of registry edit; removedsprint-queue-state.jsonrebuild step; explicit "solo master is identical to v8.2.4 peak" assertion - NEW section: Fanout semantics (v8.5 §4.1) — the LOAD-BEARING rule: master claims own-next goal atomically; fanout goals are claim-less for atomic worker pickup. Solo master never writes fanout goals (no worker to consume them). Fanout is purely additive.
- NEW section: Multi-signal liveness — worker's R8.0 check uses heartbeat OR git activity OR PID; ANY positive = live. Codified in tick code.
- Stale-sweep — walks per-agent files instead of registry; uses
list_agents()helper - Worker tick —
find_claimable_sprint()+try_atomic_claim()patterns documented - Worker bounds — "NEVER touches other agents' per-agent files" replaces "NEVER mutates sprint-queue-state.json"
- Master fanout imperative — explicit Pattern A (own-next + atomic claim) vs Pattern B (fanout, claim-less)
Result: master's in-memory teaching now matches the v8.5 filesystem layout. When a worker arms, master will write fanout goals correctly (claim-less); worker will atomically claim them; productivity flows. Solo master remains identical to peak v8.2.4 behavior.
Pure teaching alignment — no helper or hook changes. Integration tests still 25/25 GREEN.
What's new — v8.5.0: Multi-Agent Coherence (the v8.4.x consolidation)
v8.4.x shipped 12 patches over 2 weeks chasing multi-agent bugs as they surfaced. The 2026-05-02 production run with master alpha + worker bravo proved that even with all 12 patches applied, multi-agent dispatch never worked in practice — bravo claimed 0 sprints in 7 consecutive ticks despite Phase 4 fanout being explicitly designed for worker dispatch.
v8.5 fixes the root cause: CCSG had multiple sources of truth for the same concern, drifting under multi-agent operation. v8.5 enforces single source of truth per concern with atomic operations.
The big three fixes:
Per-agent registry files (was: shared
agents-registry.jsonwith multi-writer race). Each agent owns one file atsprint-docs/.ccsg/agents/<nickname>.json. Atomic writes via temp+rename. No race possible.Multi-signal liveness (was: single hardcoded 600s heartbeat threshold). Worker checks 3 signals: registry heartbeat fresh OR git activity by master in last 10 min OR process PID still running. ANY positive = live. Master's natural 600-900s tick cadence no longer causes false-stale refusals.
Claim release semantics (was: master claimed every authored goal, never released worker_eligible ones). New schema:
{intent, status, claimed_by, claimed_by_nickname, role, claimed_at}. Master writes claim atomically with goal-file ONLY for own-next sprint. Fanout goals are claim-less for workers to atomically grab via O_EXCL.
Solo master at v8.5 = identical to v8.4.x peak. Authors one goal at a time, claims it, executes, archives. No worker overhead. Master only adjusts behavior (fanout 1-3 extra goals) when a live worker is detected via the per-agent files.
Workers dock on additively. When armed, they scan sprint-open/ for claim-less worker_eligible goals and atomically claim via O_EXCL. Master is never blocked. Worker idle = harmless (no log noise, no race contribution).
Other v8.5 hygiene:
- Hook author guide (
docs/CCSG-HOOK-AUTHOR-GUIDE.md) — 7 rules every hook must follow (root via shared helper, no exit 1, atomic writes, PYTHONIOENCODING, etc.) - Shared
lib/ccsg-find-root.shhelper — phantom-immune project-root discovery, every hook agrees - Archive-cleanliness Stop hook (
archive-cleanliness-guard.sh) — universal stale-claim detection, replaces fragile v8.4.8 PreToolUse pattern /sprint-cleanup-stale-claimsslash command — one-shot orphan removalsprint-queue-state.jsonremoved entirely (was dead weight, never updated)- All hooks audited:
grep -rn "exit 1" plugin/hooks/*.shreturns empty
Migration from v8.4.x is automatic. First master tick post-upgrade splits the legacy registry, deletes queue-state, releases author-claims on worker_eligible goals, sweeps stale claims for archived sprints, normalizes remaining claims to v8.5 schema. Idempotent — safe to re-run; production-mirror tested against captured Pulscale state (14 stale claims swept, 1 active claim preserved, no data loss).
Test suite: bash tests/v8-5-integration-test.sh — 25/25 GREEN covering 7 scenarios (root discovery, registry split, multi-signal liveness, atomic claim race, migration, archive cleanliness, claim-guard probe).
Reference docs:
docs/v8-5-design-evidence.md— 13 empirical observations from the 2026-05-02 multi-agent night that motivated v8.5docs/v8-5-spec.md— full design spec, 14 sections including SSOT invariant, architecture changes, file format, migration plan, success criteria
What's new — v8.4.12: claim-guard project root via git rev-parse (kill phantom shadow)
Master alpha hit a wall on Pulscale S1009 within minutes of v8.4.11 shipping. Symptom: claim-guard kept blocking master's writes despite a valid claim file at /sprint-docs/.ccsg/claims/sprint-S1009.lock. Diagnosis: master CD'd into cloud/ subdir, and a phantom cloud/sprint-docs/ directory (left over from earlier work) shadowed the real one. claim-guard's project-root discovery walked up from CWD looking for any sprint-docs/ — found the phantom first, looked for the claim inside the phantom at cloud/sprint-docs/.ccsg/claims/sprint-S1009.lock, found nothing, blocked.
The bug was inconsistency, not the phantom. sprint-first-guard.sh has used git rev-parse --show-toplevel since v6 (correct). claim-guard.sh (v8.4.6 onward) used the legacy walk-up. Two different project-root algorithms in the same hook chain.
v8.4.12 fix: claim-guard prefers git rev-parse --show-toplevel (canonical project root). Falls back to walk-up only if not in a git repo. Aligns with sprint-first-guard.sh. Verified against master's exact failing scenario — PASS.
This is the kind of inconsistency the v8.5 consolidation pass will sweep more broadly: all hooks in the chain should agree on what the project root is. v8.4.12 fixes the most acute symptom; v8.5 will codify the rule.
What's new — v8.4.11: worker session_id probe (closes the v8.4.10 second-order gap)
After v8.4.10 fixed gate-18 to be claim-aware, worker mode was still broken on Windows. Root cause: $CLAUDE_SESSION_ID is unset in current Claude Code agent bash environments. The R8.0a fallback chain (CLAUDE_SESSION_ID → uuidgen → python uuid) silently landed on uuidgen, generating a fake WSID. Hooks see the real session_id (via stdin JSON), so claim files keyed to the fake WSID never matched — every worker write blocked at gate-18 + claim-guard regardless of v8.4.10's claim-aware passthrough.
v8.4.11 fix: probe-nonce session_id discovery.
claim-guard.shgains a probe responder. When a Bash command literally containsccsg-probe-<nonce>, the hook readssession_idfrom its stdin JSON and writes the full session_id to${TMPDIR:-/tmp}/ccsg-session-by-nonce-<nonce>.id. No-op for all other commands. Runs before all other gates (no project / claim-state dependency).sprint-start.md § R8.0arewrite. Worker arm picks a unique 12-16-char hex nonce, runs ONE Bash tool call whose command literally containsccsg-probe-<nonce>, then reads the sentinel. Hook is PreToolUse, so the sentinel exists before the same Bash call's main command runs. No race, no thrash. Falls back loud (with diagnostic instructions) if sentinel doesn't appear.
The session_id from the sentinel IS the real Claude Code session_id — the same value gate-18 + claim-guard compare against. Worker claim files now key correctly; gate-18 Gate 5 (v8.4.10 claim-aware passthrough) can finally do its job.
No new ownership model, no new hook, no new file. ~30-line patch across two existing files — fits the v8.4.x discovery cadence rather than waiting for v8.5 consolidation.
What's new — v8.4.10: gate-18 (sprint-ownership-guard) is now claim-aware
The hook hierarchy gap from the v8.4.6/7/8/9 push: gate-18 (sprint-ownership-guard.sh) was at v7.3.3 — pre-multi-agent — and blocked ALL non-owner writes to sprint-state regardless of claim files. Worker bravo claimed S1001 successfully (claim-guard verified), then halted at gate-18 because the worker is not the .ccsg-active-session owner. Master holds that ownership; the worker has its own claim file. Two ownership models that didn't talk to each other.
v8.4.10 adds Gate 5 to sprint-ownership-guard.sh — claim-aware passthrough:
For each Edit/Write/MultiEdit hook fire:
Gate 1: not a CCSG project → passthrough (existing)
Gate 2: no armed session → passthrough (existing)
Gate 3: this session IS .ccsg-active-session owner → passthrough (existing)
Gate 4: no file path → passthrough (existing)
Gate 5 (NEW): extract sprint-N from path; if claims/sprint-S<N>.lock owned by THIS session → passthrough
OR if writing OWN claim file (first-claim attempt, claim file doesn't yet exist) → passthrough
Gate 6: namespace match (sprint-open/, sprint-closed/, etc.) → BLOCK (existing)Verified by 4-case test suite: bravo first-claim write → PASS, bravo verify-file write in sibling worktree → PASS, wrong-session write to claimed sprint → BLOCK, master alpha write → PASS.
This closes the multi-agent enforcement series. The hook hierarchy now coherent:
.ccsg-active-session= project-level master ownership (gate-18 Gate 3)claims/sprint-S<N>.lock= sprint-level worker ownership (gate-18 Gate 5 + claim-guard)- Master always passes Gate 3; worker passes Gate 5 for its own claimed sprints; everyone else blocked
v8.5 consolidation planned: v8.4.x has been a discovery-driven patch series. v8.5 will be a coherent consolidation pass — single filename convention, single ownership-model documentation, hook-hierarchy spec, no more bandaid patches.
What's new — v8.4.9: claim-guard regex hotfix (filename convention without S prefix)
v8.4.6/7/8 had a silent bug. The claim-guard hook regex looked for sprint-S<N> in goal-file paths, but Pulscale's actual filename convention is sprint-<N>-*.md (no S prefix on file paths; the S is only in claim file names like claims/sprint-S<N>.lock). Result: claim-guard never fired on real production sprint files since v8.4.6.
Discovered when master alpha wrote sprint-open/sprint-1001-maya-uc01-server-state-audit.md for v8.4.4 look-ahead authoring without first writing a claim file. Hook should have blocked. It didn't. Inspection: grep -oE 'sprint-S[0-9]+' against sprint-1001-... returns nothing → SPRINT_NUM empty → hook passthrough.
The reason discipline still appeared to hold: master and bravo were following CLAUDE.md teaching ("write claim file first") because the teaching DOCUMENTS the hook's existence. Self-fulfilling discipline. But the hook was dead code.
v8.4.9 hotfix: make the S optional in all regex patterns:
sprint-S[0-9]+→sprint-S?[0-9]+- Archive case glob
sprint-open/sprint-S*→sprint-open/sprint-*
5 regex changes per hook file. Verified by 5-case test suite: WRITE without claim → BLOCK ✓, WRITE with own claim → PASS ✓, WRITE with wrong session → BLOCK ✓, ARCHIVE without rm → BLOCK ✓, ARCHIVE with rm → PASS ✓.
Hook now actually backs up the discipline. The race-protection layer that should have been live since v8.4.6 is finally enforcing.
Real fix planned for next minor (v8.5)
The hotfix preserves both filename conventions (sprint-<N> and sprint-S<N>). For the next minor, the framework should choose ONE canonical convention and migrate. Options:
- Canonicalize to
sprint-S<N>everywhere (filenames + claim files + commit messages). Breaking change for existing CCSG-managed projects; requires migration script. - Canonicalize to
sprint-<N>(filenames + claim files + commit messages). Less breaking; aligns with Pulscale's existing convention. Requires updating template + teaching. - Keep both, ship a manifest-based detection instead of regex. More robust but more complex.
Decision deferred to v8.5 design pass. v8.4.9 keeps the framework backward-compatible with both.
What's new — v8.4.8: claim-file cleanup at archive (closes the last v8.4.x discipline gap)
claim-guard.sh now enforces claim-file deletion at archive time. When archive detected (command contains sprint-open/sprint-S<N> AND sprint-closed/): if claims/sprint-S<N>.lock exists AND command does NOT include rm <claim> → BLOCK. Two valid patterns:
# Pattern A: separate
rm sprint-docs/.ccsg/claims/sprint-S<N>.lock
git mv sprint-open/sprint-S<N>-*.md sprint-closed/
# Pattern B: combined (recommended)
rm sprint-docs/.ccsg/claims/sprint-S<N>.lock && git mv sprint-open/sprint-S<N>-*.md sprint-closed/Implementation: literal substring matching via bash case (the v8.4.6 attempt's regex failed silently inside the hook for environment reasons; v8.4.8 sidesteps via portable substring match).
Step M-Archive teaching added to master tick CLAUDE.md.
Multi-agent enforcement series complete — every isolation invariant now has hook-level mechanical enforcement:
- v8.3.1
safe-push-guard.sh(force-push block + pull-rebase) - v8.4.3
loop-detect.shrole-aware (CCSG_ARM_WORKER token) - v8.4.6/v8.4.7
claim-guard.shownership + Windows path - v8.4.8
claim-guard.sharchive cleanup
What's new — v8.4.7: claim-guard.sh Windows-path hotfix
Pure hotfix to v8.4.6. The Python open(mingw-path) pattern in claim-guard.sh failed silently on Windows git-bash (Python on Windows doesn't resolve /c/Development/...-style paths; FileNotFoundError suppressed by 2>/dev/null; CLAIMED_BY came back empty; hook misreported "Claim owner: " on master's retry).
Fix: pipe file content via stdin (cat $CLAIM_FILE | python3 stdin) — no path ever passes to Python. Works on both Linux and Windows. Memory note saved for future hook authoring: NEVER use python open(mingw-path) in CCSG hooks.
What's new — v8.4.6: claim-guard hook (race protection at the file-system level)
Race condition observed in production v8.4.5: master worked solo on S997 from 23:22 to 00:04 without writing claims/sprint-S997.lock. User armed bravo at 00:04. Bravo's first tick scanned sprint-open/, saw S997 unclaimed (no claim file), saw master's registry as current_sprint: None (stale), correctly claimed via O_EXCL. Both agents ran Playwright concurrently on S997 — racing test artifacts, port collisions, dual Mongo seeds.
Root cause: v8.3.0+ teaching said master MUST write claim files; nothing enforced it. Master's solo-mode lapse left no claim files for in-flight work. When worker armed, no signal said "this is mine."
v8.4.6 fix — claim-guard.sh PreToolUse hook (Edit/Write/MultiEdit/Bash matchers):
Mechanically blocks sprint-S file edits + commits referencing S when:
- No
claims/sprint-S<N>.lockexists, OR - Claim file is owned by a different session
The corrected gate (NOT "live workers exist") is "v8.3.0+ project signal": sprint-docs/.ccsg/ directory exists. In v8.3.0+ projects, claim discipline is mandatory ALWAYS — even in solo mode. Why: solo-mode lapse is the trap that creates the race when transitioning to multi-agent. Solution: claim files always exist for in-flight work; worker arming finds them and skips correctly.
Master tick gains Step M-Claim (CLAUDE.md teaching):
- Before any sprint-S file edit OR commit referencing S: master MUST write
claims/sprint-S<N>.lockwith own session_id + role:master - Even when no worker is registered. Even in solo mode. Always.
- The hook is the safety net; the discipline is the design.
Hook behavior matrix:
| Tool | Match | Behavior |
|---|---|---|
| Edit/Write/MultiEdit | path matches sprint-{open,closed,blocked}/sprint-S<N>-* | require claim file owned by self; else BLOCK |
| Bash | command contains sprint-{open,closed,blocked}/sprint-S<N>-* path | same |
| Bash | command contains git commit with S<N>: or Sprint <N>: in message | same |
| All others | no sprint-S reference | passthrough |
| Pre-v8.3.0 projects | no sprint-docs/.ccsg/ directory | passthrough (legacy compa
