diff --git a/docs/plans/2026-02-21-stop-hook-design.md b/docs/plans/2026-02-21-stop-hook-design.md new file mode 100644 index 0000000..e93c8d1 --- /dev/null +++ b/docs/plans/2026-02-21-stop-hook-design.md @@ -0,0 +1,43 @@ +# Stop Hook Design + +**Date:** 2026-02-21 +**Scope:** Project-level Claude Code hooks for Gravl + +## Goal + +Prevent Claude from stopping mid-session without completing its work. Two enforced conditions: + +1. No uncommitted changes to tracked files +2. No unresolved conversational TODOs or promises + +## Approach + +Two parallel hooks registered in `.claude/settings.json` under the `Stop` event. Both run every time Claude considers stopping. + +## Hook 1 — Git Clean Check (command) + +**File:** `.claude/hooks/git-clean-check.sh` + +Uses `git diff --name-only HEAD` to detect modified tracked files that were not committed. Untracked files (e.g. `frontend/dist/`, planning docs) are intentionally ignored to avoid false positives. + +- Exit 2 + JSON to stderr → blocks Claude from stopping, feeds reason back +- Exit 0 + JSON to stdout → approves + +## Hook 2 — Conversation TODO Check (prompt) + +A prompt hook that reads the conversation transcript and looks for unresolved work: phrases like "I'll fix X later", "TODO:", "we should also…", or any task Claude mentioned but did not complete. Returns `approve` or `block` with a list of unresolved items. + +## File Layout + +``` +.claude/ + settings.json # Hook registration + hooks/ + git-clean-check.sh # Git dirty state check +``` + +## Decisions + +- **Modified tracked files only (not untracked):** `frontend/dist/` and `.planning/` untracked files would cause constant false positives with a full `git status --porcelain` check. +- **Prompt hook for TODOs (not grep):** The goal is conversational promises, not code comments. A prompt hook reads the transcript and can reason about intent. +- **Project-level, not plugin:** These checks are specific to Gravl's GSD commit workflow; no plugin abstraction needed.