Skip to content

Commit 8ddc180

Browse files
committed
config: record learning M224 - squash commits before approval
Enhanced warn-unsquashed-approval.sh hook to also detect when in main workspace with related commits that should be squashed (implementation + STATE.md update pairs). Prevention: Hook now warns when presenting approval with unsquashed related commits, even after merging to base branch.
1 parent 2bd1cf4 commit 8ddc180

3 files changed

Lines changed: 59 additions & 35 deletions

File tree

.claude/cat/retrospectives/mistakes.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2984,6 +2984,32 @@
29842984
"catches_variations": true
29852985
},
29862986
"correct_behavior": "Use inner_width - 2 - len(content) for padding calculation to ensure all box lines have consistent width"
2987+
},
2988+
{
2989+
"id": "M224",
2990+
"timestamp": "2026-01-24T12:00:00Z",
2991+
"category": "protocol_violation",
2992+
"description": "Presented approval gate with unsquashed commits - implementation and parent STATE.md update were separate commits instead of squashed together",
2993+
"root_cause": "Merged to base branch then committed STATE.md update separately in main workspace, bypassing worktree commit squashing",
2994+
"rca_method": "C",
2995+
"rca_method_name": "causal-barrier",
2996+
"prevention_type": "hook",
2997+
"prevention_path": "${CLAUDE_PROJECT_DIR}/plugin/hooks/warn-unsquashed-approval.sh",
2998+
"pattern_keywords": [
2999+
"squash",
3000+
"approval",
3001+
"commits",
3002+
"STATE.md"
3003+
],
3004+
"prevention_implemented": true,
3005+
"prevention_verified": false,
3006+
"recurrence_of": null,
3007+
"prevention_quality": {
3008+
"verification_type": "positive",
3009+
"fragility": "low",
3010+
"catches_variations": true
3011+
},
3012+
"correct_behavior": "Squash all related commits (implementation + STATE.md updates) into a single commit BEFORE presenting the approval gate to user"
29873013
}
29883014
]
29893015
}

.claude/cat/retrospectives/retrospectives.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"last_retrospective": "2026-01-23T19:15:39-05:00",
3-
"mistake_count_since_last": 0,
3+
"mistake_count_since_last": 1,
44
"config": {
55
"mistake_count_threshold": 10,
66
"trigger_interval_days": 7
Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/bin/bash
22
# Hook: warn-unsquashed-approval.sh
33
# Type: PreToolUse (AskUserQuestion)
4-
# Purpose: Warn when presenting approval gate with unsquashed commits (M199)
4+
# Purpose: Warn when presenting approval gate with unsquashed commits (M199/M224)
55
#
66
# This hook detects when an approval gate is being presented during /cat:work
77
# and warns if commits haven't been squashed yet.
8+
#
9+
# M224: Also checks main workspace for recent task commits that should be squashed.
810

911
set -euo pipefail
1012

@@ -15,12 +17,6 @@ if [[ "$TOOL_NAME" != "AskUserQuestion" ]]; then
1517
exit 0
1618
fi
1719

18-
# Check if we're in a task worktree
19-
if [[ ! -f "$(git rev-parse --git-dir 2>/dev/null)/cat-base" ]]; then
20-
echo '{}'
21-
exit 0
22-
fi
23-
2420
# Get the tool input to check if this is an approval-related question
2521
TOOL_INPUT="${TOOL_INPUT:-}"
2622

@@ -30,33 +26,35 @@ if ! echo "$TOOL_INPUT" | grep -qi "approve"; then
3026
exit 0
3127
fi
3228

33-
# Get base branch from worktree metadata
34-
CAT_BASE_FILE="$(git rev-parse --git-dir)/cat-base"
35-
BASE_BRANCH=$(cat "$CAT_BASE_FILE" 2>/dev/null || echo "")
36-
37-
if [[ -z "$BASE_BRANCH" ]]; then
38-
echo '{}'
39-
exit 0
40-
fi
41-
42-
# Count commits on task branch
43-
COMMIT_COUNT=$(git rev-list --count "${BASE_BRANCH}..HEAD" 2>/dev/null || echo "0")
44-
45-
# If more than 2 commits, warn about squashing
46-
if [[ "$COMMIT_COUNT" -gt 2 ]]; then
47-
output_hook_message "PreToolUse" "$(cat << EOF
48-
⚠️ PRE-APPROVAL CHECK FAILED: UNSQUASHED COMMITS (M199)
49-
50-
Found ${COMMIT_COUNT} commits on task branch (expected 1-2 after squashing).
51-
52-
Commits on branch:
53-
$(git log --oneline "${BASE_BRANCH}..HEAD")
54-
55-
BLOCKING: Run /cat:git-squash BEFORE presenting approval gate.
56-
Per work.md Pre-Approval Checklist, commits must be squashed by type first.
57-
EOF
58-
)"
59-
exit 0
29+
# Check if we're in a task worktree
30+
CAT_BASE_FILE="$(git rev-parse --git-dir 2>/dev/null)/cat-base"
31+
if [[ -f "$CAT_BASE_FILE" ]]; then
32+
# In worktree - check commits against base branch
33+
BASE_BRANCH=$(cat "$CAT_BASE_FILE" 2>/dev/null || echo "")
34+
35+
if [[ -n "$BASE_BRANCH" ]]; then
36+
COMMIT_COUNT=$(git rev-list --count "${BASE_BRANCH}..HEAD" 2>/dev/null || echo "0")
37+
38+
if [[ "$COMMIT_COUNT" -gt 2 ]]; then
39+
echo "{\"additionalContext\": \"⚠️ PRE-APPROVAL CHECK FAILED: UNSQUASHED COMMITS (M199)\\n\\nFound ${COMMIT_COUNT} commits on task branch (expected 1-2 after squashing).\\n\\nBLOCKING: Run /cat:git-squash BEFORE presenting approval gate.\"}"
40+
exit 0
41+
fi
42+
fi
43+
else
44+
# M224: In main workspace - check for recent task commits that weren't squashed
45+
# Look for multiple commits with same Task ID in last 5 commits
46+
RECENT_COMMITS=$(git log --oneline -5 2>/dev/null || echo "")
47+
48+
# Check if last 2+ commits are related (same task or config/planning pair)
49+
LAST_TWO=$(git log --oneline -2 2>/dev/null || echo "")
50+
TASK_ID_COUNT=$(git log --format="%B" -5 2>/dev/null | grep -c "Task ID:" || echo "0")
51+
52+
# If we have a planning/config commit followed by a task commit, they should be squashed
53+
if echo "$LAST_TWO" | head -1 | grep -qE "^[a-f0-9]+ (config|planning):.*progress" && \
54+
echo "$LAST_TWO" | tail -1 | grep -qE "^[a-f0-9]+ (feature|bugfix|refactor|test|docs):"; then
55+
echo "{\"additionalContext\": \"⚠️ PRE-APPROVAL CHECK: RELATED COMMITS SHOULD BE SQUASHED (M224)\\n\\nFound separate commits that should be combined:\\n$(echo \"$LAST_TWO\" | sed 's/^/ /')\\n\\nThe implementation commit and STATE.md update should be in the SAME commit.\\nSquash these commits before approval.\"}"
56+
exit 0
57+
fi
6058
fi
6159

6260
echo '{}'

0 commit comments

Comments
 (0)