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>
46 lines
1.6 KiB
Markdown
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.
|