tools+hooks: preflight_drift (board-vs-cargo reconciliation) wired into SessionStart#406
Conversation
…iation Single-file zero-deps Rust port of WoA/.claude/v0.2/tools/preflight_drift.py. Compares numeric claims in CLAUDE.md and .claude/board/LATEST_STATE.md against actual Cargo.toml workspace members/exclude counts and PR-table row counts. Exits 1 when drift exceeds the configured threshold so a sprint spawn can refuse-to-start instead of building on stale claims. Currently surfaces 9-crate drift in the workspace_members metric (claim 7, real 16) and 10-crate drift in workspace_total (claim 22, real 32) — the existing CLAUDE.md "22 crates, 7 in workspace, 15 excluded" line predates the post-#372 expansions and needs an update independent of this PR. Build: rustc .claude/tools/preflight_drift.rs -o preflight_drift Run: ./preflight_drift [--threshold N] [--metric KEY]
Three-way path:
1. compiled binary at .claude/tools/preflight_drift → run it; on exit 1
(drift > threshold) append a "⚠ Board drift detected" block with the
full table to the additionalContext, surfacing the gap as a
system-reminder in the model's first turn.
2. binary missing but source present → emit a one-line rustc hint so
the operator can opt-in.
3. neither → silent, hook behaves exactly as before.
Never blocks: the JSON payload always emits; drift is informational
pressure, not a session-start gate. The hook surfaces what the
Mandatory Board-Hygiene Rule already says, instead of relying on a
human to notice the drift after-the-fact.
`.gitignore` excludes the compiled binary (platform-specific); source
stays committed.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c170e2dad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Ok(vec![ | ||
| ("workspace_members".into(), members), | ||
| ("excluded".into(), excluded), | ||
| ("workspace_total".into(), members + excluded), | ||
| ]) |
There was a problem hiding this comment.
Implement advertised
prs_in_table metric filtering
The CLI help advertises --metric prs_in_table, but this metric is never produced in real_metrics, and the drift loop iterates only real keys, so --metric prs_in_table yields an empty table and exits 0 even when drift exists. This is a silent false-negative path for users trying to validate that specific metric and should be fixed by either generating a real prs_in_table value or removing the advertised option.
Useful? React with 👍 / 👎.
| if ! DRIFT_OUT="$(cd "$REPO_ROOT" && "$DRIFT_TOOL" 2>&1)"; then | ||
| DRIFT_BLOCK=$'\n\n## ⚠ Board drift detected (preflight_drift exit 1)\n\n```\n'"$DRIFT_OUT"$'\n```\n\nThe board claims do not match Cargo.toml workspace state. Before any new sprint spawn, update `CLAUDE.md` / `.claude/board/LATEST_STATE.md` to match real counts, OR document why the drift is intentional. The Mandatory Board-Hygiene Rule in CLAUDE.md applies.' |
There was a problem hiding this comment.
Distinguish parser failures from actual drift in hook output
This branch treats any non-zero preflight_drift exit as board drift and hardcodes exit 1 in the message, but the tool defines exit code 2 for parse/read errors. When the binary fails for non-drift reasons (for example, transient read/parse failure), the session-start context incorrectly tells operators to edit board files instead of reporting a tool failure, which can send remediation in the wrong direction.
Useful? React with 👍 / 👎.
tools+hooks: preflight_drift (board-vs-cargo reconciliation) wired into SessionStart
Summary
Surfaces board-claim vs. cargo-reality drift at session start instead of catching it after-the-fact during PR review. Three commits:
73910fe—.claude/tools/preflight_drift.rs(274 LOC, zero-deps Rust). Self-contained port ofWoA/.claude/v0.2/tools/preflight_drift.pyto the Rust ecosystem. ParsesCLAUDE.mdfor the canonical "N crates, M in workspace, K excluded" claim andLATEST_STATE.mdfor PR-table rows; compares against actualCargo.tomlmembers/excludecounts via a tolerant inline TOML scanner. Exits 1 when drift exceeds the configured threshold.0c170e2—.claude/hooks/session-start.shextended with a three-way path:.claude/tools/preflight_drift→ run it; on exit 1 append a⚠ Board drift detectedblock (full table) tohookSpecificOutput.additionalContext.rustchint so the operator can opt-in..gitignoreexcludes the compiled binary (platform-specific); source stays committed.Live demonstration
Running today against this branch:
CLAUDE.mdline 6 still reads "22 crates, 7 in workspace, 15 excluded" — predates the post-#372cognitive-shader-driver/sigma-tier-router/lance-graph-supervisor/lance-graph-ontology/lance-graph-archetype/lance-graph-rbac/lance-graph-callcenteradditions. The doc update is intentionally not in this PR (separate APPEND-ONLY governance commit per Mandatory Board-Hygiene Rule); this PR ships the detection, not the fix.Why it exists
This is the automation of the Mandatory Board-Hygiene Rule from
CLAUDE.md:Until now the rule was enforced by human noticing during review. With this hook, the gap shows up in the model's first turn of every session — same surface as the existing bootload context — so the next session can resolve the drift before adding more claims on top of stale ones.
Conformance with
.claude/ATT/autoattended-orchestrator-spec.mdMaps to PP-16 preflight-drift-auditor in the spec's sprint-spawn gate model. Currently wired as a soft signal (informational pressure, not a session-start gate); future hardening could promote drift > threshold to a refuse-to-spawn in the wave orchestrator without changing this tool's API.
Test plan
rustc .claude/tools/preflight_drift.rs -o /tmp/preflight_drift && /tmp/preflight_driftexits 1 with the table abovebash .claude/hooks/session-start.shwith binary present surfaces the drift block inadditionalContextbash .claude/hooks/session-start.shwith binary absent surfaces the rustc build-hint blockbash .claude/hooks/session-start.shalways emits valid JSON (python3 -c 'json.load(sys.stdin)'succeeds)grep -E 'extern crate|^use [a-z]+::' .claude/tools/preflight_drift.rsshould yield onlystd::*)Generated by Claude Code