Section 07: Hook-heavy ambient automation
Status: Not Started
Goal: The user explicitly chose hook-heavy automation (per AskUserQuestion on 2026-04-14). This section implements the pre-review-intel.sh hook that fires on UserPromptSubmit for review-family slash-commands and injects a bounded Intelligence Summary into the conversation context BEFORE the model sees the prompt. Maximum adoption pressure — the operator sees intel summaries by default, no invocation required.
Context: Today, .claude/hooks/ has 4 hooks (block-banned-commands.sh, block-spec-edits.sh, classify-review-command.py, verify-hook.sh), all PreToolUse matchers on Bash. None surface the graph. The UserPromptSubmit hook event is a perfect fit — it fires BEFORE the prompt reaches the model, and returning hookSpecificOutput.additionalContext injects into the prompt stream without modifying the user’s message. Combined with a review-family matcher (reusing classify-review-command.py’s logic), the hook makes every /tpr-review, /review-work, /review-plan, /independent-review, /review-bugs, /tp-help, /fix-bug, /fix-next-bug invocation start with a graph summary already in the model’s context. TPR findings codex-009 and gemini-010 both flagged this gap.
Reference implementations:
- Ori
.claude/hooks/block-banned-commands.sh(5KB): PreToolUse hook shape, python3 JSON parse,hookSpecificOutputresponse envelope — this is the template we adapt - Ori
.claude/hooks/classify-review-command.py(22KB): review-family matcher already exists; §07’s hook imports/reuses its pattern rather than reimplementing - Ori
.claude/skills/dual-tpr/compose-intel-summary.md(§03): the bounded ≤500-char summary format — hook output matches this exactly so §07-injected and §03-embedded summaries are indistinguishable
Depends on: Section 03 (the hook emits the SSOT summary format).
07.1 Write pre-review-intel.sh with —preview dry-run
File(s): .claude/hooks/pre-review-intel.sh (new, ~150 lines)
-
Create the hook with the following behavior:
#!/usr/bin/env bash # UserPromptSubmit hook: inject bounded Intelligence Summary for review-family # slash-commands. Matches the format specified by # .claude/skills/dual-tpr/compose-intel-summary.md (§03 SSOT). # # Usage (live, via Claude Code harness): # hook receives JSON on stdin with `prompt` and `cwd` fields # emits JSON on stdout with hookSpecificOutput.additionalContext # # Usage (manual dry-run for operator testing): # echo '{"prompt": "/tpr-review my changes", "cwd": "..."}' \ # | ./pre-review-intel.sh --preview # # Exit codes: 0 — always (graceful degradation matches block-banned-commands.sh) set -euo pipefail PREVIEW=0 [[ "${1:-}" == "--preview" ]] && PREVIEW=1 INPUT=$(cat) PROMPT=$(printf '%s' "$INPUT" | python3 -c \ "import sys, json; print(json.load(sys.stdin).get('prompt',''))") # 1. Review-family matcher (reuse classify-review-command.py if available) REVIEW_FAMILY_RE='^/(tpr-review|review-work|review-plan|independent-review|review-bugs|tp-help|fix-bug|fix-next-bug)\b' if [[ ! "$PROMPT" =~ $REVIEW_FAMILY_RE ]]; then # Not a review-family command; emit empty context (no injection) printf '{}\n' exit 0 fi # 2. Availability check (silent degradation) if ! STATUS=$(scripts/intel-query.sh status 2>/dev/null); then printf '{}\n' # graph unreachable exit 0 fi case "$STATUS" in *'"status":"ok"'*) ;; *) printf '{}\n'; exit 0 ;; esac # 3. Extract changed file paths (git + explicit mentions in prompt) CHANGED_FILES=$(git diff --name-only HEAD 2>/dev/null | head -5) # Also honor any files explicitly mentioned in the prompt (compiler/foo/bar.rs style) PROMPT_FILES=$(printf '%s' "$PROMPT" | grep -oE '(compiler|library|tests|scripts|docs|\.claude)/[A-Za-z0-9_/.-]+\.(rs|ori|md|py|sh)' | head -5) ALL_FILES=$(printf '%s\n%s\n' "$CHANGED_FILES" "$PROMPT_FILES" | sort -u | head -5) if [[ -z "$ALL_FILES" ]]; then printf '{}\n' # nothing to query against exit 0 fi # 4. Run file-symbols for each (bounded at 5 files) SUMMARY_LINES=() while IFS= read -r FILE; do [[ -z "$FILE" ]] && continue # Extract path fragment for file-symbols query FRAGMENT=$(basename "$FILE" .rs | head -c 40) RESULT=$(timeout 3 scripts/intel-query.sh --human file-symbols "$FRAGMENT" --repo ori 2>/dev/null | head -3) [[ -n "$RESULT" ]] && SUMMARY_LINES+=("- [$FILE] $RESULT") done <<< "$ALL_FILES" # 5. Build bounded summary (≤500 chars) if [[ ${#SUMMARY_LINES[@]} -eq 0 ]]; then printf '{}\n' exit 0 fi SUMMARY="**Intelligence Summary (from intelligence graph):**"$'\n' for LINE in "${SUMMARY_LINES[@]}"; do SUMMARY+="$LINE"$'\n' done # Truncate to 500 chars if [[ ${#SUMMARY} -gt 500 ]]; then SUMMARY="${SUMMARY:0:497}…" fi # 6. Emit response if [[ $PREVIEW -eq 1 ]]; then printf '=== DRY RUN — would inject: ===\n%s\n' "$SUMMARY" exit 0 fi python3 -c " import json, sys print(json.dumps({ 'hookSpecificOutput': { 'hookEventName': 'UserPromptSubmit', 'additionalContext': sys.argv[1] } })) " "$SUMMARY" -
Make executable:
chmod +x .claude/hooks/pre-review-intel.sh. -
Unit-test via
--preview: manually craft stdin JSON for each review-family command, verify the dry-run output is a valid Intelligence Summary ≤500 chars. -
Error path tests:
- Graph unavailable → empty
{}response - No changed files / no prompt-file mentions → empty
{}response - Non-review-family command → empty
{}response
- Graph unavailable → empty
-
Subsection close-out (07.1):
- Hook script written, executable, and passes
--previewsmoke tests - Update
07.1status tocomplete - Run
/improve-toolingretrospectively on 07.1 — did debugging the hook reveal any.claude/hooks/patterns that should be extracted to a shared library? (E.g., ashell_lex.pyalready exists forclassify-review-command.py; consider whether hook shell scaffolding belongs in a similar helper.) Commit viabuild(hooks): .... - Run
/sync-claudeon 07.1 — CLAUDE.md §Commands already mentions.claude/hooks/block-banned-commands.sh. Add a note aboutpre-review-intel.shunder §Commands or §Key Paths. Commit viadocs: .... - Repo hygiene check.
- Hook script written, executable, and passes
07.2 Register in settings.json + test live firing
File(s): .claude/settings.json (or .claude/settings.local.json per project convention)
-
Use the
/update-configskill to add aUserPromptSubmithook entry:{ "hooks": { "UserPromptSubmit": [ { "matcher": "", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-review-intel.sh", "timeout": 5000 } ] } ] } }(The matcher is empty — the hook script itself does the review-family check internally. Centralizing the matcher in the script keeps the settings file simple.)
-
Start a fresh session (new conversation) and invoke
/tpr-reviewwith some staged changes; verify the first reviewer prompt’s context includes an Intelligence Summary block. -
Invoke a NON-review command (e.g.,
/commit-push); verify no Intelligence Summary is injected — confirms the matcher is scoped correctly. -
Subsection close-out (07.2):
- Registration successful; live firing verified on review-family AND correctly skipped on non-review
- Update
07.2status tocomplete - Run
/improve-toolingretrospectively on 07.2 — is there a way to test hook wiring without starting a new session? (Ascripts/test-hook.sh <hook> <input>harness might help.) Commit viabuild(diagnostics): .... - Run
/sync-claudeon 07.2 —.claude/settings.jsonchanged;.claude/rules/*.mdthat describe hook conventions may need a note on theUserPromptSubmithook family (this is the first hook of that type in the project). - Repo hygiene check.
07.3 Graceful-degradation verification
File(s): N/A (verification-only)
-
Simulate graph unavailability (
docker stop lang-intelligence), invoke/tpr-review, verify no Intelligence Summary appears and no error surfaces to the operator. -
Bring the graph back up; verify summary injection resumes on the next invocation.
-
Confirm: in no scenario does the hook’s output corrupt the prompt, cause the review skill to fail, or surface a visible error to the operator. Intelligence is ADDITIVE, never blocking.
-
Subsection close-out (07.3):
- All degradation scenarios pass
- Update
07.3status tocomplete - Run
/improve-toolingretrospectively on 07.3 — is there a way to make graph-availability state visible to the operator without being noisy? (E.g., a status-line indicator?) Cross-reference.claude/skills/update-config/— that’s where status-line configuration lives. - Run
/sync-claudeon 07.3 — hook coverage is now stable; verify.claude/rules/intelligence.md§Availability section still accurately describes the degradation contract. - Repo hygiene check.
07.4 SSOT registry drift detector (scripts/ssot-registry-audit.py)
File(s): scripts/ssot-registry-audit.py (new).
Surfaced by: plans/query-intel-adoption §03 section-close sweep (2026-04-14). The §03 SSOT consolidation used a per-consumer registry (Step F in compose-intel-summary.md) documenting each consumer’s exact scripts/intel-query.sh query set. Across 3 TPR rounds, 4 of the 6 total findings were Step F vs consumer query-set mismatches — a single automated audit would have caught them all in one pass. The Registry Contract clause in §03’s SSOT commits this contract in prose; §07.4 commits it in code.
-
Create
scripts/ssot-registry-audit.py:- Reads an SSOT registry file (default:
.claude/skills/dual-tpr/compose-intel-summary.md) - Parses Step F (or any designated registry section) to extract per-consumer query sets
- For each listed consumer, opens the consumer file and greps for
scripts/intel-query.sh --human <subcommand>invocations - Compares the registry’s documented query set against the consumer’s actual invocations
- Reports mismatches: queries in registry not in consumer (over-documentation), queries in consumer not in registry (DRIFT).
- Reads an SSOT registry file (default:
-
Output shape: human by default,
--jsonfor CI/hook consumption. Exit 0 if all entries match; non-zero with a mismatch count otherwise. -
--registry <path>flag to audit registries other thancompose-intel-summary.md(future SSOTs may adopt the same pattern). -
--fixflag (stretch): auto-update the registry to match consumer reality (with--dry-runpreview). Dangerous — gate behind explicit--apply. -
Rust/Python: Python is fine; this runs pre-commit, not in the compiler hot path.
-
Integration options (pick one in close-out): (a) invoke from §07.1’s hook as an additional pre-submission check, (b) lefthook pre-commit entry matching
.claude/skills/dual-tpr/compose-intel-summary.mdor any Step F-style section, (c) a standalone script invoked manually + in CI. -
Subsection close-out (07.4):
- Audit detects the 2026-04-14 TPR findings if the SSOT is reverted to pre-
dc1086cestate (regression safety check). - Update
07.4status tocomplete.
- Audit detects the 2026-04-14 TPR findings if the SSOT is reverted to pre-
07.R Third Party Review Findings
- None.
Exit Criteria: pre-review-intel.sh fires on review-family prompts, injects §03-format summaries into the conversation context, and degrades silently when the graph is unavailable. No visible regression in any review workflow. ./test-all.sh green.