diff --git a/.claude/guard-branch.sh b/.claude/guard-branch.sh new file mode 100755 index 0000000..6d6ca93 --- /dev/null +++ b/.claude/guard-branch.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# PreToolUse hook for Bash tool: blocks git commit/push on protected branches. +# TOOL_INPUT is JSON with a "command" field containing the bash command. + +PROTECTED_BRANCHES=("develop" "main" "master") + +COMMAND="${TOOL_INPUT:-}" + +# Only check commands that look like git commit or git push +if ! echo "$COMMAND" | grep -qE '\bgit\b.*(commit|push)'; then + exit 0 +fi + +BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")" + +for protected in "${PROTECTED_BRANCHES[@]}"; do + if [[ "$BRANCH" == "$protected" ]]; then + echo "BLOCKED: Cannot commit or push on protected branch '$BRANCH'." + echo "Create a feature branch first: git checkout -b feature/" + exit 2 + fi +done + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index 3c4a2bd..24c6445 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -6,13 +6,14 @@ "PreCompact": [ { "hooks": [{ "type": "command", "command": "beans prime" }] } ], - "PreToolCall": [ + "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", - "command": "bash -c 'if echo \"$TOOL_INPUT\" | grep -q \"git commit\"; then BRANCH=$(git branch --show-current); if [ \"$BRANCH\" = \"develop\" ] || [ \"$BRANCH\" = \"main\" ]; then echo \"BLOCK: Cannot commit directly to $BRANCH. Create a feature branch first: git checkout -b feature/\"; exit 2; fi; fi'" + "command": ".claude/guard-branch.sh", + "statusMessage": "Checking branch protection..." } ] } diff --git a/CLAUDE.md b/CLAUDE.md index f8c3f4f..0959288 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,11 +1,15 @@ # Branching Strategy -- **Never commit directly to `develop` or `main`.** Always create a `feature/*` branch first. -- When starting an **epic**, create `feature/` off `develop` -- When starting a **standalone task/bug** (no parent epic), create `feature/` off `develop` -- Each task within an epic gets its own commit(s) on the epic's feature branch -- Branch naming: use a kebab-case slug of the bean title (e.g., `feature/add-auth-system`) -- When the epic/task is complete, squash merge into `develop` +- **NEVER commit or push directly to `develop` or `main`.** These branches are protected. All work happens on `feature/*` branches. +- **Every epic** gets its own feature branch: `feature/` off `develop` +- **Every standalone task/bug** (no parent epic) gets its own feature branch: `feature/` off `develop` +- Branch naming: kebab-case slug of the bean title (e.g., `feature/add-auth-system`) + +## Committing workflow + +- **Every completed task gets its own commit** on the feature branch — including tasks within an epic. One task = one commit. +- After finishing a task, **immediately commit** the changes to the feature branch. Do not batch multiple tasks into a single commit. +- When the epic or standalone task is fully complete, squash merge the feature branch into `develop` (via PR). # Pre-commit Hooks @@ -21,7 +25,7 @@ Frontend hooks require `npm ci` in `frontend/` first (they use `npx` to run from # Instructions -- After completing a task, always ask the user if they'd like to commit the changes. +- After completing a task, immediately commit the changes to the current feature branch and ask the user to confirm. - Before working on a bean, always set it to in-progress. After the changes related to the bean are committed, mark it as completed. - If a bean is marked as draft, refine it first before starting work on it. - When completing a bean that has a parent (epic, feature, etc.), check the parent's checklist/success criteria for items that can now be marked as completed and update them.