Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 109 additions & 47 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,81 +96,109 @@
],
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-unified-gate.py\"",
"description": "Unified gate: gitignore-bypass, git-submission, dangerous-command, creation-gate, sensitive-file (ADR-068)",
"timeout": 3000
},
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-synthesis-gate.py\"",
"description": "Consultation synthesis gate: blocks implementation when ADR consultation is incomplete",
"command": "python3 \"$HOME/.claude/hooks/pretool-branch-safety.py\"",
"description": "Branch safety: blocks git commit on main/master, forces feature branches",
"timeout": 3000
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-branch-safety.py\"",
"description": "Branch safety: blocks git commit on main/master, forces feature branches",
"command": "python3 \"$HOME/.claude/hooks/ci-merge-gate.py\"",
"description": "Gate: block merge to main/master when CI checks are red",
"timeout": 3000
},
}
]
},
{
"matcher": "Bash|Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-plan-gate.py\"",
"description": "Plan gate: blocks implementation code without task_plan.md",
"command": "python3 \"$HOME/.claude/hooks/pretool-learning-injector.py\"",
"description": "Inject known error patterns before Bash/Edit tools run",
"timeout": 3000
},
}
]
},
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-adr-creation-gate.py\"",
"description": "ADR creation gate: blocks new components without an ADR in adr/",
"command": "python3 \"$HOME/.claude/hooks/pretool-synthesis-gate.py\"",
"description": "Consultation synthesis gate: blocks implementation when ADR consultation is incomplete",
"timeout": 3000
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-learning-injector.py\"",
"description": "Inject known error patterns before Bash/Edit tools run",
"command": "python3 \"$HOME/.claude/hooks/pretool-plan-gate.py\"",
"description": "Plan gate: blocks implementation code without task_plan.md",
"timeout": 3000
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-prompt-injection-scanner.py\"",
"description": "Advisory scan for prompt injection patterns in agent context files (ADR-070)",
"timeout": 3000
},
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-subagent-warmstart.py\"",
"description": "Inject parent session context into subagent prompts (ADR-088)",
"timeout": 5000
},
"command": "python3 \"$HOME/.claude/hooks/pretool-adr-creation-gate.py\"",
"description": "ADR creation gate: blocks new components without an ADR in adr/",
"timeout": 3000
}
]
},
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/pretool-file-backup.py\"",
"description": "Backup files before Edit tool modifies them",
"timeout": 3000
},
}
]
},
{
"matcher": "Agent",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/ci-merge-gate.py\"",
"description": "Gate: block merge to main/master when CI checks are red",
"timeout": 3000
"command": "python3 \"$HOME/.claude/hooks/pretool-subagent-warmstart.py\"",
"description": "Inject parent session context into subagent prompts (ADR-088)",
"timeout": 5000
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/post-tool-lint-hint.py\""
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/error-learner.py\"",
"description": "Learn from tool errors and suggest solutions"
"command": "python3 \"$HOME/.claude/hooks/post-tool-lint-hint.py\"",
"description": "Gentle lint reminder after file modifications"
},
{
"type": "command",
Expand All @@ -185,48 +213,82 @@
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/routing-gap-recorder.py\"",
"description": "Record /do routing gaps to learning DB for pattern tracking",
"timeout": 2000
},
"command": "python3 \"$HOME/.claude/hooks/posttool-security-scan.py\"",
"description": "Advisory scan for credentials and SQL injection in Write/Edit output",
"timeout": 3000
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/retro-graduation-gate.py\"",
"description": "Warn about ungraduated retro entries when creating PRs in toolkit repo",
"timeout": 3000
},
}
]
},
{
"matcher": "Edit|Write|Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/record-activation.py\"",
"description": "Record session activation stats for ROI tracking (ADR-032)"
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/record-waste.py\"",
"description": "Record wasted tokens from tool failures for ROI tracking (ADR-032)"
},
}
]
},
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/posttool-session-reads.py\"",
"description": "Track files read this session for subagent warmstart (ADR-088)"
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/posttool-security-scan.py\"",
"description": "Advisory scan for credentials and SQL injection in Write/Edit output",
"timeout": 3000
},
}
]
},
{
"matcher": "Skill|Agent",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/usage-tracker.py\"",
"description": "Record Skill and Agent invocation analytics",
"timeout": 3000
},
}
]
},
{
"matcher": "Agent",
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/review-capture.py\"",
"description": "Capture CRITICAL/HIGH review findings to learning DB",
"timeout": 3000
}
]
},
{
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/error-learner.py\"",
"description": "Learn from tool errors and suggest solutions"
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/routing-gap-recorder.py\"",
"description": "Record /do routing gaps to learning DB for pattern tracking",
"timeout": 2000
},
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/record-waste.py\"",
"description": "Record wasted tokens from tool failures for ROI tracking (ADR-032)"
},
{
"type": "command",
Expand Down
13 changes: 2 additions & 11 deletions hooks/adr-enforcement.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,17 +180,8 @@ def main() -> None:

event = json.loads(raw)

# Only process PostToolUse events
event_type = event.get("hook_event_name") or event.get("type", "")
if event_type != _EVENT_NAME:
empty_output(_EVENT_NAME).print_and_exit(0)
return

# Only act on Write or Edit tool calls
tool_name = event.get("tool_name", "")
if tool_name not in ("Write", "Edit"):
empty_output(_EVENT_NAME).print_and_exit(0)
return
# tool_name/event_type filters removed — matcher "Write|Edit" in settings.json
# prevents this hook from spawning for non-matching tools.

# Extract file path from tool input
tool_input = event.get("tool_input", {})
Expand Down
6 changes: 2 additions & 4 deletions hooks/agent-grade-on-change.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,8 @@ def main():
if not hook_input:
return

# Check if this is a relevant tool call
tool_name = hook_input.get("tool_name", "")
if tool_name not in ("Edit", "Write"):
return
# tool_name filter removed — matcher "Write|Edit" in settings.json prevents
# this hook from spawning for non-matching tools.

# Extract file path from tool input
tool_input_data = hook_input.get("tool_input", {})
Expand Down
5 changes: 2 additions & 3 deletions hooks/ci-merge-gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
def main() -> None:
data = json.loads(read_stdin(timeout=2))

tool = data.get("tool_name", "")
if tool != "Bash":
return
# tool_name filter removed — matcher "Bash" in settings.json prevents
# this hook from spawning for non-Bash tools.

command = data.get("tool_input", {}).get("command", "")

Expand Down
10 changes: 2 additions & 8 deletions hooks/post-tool-lint-hint.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,8 @@ def main():
event_data = read_stdin(timeout=2)
event = json.loads(event_data)

# Check this is PostToolUse for Write or Edit
event_type = event.get("hook_event_name") or event.get("type", "")
if event_type != "PostToolUse":
return

tool_name = event.get("tool_name", "")
if tool_name not in ("Write", "Edit"):
return
# tool_name/event_type filters removed — matcher "Write|Edit" in settings.json
# prevents this hook from spawning for non-matching tools.

# Get the file path from tool input
tool_input = event.get("tool_input", {})
Expand Down
9 changes: 2 additions & 7 deletions hooks/posttool-security-scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,8 @@ def main() -> None:
raw = read_stdin(timeout=2)
event = json.loads(raw)

event_type = event.get("hook_event_name") or event.get("type", "")
if event_type != "PostToolUse":
return

tool_name = event.get("tool_name", "")
if tool_name not in ("Write", "Edit"):
return
# tool_name/event_type filters removed — matcher "Write|Edit" in settings.json
# prevents this hook from spawning for non-matching tools.

tool_input = event.get("tool_input", {})
file_path = tool_input.get("file_path", "")
Expand Down
6 changes: 2 additions & 4 deletions hooks/posttool-session-reads.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ def main() -> None:

event = json.loads(event_data)

# Only process Read tool results
tool_name = event.get("tool_name", "")
if tool_name != "Read":
return
# tool_name filter removed — matcher "Read" in settings.json prevents
# this hook from spawning for non-Read tools.

# Extract file_path from tool_input
tool_input = event.get("tool_input", {})
Expand Down
6 changes: 2 additions & 4 deletions hooks/pretool-adr-creation-gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,8 @@ def main() -> None:
except (json.JSONDecodeError, ValueError):
sys.exit(0)

# Only gate Write — edits to existing files are fine.
tool_name = event.get("tool_name", "")
if tool_name != "Write":
sys.exit(0)
# tool_name filter removed — matcher "Write" in settings.json prevents
# this hook from spawning for non-Write tools.

# Bypass env var.
if os.environ.get(_BYPASS_ENV) == "1":
Expand Down
5 changes: 2 additions & 3 deletions hooks/pretool-branch-safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ def main() -> None:
except (json.JSONDecodeError, ValueError):
sys.exit(0)

tool_name = event.get("tool_name", "")
if tool_name != "Bash":
sys.exit(0)
# tool_name filter removed — matcher "Bash" in settings.json prevents
# this hook from spawning for non-Bash tools.

command = event.get("tool_input", {}).get("command", "")
if "git commit" not in command:
Expand Down
5 changes: 2 additions & 3 deletions hooks/pretool-file-backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ def main() -> None:
except (json.JSONDecodeError, ValueError):
sys.exit(0)

tool_name = event.get("tool_name", "")
if tool_name != "Edit":
sys.exit(0)
# tool_name filter removed — matcher "Edit" in settings.json prevents
# this hook from spawning for non-Edit tools.

tool_input = event.get("tool_input", {})
file_path = tool_input.get("file_path", "")
Expand Down
9 changes: 2 additions & 7 deletions hooks/pretool-learning-injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@

EVENT_NAME = "PreToolUse"

# Tools that benefit from proactive learning injection
TARGET_TOOLS = {"Bash", "Edit"}

# Max characters in the injected context to stay lightweight
MAX_CONTEXT_CHARS = 500

Expand Down Expand Up @@ -160,11 +157,9 @@ def main():

event = json.loads(event_data)

# Early exit for non-target tools
# tool_name filter removed — matcher "Bash|Edit" in settings.json prevents
# this hook from spawning for non-matching tools.
tool_name = event.get("tool_name", "")
if tool_name not in TARGET_TOOLS:
empty_output(EVENT_NAME).print_and_exit()

tool_input = event.get("tool_input", {})

# Extract tags based on tool type
Expand Down
Loading
Loading