Files
claude-foundations/memory/script-require-plan-file.md
Paul O'Reilly e94417b896 Add best practices, hooks, memory files, and scripts from recent sessions
Includes: spec-driven and test-driven development best practices,
reproduce-before-fixing debugging workflow, require-plan-file hook,
find-project-root script, session logs, memory files for decisions/
gotchas/process-lessons, and updates to existing best practice topics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 09:47:47 +13:00

46 lines
1.6 KiB
Markdown

---
name: require-plan-file
description: PreToolUse hook that blocks ExitPlanMode unless a *-PLAN.md file exists in the current project root
type: reference
---
# script: require-plan-file
**Location:** `hooks/require-plan-file.sh`
**Symlinked to:** `~/.claude/hooks/require-plan-file.sh`
## Purpose
Enforces the plan-file convention: Claude cannot exit plan mode until it has written a `[MILESTONE]-[PURPOSE]-PLAN.md` file in the project root.
## How it works
Fires as a `PreToolUse` hook on `ExitPlanMode`. Reads `cwd` from the JSON input on stdin, checks for any `*-PLAN.md` file in that directory:
- **File found** → exits 0 (allows ExitPlanMode to proceed)
- **No file found** → exits 2 (blocks ExitPlanMode, stderr message injected into Claude's context)
The exit 2 message tells Claude exactly what to do, so it self-corrects and writes the file before retrying.
## Settings.json config
```json
"PreToolUse": [
{
"matcher": "ExitPlanMode",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/require-plan-file.sh" }]
}
]
```
## Naming convention
`[MILESTONE]-[PURPOSE]-PLAN.md` — e.g. `M2-auth-PLAN.md`, `M3-monitoring-PLAN.md`, `initial-setup-PLAN.md`
Plan files are committed to the project repo as a persistent record of planning decisions.
## Gotchas
- `cwd` in the hook input is the Claude session's working directory, not necessarily the repo root — works correctly as long as Claude is `cd`'d into the project root (the standard pattern).
- If `cwd` cannot be parsed from stdin, the hook exits 0 (fails open) to avoid blocking legitimate use.