# Orchestration Protocol — Claude Code Hook Installer > **Usage:** Paste this file as a prompt to Claude Code, or run: `claude -p "$(curl -sL https://agentic-orchestration-workflows.vercel.app/orchestration/orchestration_hook_install.md)"` You are an installer. Execute every step below exactly. Do not skip, summarize, or ask for confirmation. Work silently — only report the final verification table. ## Prerequisites Run these checks first. If any fail, stop and report the missing dependency. ```bash command -v jq >/dev/null 2>&1 || { echo "MISSING: jq"; exit 1; } command -v curl >/dev/null 2>&1 || { echo "MISSING: curl"; exit 1; } command -v sha256sum >/dev/null 2>&1 || { echo "MISSING: sha256sum (coreutils)"; exit 1; } ``` ## Step 1 — Create directory structure Hooks are installed globally at `~/.claude/hooks/` so they apply to every project. ```bash mkdir -p ~/.claude/hooks mkdir -p .orchestration/tools/scripts ``` ## Step 2 — Ensure project CLAUDE.md The project's `CLAUDE.md` must contain the orchestration directive so every conversation loads the protocol. **Required content** (must appear in `CLAUDE.md`, exact text): ``` **CRITICAL: Follow the orchestration protocol injected by hooks on every prompt. This is non-negotiable — even for "simple" or "exploration" tasks.** ``` **Rules:** - If `CLAUDE.md` does not exist at the project root, create it with just the line above. - If `CLAUDE.md` exists but does NOT contain the directive, **prepend** the directive as the first line (preserve all existing content below). - If `CLAUDE.md` already contains the directive, do nothing. ```bash CLAUDE_MD="$PROJECT_DIR/CLAUDE.md" DIRECTIVE='**CRITICAL: Follow the orchestration protocol injected by hooks on every prompt. This is non-negotiable — even for "simple" or "exploration" tasks.**' if [ ! -f "$CLAUDE_MD" ]; then echo "$DIRECTIVE" > "$CLAUDE_MD" echo "CLAUDE.md: CREATED" elif ! grep -qF 'orchestration protocol' "$CLAUDE_MD"; then { echo "$DIRECTIVE"; echo ""; cat "$CLAUDE_MD"; } > "$CLAUDE_MD.tmp" && mv "$CLAUDE_MD.tmp" "$CLAUDE_MD" echo "CLAUDE.md: UPDATED (directive prepended)" else echo "CLAUDE.md: OK (directive already present)" fi ``` ## Step 3 — Write hook scripts Create the following four files exactly as shown in `~/.claude/hooks/`. ### `~/.claude/hooks/classify.sh` ```bash #!/bin/bash # Orchestration Hook: UserPromptSubmit (v2.0.0) # Session-aware injection: full protocol on first prompt, condensed reminder on subsequent. # Auto-classifies workflow, detects EXEMPT tasks, injects patterns. set -uo pipefail PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}" # Skip if project has no orchestration setup if [ ! -d "$PROJECT_DIR/.orchestration" ]; then exit 0 fi # --- Per-prompt: clear markers so each prompt starts fresh --- rm -f "$PROJECT_DIR/.orchestration/tools/.exempt" rm -f "$PROJECT_DIR/.orchestration/tools/.compaction_grepped" rm -f "$PROJECT_DIR/.orchestration/tools/.grep_patterns" rm -f "$PROJECT_DIR/.orchestration/tools/.prompt_keywords" INPUT=$(cat) PROMPT=$(echo "$INPUT" | jq -r '.prompt // .user_input // ""' 2>/dev/null) || PROMPT="" PROMPT_LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]') # --- Extract prompt keywords for grep quality validation --- # Stop words: common English words unlikely to match code identifiers STOP_WORDS="the a an is are was were be been being to for in on it its this that with from by at of and or not but so if do does did has have had can could will would shall should may might must into than then also just only very too quite" # Signal words: action verbs used for classification (won't match code identifiers) SIGNAL_WORDS="fix broken error crash bug build create add implement new clean improve restructure rename refactor slow optimize performance speed review check merge pr pull request test spec coverage e2e unit document readme explain qa smoke visual regression browser complex multi-step plan patterns conventions generate typo bump update change wording label what how does describe tell show look read view open print list" if [ -n "$PROMPT_LOWER" ]; then KEYWORDS="" for word in $PROMPT_LOWER; do # Strip non-alphanumeric chars from edges clean=$(echo "$word" | sed 's/^[^a-z0-9]*//;s/[^a-z0-9]*$//') # Skip if less than 3 chars [ ${#clean} -lt 3 ] && continue # Skip stop words is_stop=false for sw in $STOP_WORDS; do [ "$clean" = "$sw" ] && { is_stop=true; break; } done [ "$is_stop" = true ] && continue # Skip signal words is_signal=false for sig in $SIGNAL_WORDS; do [ "$clean" = "$sig" ] && { is_signal=true; break; } done [ "$is_signal" = true ] && continue KEYWORDS="${KEYWORDS}${clean}\n" done if [ -n "$KEYWORDS" ]; then mkdir -p "$PROJECT_DIR/.orchestration/tools" printf "%b" "$KEYWORDS" | sort -u > "$PROJECT_DIR/.orchestration/tools/.prompt_keywords" fi fi CDN_BASE="https://agentic-orchestration-workflows.vercel.app" CDN="$CDN_BASE/orchestration/workflows" LOCAL="$PROJECT_DIR/.orchestration/workflows" ORCH_FILE="$PROJECT_DIR/.orchestration/orchestration.md" PROTOCOL_MARKER="$PROJECT_DIR/.orchestration/tools/.protocol_injected" # --- Session-aware protocol loading --- PROTOCOL_CONTENT="" IS_FIRST_PROMPT=true if [ -f "$PROTOCOL_MARKER" ]; then IS_FIRST_PROMPT=false fi if [ "$IS_FIRST_PROMPT" = true ]; then # First prompt: load full protocol if [ -f "$ORCH_FILE" ]; then PROTOCOL_CONTENT=$(cat "$ORCH_FILE") else PROTOCOL_CONTENT=$(curl -sL --max-time 5 "$CDN_BASE/orchestration/orchestration.md" 2>/dev/null) || PROTOCOL_CONTENT="" fi # Set marker for subsequent prompts mkdir -p "$PROJECT_DIR/.orchestration/tools" touch "$PROTOCOL_MARKER" fi # --- Detect project technology for workflow routing --- # React (.jsx/.tsx) → workflows/react/ | .NET (.cs) → workflows/ | Other → workflows/ TECH_PREFIX="" if find "$PROJECT_DIR" -maxdepth 4 -name '*.tsx' -o -name '*.jsx' 2>/dev/null | head -1 | grep -q .; then TECH_PREFIX="react/" fi # --- Classification Table --- RULES=( "feature|${TECH_PREFIX}feature.md|build create add implement new" "bugfix|${TECH_PREFIX}bugfix.md|fix broken error crash bug" "refactor|${TECH_PREFIX}refactor.md|clean improve restructure rename refactor" "performance|${TECH_PREFIX}performance.md|slow optimize performance speed" "review|${TECH_PREFIX}review.md|review check merge" "pr|${TECH_PREFIX}pr.md|pr pull request" "test|${TECH_PREFIX}test.md|test spec coverage e2e unit" "docs|${TECH_PREFIX}docs.md|document readme explain" "qa|qa.md|qa smoke visual regression browser" "todo|todo.md|complex multi-step plan" "patterns-gen|patterns-gen.md|patterns conventions generate" ) # --- Match signal words --- MATCHED_KEY="" MATCHED_PATH="" BEST_SCORE=0 for rule in "${RULES[@]}"; do IFS='|' read -r key path words <<< "$rule" score=0 for word in $words; do if echo "$PROMPT_LOWER" | grep -qiw "$word"; then score=$((score + 1)) fi done if [ "$score" -gt "$BEST_SCORE" ]; then BEST_SCORE=$score MATCHED_KEY=$key MATCHED_PATH=$path fi done # --- Load matched workflow (if any) --- WORKFLOW_CONTENT="" CLASSIFICATION_NOTE="" if [ "$BEST_SCORE" -gt 0 ]; then LOCAL_FILE="$LOCAL/$MATCHED_PATH" if [ -f "$LOCAL_FILE" ]; then WORKFLOW_CONTENT=$(cat "$LOCAL_FILE") else WORKFLOW_CONTENT=$(curl -sL --max-time 5 "$CDN/$MATCHED_PATH" 2>/dev/null) || WORKFLOW_CONTENT="" fi if [ -n "$WORKFLOW_CONTENT" ]; then CLASSIFICATION_NOTE="AUTO-CLASSIFIED: $MATCHED_KEY workflow (confidence: $BEST_SCORE signal words matched)" else CLASSIFICATION_NOTE="Auto-classified as '$MATCHED_KEY' but workflow file not found. Fetch from: $CDN/$MATCHED_PATH" fi else CLASSIFICATION_NOTE="No auto-classification matched. Use the classification table in the protocol to classify this task manually." fi # --- EXEMPT detection (safe default: NOT exempt) --- # EXEMPT rule: single file, 1-2 ops, zero architecture impact, obvious correctness, no codebase search needed. EXEMPT="false" # Step 1: NEVER-EXEMPT keywords (architecture impact / multi-file / codebase search) NEVER_EXEMPT_PATTERN="\b(rename|refactor|restructure|move|delete|remove|replace|shared|component|import|export|across|everywhere|every|all files|multiple files|codebase|blast radius|dep graph|dependency|schema|migration|database|api|endpoint|route|middleware|auth)\b" HAS_NEVER_EXEMPT=false if echo "$PROMPT_LOWER" | grep -qEi "$NEVER_EXEMPT_PATTERN"; then HAS_NEVER_EXEMPT=true fi # Step 2: Split EXEMPT signals into read-only and trivial-edit categories READONLY_PATTERN="\b(what does|what is|how does|explain|describe|tell me|show me|look at|read|view|open|print|list)\b" TRIVIAL_EDIT_PATTERN="\b(typo|string literal|bump version|update version|change text|fix text|wording|label|fix typo)\b" HAS_READONLY=false HAS_TRIVIAL_EDIT=false if echo "$PROMPT_LOWER" | grep -qEi "$READONLY_PATTERN"; then HAS_READONLY=true fi if echo "$PROMPT_LOWER" | grep -qEi "$TRIVIAL_EDIT_PATTERN"; then HAS_TRIVIAL_EDIT=true fi # Step 3: Decision if [ "$HAS_NEVER_EXEMPT" = true ]; then # Never exempt regardless of other signals EXEMPT="false" elif [ "$HAS_TRIVIAL_EDIT" = true ]; then # Trivial edits are always exempt EXEMPT="true" elif [ "$HAS_READONLY" = true ] && [ "$BEST_SCORE" -le 1 ]; then # Read-only queries are exempt only if ≤1 workflow signal word matched EXEMPT="true" else # Unknown prompts → NOT EXEMPT (safe default) EXEMPT="false" fi # Write marker if EXEMPT if [ "$EXEMPT" = "true" ]; then mkdir -p "$PROJECT_DIR/.orchestration/tools" touch "$PROJECT_DIR/.orchestration/tools/.exempt" fi # --- Load patterns if not EXEMPT --- PATTERNS_CONTENT="" if [ "$EXEMPT" = "false" ]; then PATTERNS_FILE="$PROJECT_DIR/.patterns/patterns.md" if [ -f "$PATTERNS_FILE" ]; then PATTERNS_CONTENT=$(cat "$PATTERNS_FILE") fi fi # --- Output --- echo "" if [ "$IS_FIRST_PROMPT" = true ]; then # First prompt: inject full protocol if [ -n "$PROTOCOL_CONTENT" ]; then echo "--- ORCHESTRATION PROTOCOL (implement strictly) ---" echo "$PROTOCOL_CONTENT" echo "--- END PROTOCOL ---" echo "" fi else # Subsequent prompts: inject condensed reminder cat <<'REMINDER' --- ORCHESTRATION REMINDER --- GATED SEQUENCE: 1) Compact → 2) Grep compaction → 3) Read source (only for gaps) HARD RULE: Do NOT Read source, Glob, or Explore until compaction is grepped and findings stated. NO CONTEXT REUSE: Each new task must grep compaction independently. BINDING: ⚙ [task] | [workflow + URL] | [simple/complex] | [tools] EXEMPT: ⚙ [task] | EXEMPT — only when: single file, 1-2 ops, zero architecture impact, no codebase search. Use standard tools (Read/Glob/Grep), NOT Bash for file reading. COMPLETION: ✓ [task] | [workflow] | [files modified] | cleanup: [yes/no/n/a] PATTERNS: If .patterns/patterns.md exists, load and treat as binding constraints. --- END REMINDER --- REMINDER fi echo "$CLASSIFICATION_NOTE" echo "EXEMPT-DETECTED: $EXEMPT" if [ -n "$WORKFLOW_CONTENT" ]; then echo "" echo "--- WORKFLOW: $MATCHED_KEY ---" echo "$WORKFLOW_CONTENT" echo "--- END WORKFLOW ---" fi if [ -n "$PATTERNS_CONTENT" ]; then echo "" echo "--- PATTERNS (binding constraints) ---" echo "$PATTERNS_CONTENT" echo "--- END PATTERNS ---" fi echo "" exit 0 ``` ### `~/.claude/hooks/maintain.sh` ```bash #!/bin/bash # Orchestration Hook: SessionStart # Self-maintenance: checks CDN for protocol/script updates and downloads if needed. set -uo pipefail PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}" # Skip if project has no orchestration setup if [ ! -d "$PROJECT_DIR/.orchestration" ]; then exit 0 fi ORCH_DIR="$PROJECT_DIR/.orchestration" SCRIPTS_DIR="$ORCH_DIR/tools/scripts" CDN_BASE="https://agentic-orchestration-workflows.vercel.app" mkdir -p "$SCRIPTS_DIR" # Clear session markers from previous sessions rm -f "$ORCH_DIR/tools/.compaction_grepped" rm -f "$ORCH_DIR/tools/.protocol_injected" rm -f "$ORCH_DIR/tools/.grep_patterns" rm -f "$ORCH_DIR/tools/.prompt_keywords" UPDATES="" # --- 1. Check orchestration.md freshness --- LOCAL_ORCH="$ORCH_DIR/orchestration.md" if [ -f "$LOCAL_ORCH" ]; then CDN_ORCH=$(curl -sL --max-time 5 "$CDN_BASE/orchestration/orchestration.md" 2>/dev/null || echo "") if [ -n "$CDN_ORCH" ]; then LOCAL_HASH=$(sha256sum "$LOCAL_ORCH" | cut -d' ' -f1) CDN_HASH=$(echo "$CDN_ORCH" | sha256sum | cut -d' ' -f1) if [ "$LOCAL_HASH" != "$CDN_HASH" ]; then echo "$CDN_ORCH" > "$LOCAL_ORCH" UPDATES="${UPDATES}Updated orchestration.md from CDN. " fi fi fi # --- 2. Check tool scripts via manifest.json --- MANIFEST=$(curl -sL --max-time 5 "$CDN_BASE/tools/manifest.json" 2>/dev/null || echo "") if [ -n "$MANIFEST" ] && echo "$MANIFEST" | jq empty 2>/dev/null; then for script in $(echo "$MANIFEST" | jq -r 'keys[]'); do EXPECTED_HASH=$(echo "$MANIFEST" | jq -r ".[\"$script\"] | if type == \"object\" then .sha256 else . end") LOCAL_SCRIPT="$SCRIPTS_DIR/$script" NEEDS_UPDATE=false if [ ! -f "$LOCAL_SCRIPT" ]; then NEEDS_UPDATE=true else ACTUAL_HASH=$(sha256sum "$LOCAL_SCRIPT" | cut -d' ' -f1) if [ "$ACTUAL_HASH" != "$EXPECTED_HASH" ]; then NEEDS_UPDATE=true fi fi if [ "$NEEDS_UPDATE" = true ]; then curl -sL --max-time 5 "$CDN_BASE/tools/$script" -o "$LOCAL_SCRIPT.tmp" 2>/dev/null && \ [ -s "$LOCAL_SCRIPT.tmp" ] && mv "$LOCAL_SCRIPT.tmp" "$LOCAL_SCRIPT" && \ UPDATES="${UPDATES}Updated script: $script. " || rm -f "$LOCAL_SCRIPT.tmp" fi done else # No manifest — ensure base scripts exist for s in compaction.js dep-graph.js symbols.js; do if [ ! -f "$SCRIPTS_DIR/$s" ]; then curl -sL --max-time 5 "$CDN_BASE/tools/$s" -o "$SCRIPTS_DIR/$s.tmp" 2>/dev/null && \ [ -s "$SCRIPTS_DIR/$s.tmp" ] && mv "$SCRIPTS_DIR/$s.tmp" "$SCRIPTS_DIR/$s" && \ UPDATES="${UPDATES}Downloaded script: $s. " || rm -f "$SCRIPTS_DIR/$s.tmp" fi done fi # --- 3. Clean old artifacts (keep only latest of each) --- for pattern in compacted depgraph symbols; do ls -t "$ORCH_DIR/tools/${pattern}_"*.md 2>/dev/null | tail -n +2 | xargs rm -f 2>/dev/null || true done # --- 4. Check artifact staleness via git-sha --- CURRENT_SHA=$(git -C "$PROJECT_DIR" rev-parse HEAD 2>/dev/null || echo "") HAS_CHANGES=$(git -C "$PROJECT_DIR" status --short 2>/dev/null | head -1) if [ -n "$CURRENT_SHA" ]; then for artifact in "$ORCH_DIR/tools/"compacted_*.md "$ORCH_DIR/tools/"depgraph_*.md "$ORCH_DIR/tools/"symbols_*.md; do [ -f "$artifact" ] || continue ARTIFACT_SHA=$(sed -n 's/.*git-sha:[[:space:]]*\([0-9a-f]\{7,\}\).*/\1/p' "$artifact" 2>/dev/null | head -1) || ARTIFACT_SHA="" if [ -z "$ARTIFACT_SHA" ] || [ "$ARTIFACT_SHA" != "$CURRENT_SHA" ] || [ -n "$HAS_CHANGES" ]; then rm -f "$artifact" UPDATES="${UPDATES}Removed stale artifact: $(basename "$artifact"). " fi done fi # --- Output --- if [ -n "$UPDATES" ]; then echo "$UPDATES" else echo "All orchestration artifacts up to date." fi exit 0 ``` ### `~/.claude/hooks/guard-explore.sh` ```bash #!/bin/bash # Orchestration Hook: PreToolUse # Guards against accessing source files before grepping compaction. # Uses a session marker to track whether compaction was grepped. # Blocks Read/Glob/Grep/Task(Explore) on source paths until compaction is grepped. set -uo pipefail PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}" # Skip if project has no orchestration setup if [ ! -d "$PROJECT_DIR/.orchestration" ]; then exit 0 fi # Skip guard entirely for EXEMPT tasks if [ -f "$PROJECT_DIR/.orchestration/tools/.exempt" ]; then exit 0 fi INPUT=$(cat) TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null) || TOOL_NAME="" MARKER="$PROJECT_DIR/.orchestration/tools/.compaction_grepped" DENY_REASON="BLOCKED: You must grep compacted_*.md BEFORE accessing source files. Required sequence: 1) Generate compaction if missing (node .orchestration/tools/scripts/compaction.js ), 2) Grep .orchestration/tools/compacted_*.md for task-relevant terms, 3) State findings from compaction grep, 4) Only then access source files (state why compaction was insufficient). This tool call has been denied." deny() { cat </dev/null) || TARGET_PATH="" if echo "$TARGET_PATH" | grep -qE "compacted_|\.orchestration/tools"; then if ! ls "$PROJECT_DIR/.orchestration/tools/compacted_"*.md >/dev/null 2>&1; then cat < — then grep the output." } } JSON exit 0 fi # Extract the grep pattern GREP_PATTERN=$(echo "$INPUT" | jq -r '.tool_input.pattern // ""' 2>/dev/null) || GREP_PATTERN="" # Reject trivial patterns if is_trivial_pattern "$GREP_PATTERN"; then cat <> "$PATTERNS_FILE" # Check if unlock criteria are met if check_unlock_criteria; then touch "$MARKER" fi # Always allow the grep itself (just may not unlock source access yet) exit 0 fi # Block Grep on source files if compaction not yet grepped if [ ! -f "$MARKER" ] && [ -n "$TARGET_PATH" ] && ! is_safe_path "$TARGET_PATH"; then # Enhanced deny message when patterns exist but criteria not met if [ -f "$PATTERNS_FILE" ]; then TRIED=$(tr '\n' ', ' < "$PATTERNS_FILE" | sed 's/,$//') DENY_REASON="BLOCKED: You've grepped compaction but haven't found task-relevant results yet. Try grepping for terms from the user's request, or check the Entry Points section. Patterns tried so far: $TRIED. Need: 2+ distinct patterns or 1 matching a prompt keyword." fi deny fi exit 0 fi # ─── Only guard Read, Glob, Task ─── case "$TOOL_NAME" in Read|Glob|Task) ;; *) exit 0 ;; esac # ─── Already grepped compaction? Allow everything ─── if [ -f "$MARKER" ]; then exit 0 fi # ─── Check if targeting source files (inverted: block unless safe path) ─── IS_SOURCE=false case "$TOOL_NAME" in Read) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null) || FILE_PATH="" if [ -n "$FILE_PATH" ] && ! is_safe_path "$FILE_PATH"; then IS_SOURCE=true fi ;; Glob) PATTERN=$(echo "$INPUT" | jq -r '.tool_input.pattern // ""' 2>/dev/null) || PATTERN="" SEARCH_PATH=$(echo "$INPUT" | jq -r '.tool_input.path // ""' 2>/dev/null) || SEARCH_PATH="" # Allow globs targeting orchestration/patterns dirs if echo "$PATTERN" | grep -qE "(\.orchestration|\.patterns)"; then IS_SOURCE=false elif echo "$PATTERN" | grep -qE "\*\.(tsx?|jsx?|cs|css|scss|html|py|go|rs|vue|svelte)"; then IS_SOURCE=true elif [ -n "$SEARCH_PATH" ] && ! is_safe_path "$SEARCH_PATH"; then IS_SOURCE=true fi ;; Task) SUBTYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // ""' 2>/dev/null) || SUBTYPE="" if [ "$SUBTYPE" = "Explore" ]; then IS_SOURCE=true fi ;; esac # Allow non-source access (orchestration, patterns, config, etc.) if [ "$IS_SOURCE" = false ]; then exit 0 fi # ─── Block source access before compaction grep ─── # Enhanced deny message when patterns exist but criteria not met if [ -f "$PATTERNS_FILE" ]; then TRIED=$(tr '\n' ', ' < "$PATTERNS_FILE" | sed 's/,$//') DENY_REASON="BLOCKED: You've grepped compaction but haven't found task-relevant results yet. Try grepping for terms from the user's request, or check the Entry Points section. Patterns tried so far: $TRIED. Need: 2+ distinct patterns or 1 matching a prompt keyword." fi deny ``` ### `~/.claude/hooks/rehydrate.sh` This hook re-injects the full orchestration protocol after context compaction. Note: `PostCompact` is not a registerable hook event, so this script is sourced by the rehydration system-reminder or can be called manually. ```bash #!/bin/bash # Orchestration Hook: PostCompact # Re-injects the orchestration protocol after context compaction. set -uo pipefail PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}" ORCH_FILE="$PROJECT_DIR/.orchestration/orchestration.md" # Clear protocol_injected marker so next prompt re-injects full protocol rm -f "$PROJECT_DIR/.orchestration/tools/.protocol_injected" if [ -f "$ORCH_FILE" ]; then PROTOCOL_CONTENT=$(cat "$ORCH_FILE") echo "" echo "Context was compacted. The orchestration protocol is still active." echo "" echo "--- ORCHESTRATION PROTOCOL (implement strictly) ---" echo "$PROTOCOL_CONTENT" echo "--- END PROTOCOL ---" echo "" else echo "Orchestration protocol file not found at $ORCH_FILE" fi exit 0 ``` ## Step 4 — Make scripts executable ```bash chmod +x ~/.claude/hooks/classify.sh ~/.claude/hooks/maintain.sh ~/.claude/hooks/guard-explore.sh ~/.claude/hooks/rehydrate.sh ``` ## Step 5 — Register hooks in user-level settings Read `~/.claude/settings.json`. If it exists, **merge** the `hooks` key into the existing JSON (preserve all existing keys). If it does not exist, create it. **IMPORTANT — Claude Code settings requirements:** - Command paths must be **absolute** — `~` and `$HOME` do NOT expand in JSON. Resolve the user's home directory and write the full path (e.g., `/home/user/.claude/hooks/classify.sh`). - Each hook group must include `"matcher": "*"`. - `PostCompact` is **NOT** a valid hook event — do not register `rehydrate.sh` in settings.json. It is kept as a utility script only. - `timeout` values are in **seconds**. - PreToolUse deny decisions use JSON `hookSpecificOutput` with `permissionDecision: "deny"` + exit 0 (exit 2 silently drops output). Resolve the absolute home path: ```bash HOOKS_DIR="$(eval echo ~)/.claude/hooks" echo "$HOOKS_DIR" ``` The hooks configuration to merge (replace `HOOKS_DIR` with the resolved absolute path): ```json { "hooks": { "UserPromptSubmit": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "HOOKS_DIR/classify.sh", "timeout": 10 } ] } ], "SessionStart": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "HOOKS_DIR/maintain.sh", "timeout": 30 } ] } ], "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "HOOKS_DIR/guard-explore.sh", "timeout": 5 } ] } ] } } ``` **Merge rules:** - If `settings.json` already has a `hooks` key, merge each event array. Do not duplicate entries with identical commands. - Preserve all existing keys (`enabledPlugins`, `permissions`, `effortLevel`, etc.) untouched. - Write valid JSON (no trailing commas, no comments). ## Step 6 — Verify installation Run each hook with test input and confirm exit code 0: ```bash echo '{"prompt":"add a new component","session_id":"test"}' | CLAUDE_PROJECT_DIR="." ~/.claude/hooks/classify.sh > /dev/null 2>&1 && echo "classify: OK" || echo "classify: FAIL" echo '{"source":"startup","session_id":"test"}' | CLAUDE_PROJECT_DIR="." ~/.claude/hooks/maintain.sh > /dev/null 2>&1 && echo "maintain: OK" || echo "maintain: FAIL" echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | CLAUDE_PROJECT_DIR="." ~/.claude/hooks/guard-explore.sh > /dev/null 2>&1 && echo "guard-explore: OK" || echo "guard-explore: FAIL" echo '{"source":"compact","session_id":"test"}' | CLAUDE_PROJECT_DIR="." ~/.claude/hooks/rehydrate.sh > /dev/null 2>&1 && echo "rehydrate: OK" || echo "rehydrate: FAIL" ``` Validate CLAUDE.md: ```bash grep -qF 'orchestration protocol' CLAUDE.md && echo "CLAUDE.md: OK" || echo "CLAUDE.md: MISSING DIRECTIVE" ``` Validate settings JSON: ```bash jq empty ~/.claude/settings.json && echo "settings: VALID JSON" || echo "settings: INVALID JSON" ``` ## Step 7 — CDN availability check Verify all CDN endpoints the hooks depend on are reachable: ```bash CDN="https://agentic-orchestration-workflows.vercel.app" cdn_ok=0; cdn_fail=0 for path in \ orchestration/orchestration.md \ orchestration/workflows/react/feature.md \ orchestration/workflows/dotnet/feature.md \ orchestration/workflows/qa.md \ orchestration/workflows/todo.md \ tools/manifest.json \ tools/compaction.js tools/dep-graph.js tools/symbols.js tools/parse-utils.js; do code=$(curl -sL --max-time 5 -o /dev/null -w '%{http_code}' "$CDN/$path") if [ "$code" = "200" ]; then cdn_ok=$((cdn_ok+1)); else cdn_fail=$((cdn_fail+1)); echo " FAIL ($code): $path"; fi done echo "CDN: $cdn_ok reachable, $cdn_fail failed" ``` ## Step 8 — Report Print this summary table with actual results: ``` Orchestration Hooks — Installation Report ========================================== Hook Event Status ───────────────────────────────────────────── classify.sh UserPromptSubmit [OK/FAIL] maintain.sh SessionStart [OK/FAIL] guard-explore.sh PreToolUse [OK/FAIL] rehydrate.sh (utility only) [OK/FAIL] CLAUDE.md — [OK/MISSING] settings.json — [VALID/INVALID] ───────────────────────────────────────────── Hooks installed globally at ~/.claude/hooks/ Settings updated at ~/.claude/settings.json Active for ALL projects. Restart Claude Code to activate. ``` ## Appendix — Known Constraints | Constraint | Detail | |---|---| | No tilde expansion | `~` and `$HOME` don't expand in settings.json `command` fields — use absolute paths | | No `PostCompact` event | Not a valid Claude Code hook event — `rehydrate.sh` is kept as a utility script, not registered in settings | | Matcher required | Each hook group needs `"matcher": "*"` | | PreToolUse deny format | Must output JSON `hookSpecificOutput` with `permissionDecision: "deny"` + exit 0. Exit code 2 blocks silently (drops output) | | Timeout in seconds | Not milliseconds | | JSON output on exit 0 only | Hook JSON output is only parsed when exit code is 0 | | No `set -e` in hooks | Use `set -uo pipefail` (not `set -euo pipefail`) — `-e` causes silent crashes on `jq` failures, producing "No stderr output" errors | | Parse from `$INPUT` directly | Extract `jq` fields from original stdin JSON via `.tool_input.field` — do NOT extract `tool_input` as intermediate string then re-parse (breaks on complex content like Agent prompts) | | Always add `jq` fallbacks | Every `jq` extraction should have `2>/dev/null) \|\| VAR=""` to prevent non-zero exits |