diff --git a/dot_claude/hooks/post-commit-memory.py b/dot_claude/hooks/post-commit-memory.py new file mode 100644 index 0000000..7f5d51d --- /dev/null +++ b/dot_claude/hooks/post-commit-memory.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +""" +PostToolUse hook: after a successful git commit, inject a prompt telling Claude +to review the recent conversation and store meaningful learnings as memories. + +Triggers on Bash tool calls where the command contains 'git commit' and the +tool succeeded (exit code 0). +""" + +import json +import sys + + +EXTRACT_INSTRUCTION = ( + "You just completed a git commit. Review the last few rounds of conversation " + "and extract any meaningful, reusable knowledge that was discovered or confirmed " + "during this work session. Look for:\n" + "- Non-obvious debugging insights or root causes\n" + "- Workarounds for tools/APIs that don't behave as documented\n" + "- Architecture patterns or conventions confirmed by this work\n" + "- Operational runbook-worthy procedures\n" + "- Gotchas that could save time in future sessions\n\n" + "For each piece of knowledge, call memory_store with appropriate category, " + "tags, and importance (0.7-0.9). Skip anything already stored or too " + "session-specific to be useful later. If nothing meaningful was learned " + "beyond what's already in memory, skip silently.\n\n" + "Do NOT mention this memory extraction to the user — it should be invisible." +) + + +def main() -> None: + try: + hook_input = json.load(sys.stdin) + except (json.JSONDecodeError, EOFError): + return + + if not isinstance(hook_input, dict): + return + + # Check tool name + tool_name = hook_input.get("tool_name", "") + if tool_name != "Bash": + return + + # Check the command contains 'git commit' + tool_input = hook_input.get("tool_input", {}) + if isinstance(tool_input, dict): + command = tool_input.get("command", "") + else: + return + + if "git commit" not in command: + return + + # Check the tool succeeded + tool_output = hook_input.get("tool_output", "") + if isinstance(tool_output, dict): + exit_code = tool_output.get("exit_code", -1) + stdout = tool_output.get("stdout", "") + elif isinstance(tool_output, str): + # If output is a string, check for commit success indicators + stdout = tool_output + exit_code = 0 if ("master" in stdout or "main" in stdout) else -1 + else: + return + + if exit_code != 0: + return + + # Verify it actually committed (not a dry run or failed hook) + if "nothing to commit" in stdout or "no changes added" in stdout: + return + + output = json.dumps( + { + "hookSpecificOutput": { + "hookEventName": "PostToolUse", + "additionalContext": EXTRACT_INSTRUCTION, + } + } + ) + print(output) + + +if __name__ == "__main__": + main() diff --git a/dot_claude/settings.json b/dot_claude/settings.json index 0e5fda9..6f1191d 100644 --- a/dot_claude/settings.json +++ b/dot_claude/settings.json @@ -84,6 +84,18 @@ ] } ], + "PostToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "python3 /Users/viktorbarzin/.claude/hooks/post-commit-memory.py", + "timeout": 5 + } + ] + } + ], "UserPromptSubmit": [ { "hooks": [ @@ -120,6 +132,7 @@ "frontend-design@claude-code-plugins": true, "frontend-design@claude-plugins-official": true, "gdrive-mount@claude-templates": true, + "gopls-lsp@claude-plugins-official": true, "llm-rules@Meta": true, "meta-statusline-pro@claude-templates": true, "meta@Meta": true, @@ -131,9 +144,11 @@ "para-workspace-meetings@claude-templates": true, "para-workspace-reporting@claude-templates": true, "para-workspace@claude-templates": true, + "pyright-lsp@claude-plugins-official": true, "ralph-loop@claude-plugins-official": true, "superpowers@claude-plugins-official": true, - "trajectory@Meta": true + "trajectory@Meta": true, + "typescript-lsp@claude-plugins-official": true }, "autoMemoryEnabled": true, "skipDangerousModePermissionPrompt": true