fix(#433): promote /handover MCP reindex to named step + add advisory hook#439
Conversation
… hook Bug: the post-clone MCP reindex in /handover was buried in a prose comment inside step 1.5-clone, surrounded by the phrase "skip silently if the MCP server is not available". In practice that reads as permission to skip unconditionally, so the reindex got skipped even when the server was running. All subsequent reads in steps 2-6 then fell back to grep + Read against a stale index, defeating the 3-15x token-cost benefit of cloning early. Fix: - Promote the reindex to its own named, numbered sub-step 1.5-reindex in handover SKILL.md, structurally parallel to 1.5-clone above and 1.5 (topology) below. Naming it makes it visually harder to skip during agent execution. - Replace the "skip silently" language with explicit non-silent failure semantics: emit a one-line warning, set REINDEX_STATUS=unavailable, then continue. Silent skips are the failure mode the change is trying to prevent. - Add a PostToolUse Bash hook (suggest-mcp-reindex-after-clone.sh) that fires after the matching git clone command and emits a one-line reminder banner. Advisory shape: exit 0 always, same pattern as detect-role-trigger.sh and check-upstream-drift.sh. Recognises both the literal workspace/<name> form (single-fork default) and absolute paths ending in /workspace/<name> (the helper-resolved form used in split-portfolio v2). Skips on failed clones (exit code != 0). - Wire the hook in .claude/settings.json under PostToolUse Bash with matcher `if: "Bash(git clone *)"`. - Add 11-case test suite at .claude/hooks/tests/test_suggest_mcp_reindex_after_clone.sh: 5 should-fire (workspace clones with various forms, banner content checks, project-name extraction) and 6 should-not (non-workspace clones, failed clones, non-clone commands, wrong tool, empty input). All 11 pass. Closes #433. ## Glossary | Term | Definition | |------|------------| | 1.5-clone | Step in /handover SKILL.md (added in #417) that clones the target repo into workspace/<name>/ immediately when a Git URL is given. Default behaviour; cheapest moment to clone before any reads. | | 1.5-reindex | New step added by this PR — call mcp__apexyard-search__reindex(scope="project", project="<name>") after a successful clone so search_code / search_docs return results during the deep-dive phases (steps 2-6) that follow. | | REINDEX_STATUS | New marker variable set by step 1.5-reindex: "indexed" on success, "unavailable" when the MCP server can't be reached, "skipped" when CLONE_STATUS isn't "cloned". Steps 2-6 read it to decide between MCP search and grep + Read. | | Advisory hook | Non-blocking PostToolUse hook that emits a banner to stderr and exits 0. Same pattern as detect-role-trigger.sh / check-upstream-drift.sh — removes the "I forgot the rule applied here" failure mode without changing tool behaviour. | | PostToolUse | Claude Code hook event that fires after a tool call completes. Carries the tool input AND the response (exit code, output) — lets the hook condition on whether the operation succeeded before suggesting follow-ups. | Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #439
Commit: 6381f88efaaf397075dad529d36ccaab8469001e
Summary
Two coordinated fixes for the failure mode in #433: the /handover skill's post-clone MCP reindex was buried in a prose comment with "skip silently if MCP unavailable" language, and agents read that as permission to skip unconditionally. This PR (a) promotes the reindex to a named numbered sub-step 1.5-reindex between 1.5-clone and 1.5 (topology), with explicit non-silent failure semantics (REINDEX_STATUS=unavailable + one-line warning), and (b) adds an advisory PostToolUse hook (suggest-mcp-reindex-after-clone.sh) that fires after git clone … workspace/<name> and emits a one-line reminder banner — same shape as detect-role-trigger.sh / check-upstream-drift.sh.
Checklist Results
- Architecture & Design: Pass — advisory hook follows the established non-blocking PostToolUse pattern; SKILL.md restructure cleanly slots the new step between two existing numbered headings.
- Code Quality: Pass — bash syntax clean (
bash -nOK), hook is well-commented, JSON parsing viajqmatches sibling hooks, banner is concise (~280 chars, within the ≤600 budget the file documents). - Testing: Pass — 11-case test suite (5 should-fire + 6 should-not). Verified locally:
PASS: 11, FAIL: 0. Covers single-fork form, quoted targets, absolute paths via portfolio helper, project-name extraction, non-workspace targets, failed clones (exit 1 and 128), and wrong-tool input. - Security: Pass — no secrets, no user-supplied input executed; the hook only reads from stdin JSON and writes to stderr.
- Performance: Pass — hook is short, runs once per
git clonePostToolUse, no shelling out beyondgrep/sed/jq. - PR Description & Glossary: Pass — glossary present with 5 well-defined terms (
1.5-clone,1.5-reindex,REINDEX_STATUS, advisory hook, PostToolUse). Ticket linked (Closes #433). Testing instructions present. - Summary Bullet Narrative: Pass — every bullet answers what changed AND why it matters (especially the "skip silently is indistinguishable from server down vs agent forgot" framing on bullet 2).
- Technical Decisions (AgDR): N/A — this is a tooling/skill polish PR. The "promote prose comment to named step" + "add advisory hook" pair follows existing framework patterns (detect-role-trigger.sh, check-upstream-drift.sh) and doesn't introduce a new technology, dependency, or architectural pattern that warrants AgDR ceremony.
- Adopter Handbooks: Pass with one advisory finding (see below).
Issues Found
One non-blocking CI failure — site-counts drift detection fails because the PR adds a new hook (hooks count: 35 → 36) and site/*.html, site/*.md.gen, site/llms.txt, site/llms-full.txt, site/skill.md still claim 35. The CI log explicitly calls this out as the expected failure mode for "added a new hook in this PR — refresh the counts in site/". This needs fixing before merge per the no-red-CI rule (.claude/rules/pr-quality.md § "No Red CI Before Merge") — surface as a finding but it's a 7-file count update, not a substantive code change. Either fold the count updates into this PR or land a follow-up commit on the same branch before merge.
Drift list from the CI log:
site/architecture.html:545— claims 35 shell gates, actual 36site/index.md.gen:78— claims 35 hooks, actual 36site/architecture.md.gen:28— claims 35 shell gates, actual 36site/llms.txt:6— claims 35 hooks, actual 36site/llms.txt:57— claims 35 shell hooks, actual 36site/llms-full.txt:6— claims 35 hooks, actual 36site/skill.md:46— claims 35 shell hooks, actual 36
Handbook Findings
Commit-message quality — handbooks/general/commit-message-quality.md (advisory)
- Commit
6381f88has subjectfix(#433): promote /handover MCP reindex to named step + add advisory hook(good — descriptive, under 70 chars), but the commit body is empty for a 202-line diff across 4 files. Per the handbook § "What Rex flags" #2: substantive PRs (>50 lines) deserve a commit body explaining the WHY independently of the PR body. A futuregit blamereader reaches the commit message, not the PR description. Consider amending with a short body that captures the load-bearing rationale from the PR body's "Why this matters" section (the "skip silently is indistinguishable" framing especially). Non-blocking; PR-body narrative is already solid.
Suggestions
- Hook robustness — workspace-name with hyphens via portfolio helper: the absolute-path branch of the regex (
/workspace/[A-Za-z0-9._-]+) handles hyphens, and the literal-path branch viaworkspace/[^[:space:]"'…]+accepts them too. Verified by the "absolute path via portfolio helper" test case extractingbar. No change needed — flagging as confirmed coverage. - Banner closing line: the third sentence — "If the MCP server is unavailable, print one-line warning + set REINDEX_STATUS=unavailable and continue" — duplicates what SKILL.md § 1.5-reindex says in more detail. Could be tightened to a one-liner ("On error: see § 1.5-reindex") to stay closer to the ~280-char banner budget; the current shape is still within budget so this is purely a polish nit.
- SKILL.md cross-reference back to the hook: the closing paragraph of § 1.5-reindex mentions the hook by filename ("A
PostToolUsehook (suggest-mcp-reindex-after-clone.sh) fires …"). Good — this gives the reader the symmetric link from skill ↔ hook, matching the framework's pattern (e.g.require-active-ticket.shreferenced from/start-ticket).
Verdict
COMMENT — the diff is well-shaped, well-tested, and addresses the documented failure mode cleanly. Merge blocked by the failing site-counts drift detection check (no-red-CI rule); update the 7 site/ files to reflect the new 36-hook total in the same branch and the gate clears. Once CI is green, this is ready to merge from the code-review side.
(Posted as COMMENT — author is self, GitHub blocks self-approve. Per the agent rules, no approval marker written on a COMMENT verdict.)
Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: 6381f88efaaf397075dad529d36ccaab8469001e
Adds the count refresh that the site-counts-check CI workflow requires
when a new hook ships in the same PR. Touches the six files the drift
test scans:
- site/llms.txt — top-line "> 35 hooks" claim + bullet "35 shell hooks"
- site/llms-full.txt — same two surfaces
- site/index.md.gen — H3 "Guardrails that stop bad code from shipping · 35 hooks"
- site/architecture.md.gen — Layer-1 bullet "35 shell gates"
- site/architecture.html — SVG text label ".claude/hooks/ · 35 shell gates"
- site/skill.md — "35 shell hooks enforce SDLC rules"
Verified locally:
bash .claude/hooks/tests/test_site_counts.sh
skills: 55 hooks: 36 roles: 19
PASS: site framework counts match actuals across all scanned files.
Refs #433.
atlas-apex
left a comment
There was a problem hiding this comment.
Code Re-Review: PR #439 (commit ccd06d7)
Re-review trigger: new commit ccd06d7 pushed since the last review at 6381f88 to address the site-counts-check CI failure.
Diff scope of the new commit — clean
The fixup commit ccd06d7 touches exactly 6 files, all under site/, all single-line 35 → 36 replacements. No scope creep — the SKILL.md, hook script, hook test, and settings.json from the original commit are untouched:
| File | Change |
|---|---|
site/architecture.html |
SVG text label .claude/hooks/ · 35 shell gates → 36 |
site/architecture.md.gen |
Hooks bullet 35 shell gates → 36 |
site/index.md.gen |
H3 Guardrails … · 35 hooks → 36 |
site/llms-full.txt |
Two surfaces: top-line claim + capability-layer bullet |
site/llms.txt |
Two surfaces: top-line claim + capability-layer bullet |
site/skill.md |
Body line 35 shell hooks enforce SDLC rules → 36 |
Each file's diff is exactly +1 −1 (or +2 −2 for the two-surface txt files). The commit message names all six files and includes the local verification output (hooks: 36 ✓).
CI status — all green
| Check | Status |
|---|---|
| Verify Ticket ID | pass |
| lychee (link check) | pass |
| markdownlint-cli2 | pass |
| shellcheck .claude/hooks | pass |
| site-counts drift detection | pass (was the failure I flagged) |
The lychee transient-flake concern didn't materialise — it passed cleanly on this run. The site-counts gate is now satisfied because both sides of its equality (the count in the source files vs the actual hook count under .claude/hooks/) agree at 36.
Combined PR head SHA
ccd06d7be4eebdd0cd287b4f99a574bbcba9719b
Matches the SHA you named. mergeable: MERGEABLE.
Remaining issues
None blocking. Everything I flagged in the prior review at 6381f88 is now resolved:
- ✅ site-counts-check CI failure: fixed by the deterministic count refresh
- ✅ No scope creep into the substantive PR files
- ✅ Glossary section present and complete
- ✅ Summary bullets are narrative (what + why), no label-only nits
- ✅ AgDR not required (advisory hook + SKILL.md restructure, no new dependency / framework / architecture decision)
Verdict
APPROVED (modulo the self-author constraint — posting as --comment instead of --approve, no approval marker written).
The fix is the right shape: count refresh isolated in its own commit, deterministic local verification command in the commit body, scope strictly limited to the six files the drift test scans. Ready for the merge-gate hand-off once a second reviewer takes a look.
🤖 Re-reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: ccd06d7be4eebdd0cd287b4f99a574bbcba9719b
… hook (#439) * fix(#433): promote /handover MCP reindex to named step + add advisory hook Bug: the post-clone MCP reindex in /handover was buried in a prose comment inside step 1.5-clone, surrounded by the phrase "skip silently if the MCP server is not available". In practice that reads as permission to skip unconditionally, so the reindex got skipped even when the server was running. All subsequent reads in steps 2-6 then fell back to grep + Read against a stale index, defeating the 3-15x token-cost benefit of cloning early. Fix: - Promote the reindex to its own named, numbered sub-step 1.5-reindex in handover SKILL.md, structurally parallel to 1.5-clone above and 1.5 (topology) below. Naming it makes it visually harder to skip during agent execution. - Replace the "skip silently" language with explicit non-silent failure semantics: emit a one-line warning, set REINDEX_STATUS=unavailable, then continue. Silent skips are the failure mode the change is trying to prevent. - Add a PostToolUse Bash hook (suggest-mcp-reindex-after-clone.sh) that fires after the matching git clone command and emits a one-line reminder banner. Advisory shape: exit 0 always, same pattern as detect-role-trigger.sh and check-upstream-drift.sh. Recognises both the literal workspace/<name> form (single-fork default) and absolute paths ending in /workspace/<name> (the helper-resolved form used in split-portfolio v2). Skips on failed clones (exit code != 0). - Wire the hook in .claude/settings.json under PostToolUse Bash with matcher `if: "Bash(git clone *)"`. - Add 11-case test suite at .claude/hooks/tests/test_suggest_mcp_reindex_after_clone.sh: 5 should-fire (workspace clones with various forms, banner content checks, project-name extraction) and 6 should-not (non-workspace clones, failed clones, non-clone commands, wrong tool, empty input). All 11 pass. Closes #433. ## Glossary | Term | Definition | |------|------------| | 1.5-clone | Step in /handover SKILL.md (added in #417) that clones the target repo into workspace/<name>/ immediately when a Git URL is given. Default behaviour; cheapest moment to clone before any reads. | | 1.5-reindex | New step added by this PR — call mcp__apexyard-search__reindex(scope="project", project="<name>") after a successful clone so search_code / search_docs return results during the deep-dive phases (steps 2-6) that follow. | | REINDEX_STATUS | New marker variable set by step 1.5-reindex: "indexed" on success, "unavailable" when the MCP server can't be reached, "skipped" when CLONE_STATUS isn't "cloned". Steps 2-6 read it to decide between MCP search and grep + Read. | | Advisory hook | Non-blocking PostToolUse hook that emits a banner to stderr and exits 0. Same pattern as detect-role-trigger.sh / check-upstream-drift.sh — removes the "I forgot the rule applied here" failure mode without changing tool behaviour. | | PostToolUse | Claude Code hook event that fires after a tool call completes. Carries the tool input AND the response (exit code, output) — lets the hook condition on whether the operation succeeded before suggesting follow-ups. | Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(#433): bump site hook count 35 → 36 to match the new advisory hook Adds the count refresh that the site-counts-check CI workflow requires when a new hook ships in the same PR. Touches the six files the drift test scans: - site/llms.txt — top-line "> 35 hooks" claim + bullet "35 shell hooks" - site/llms-full.txt — same two surfaces - site/index.md.gen — H3 "Guardrails that stop bad code from shipping · 35 hooks" - site/architecture.md.gen — Layer-1 bullet "35 shell gates" - site/architecture.html — SVG text label ".claude/hooks/ · 35 shell gates" - site/skill.md — "35 shell hooks enforce SDLC rules" Verified locally: bash .claude/hooks/tests/test_site_counts.sh skills: 55 hooks: 36 roles: 19 PASS: site framework counts match actuals across all scanned files. Refs #433. --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
1.5-reindex— putting it structurally on the same footing as1.5-cloneabove and1.5(topology) below so the agent's eye doesn't pass over it during executionREINDEX_STATUS=unavailable, continue. Silent skips are indistinguishable between "server down" and "agent forgot" — the second is the failure mode this PR exists to preventsuggest-mcp-reindex-after-clone.sh) that fires after the matchinggit clone … workspace/<name>and emits a one-line reminder. Exit 0 always, non-blocking — same shape asdetect-role-trigger.shandcheck-upstream-drift.sh.claude/settings.jsonunder PostToolUse Bash withif: "Bash(git clone *)".claude/hooks/tests/— 5 should-fire cases (workspace clones, quoted paths, absolute paths via portfolio helper, banner content, project-name extraction) + 6 should-not (non-workspace targets, failed clones with exit code != 0, wrong tool, empty input). All 11 passWhy this matters
The /handover skill clones the target repo into
workspace/<name>/at step 1.5-clone (default behaviour since #417). The whole point is that subsequent reads in steps 2–6 use the local clone viasearch_code/search_docsand the MCP index — described as "3-15× cheaper per query" thangrep+Read. That savings is conditional on the MCP index actually containing the cloned repo, which requires the reindex call.Before this PR the reindex was a prose comment inside the bash block, framed as "best-effort — skip silently if the MCP server is not available." In practice that phrase reads as permission to skip unconditionally. The reindex got skipped even when the server was running, all deep-dive reads ran against an empty index, and the savings vanished.
Two changes together fix it: structural promotion (so the step is harder to miss in the SKILL.md), and an advisory hook (so even if it IS missed, the operator gets a visible reminder right after the clone command).
Testing
bash .claude/hooks/tests/test_suggest_mcp_reindex_after_clone.sh— 11 PASS, 0 FAILjq empty .claude/settings.json— valid JSON.claude/skills/handover/SKILL.mdand confirm the new1.5-reindexsection sits between1.5-cloneand1.5(topology) with the warning + status-marker example blocks/handover <url>against a URL, observe step 1.5-clone clone, observe the hook banner emitted after the clone, observe step 1.5-reindex triggering the MCP reindex tool call$REINDEX_STATUS=unavailableis set; subsequent steps fall back togrep+Readwithout further apologyBackwards compatibility
Bashtool calls — adopters who don't use/handoversee no changeCloses #433.
Glossary
/handoverSKILL.md (added in #417) that clones the target repo intoworkspace/<name>/immediately when a Git URL is given. Default behaviour; cheapest moment to clone before any reads.mcp__apexyard-search__reindex(scope="project", project="<name>")after a successful clone sosearch_code/search_docsreturn results during the deep-dive phases (steps 2–6) that follow.REINDEX_STATUS"indexed"on success,"unavailable"when the MCP server can't be reached,"skipped"whenCLONE_STATUSisn't"cloned". Steps 2–6 read it to decide between MCP search andgrep+Read.detect-role-trigger.sh/check-upstream-drift.sh— removes the "I forgot the rule applied here" failure mode without changing tool behaviour.