cc-discipline 2.10.1 → 2.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -153
- package/README.zh-CN.md +299 -207
- package/bin/cli.sh +96 -96
- package/global/CLAUDE.md +45 -45
- package/init.sh +594 -594
- package/lib/doctor.sh +145 -145
- package/lib/stack-remove.sh +68 -68
- package/lib/status.sh +100 -100
- package/package.json +34 -34
- package/templates/.claude/agents/investigator.md +44 -44
- package/templates/.claude/agents/reviewer.md +46 -46
- package/templates/.claude/hooks/action-counter.sh +58 -58
- package/templates/.claude/hooks/git-guard.sh +62 -62
- package/templates/.claude/hooks/phase-gate.sh +10 -10
- package/templates/.claude/hooks/post-error-remind.sh +114 -114
- package/templates/.claude/hooks/pre-edit-guard.sh +100 -100
- package/templates/.claude/hooks/session-start.sh +44 -44
- package/templates/.claude/hooks/streak-breaker.sh +111 -111
- package/templates/.claude/rules/00-core-principles.md +16 -16
- package/templates/.claude/rules/01-debugging.md +32 -32
- package/templates/.claude/rules/02-before-edit.md +22 -22
- package/templates/.claude/rules/03-context-mgmt.md +44 -44
- package/templates/.claude/rules/04-no-mole-whacking.md +26 -26
- package/templates/.claude/rules/05-phase-discipline.md +15 -15
- package/templates/.claude/rules/06-multi-task.md +12 -12
- package/templates/.claude/rules/07-integrity.md +92 -92
- package/templates/.claude/rules/stacks/embedded.md +24 -24
- package/templates/.claude/rules/stacks/js-ts.md +21 -21
- package/templates/.claude/rules/stacks/mobile.md +16 -16
- package/templates/.claude/rules/stacks/python.md +20 -20
- package/templates/.claude/rules/stacks/rtl.md +24 -24
- package/templates/.claude/settings.json +84 -84
- package/templates/.claude/skills/commit/SKILL.md +40 -40
- package/templates/.claude/skills/evaluate/SKILL.md +57 -57
- package/templates/.claude/skills/investigate/SKILL.md +192 -192
- package/templates/.claude/skills/retro/SKILL.md +40 -40
- package/templates/.claude/skills/self-check/SKILL.md +112 -87
- package/templates/.claude/skills/summary/SKILL.md +48 -48
- package/templates/.claude/skills/think/SKILL.md +108 -108
- package/templates/CLAUDE.md +96 -96
- package/templates/docs/debug-log.md +48 -48
- package/templates/docs/progress.md +72 -72
- package/templates/memory/MEMORY.md +23 -23
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# post-error-remind.sh — PostToolUse / PostToolUseFailure hook
|
|
3
|
-
# Detects error patterns in Bash output and reminds Claude to follow debugging discipline
|
|
4
|
-
#
|
|
5
|
-
# NOTE: We use exit 2 intentionally for Post hooks.
|
|
6
|
-
# exit 0 is completely silent — no way to inject text from Post hooks.
|
|
7
|
-
# exit 2 + stderr is the ONLY mechanism to show reminders to Claude after tool execution.
|
|
8
|
-
# The tool has already executed — exit 2 doesn't block execution, it injects the message.
|
|
9
|
-
#
|
|
10
|
-
# Design principles:
|
|
11
|
-
# - Only match REAL error patterns, not the word "error" in any context
|
|
12
|
-
# - Skip non-Bash tools (Edit/Write/Read have their own error handling)
|
|
13
|
-
# - Skip truncated output (large output ≠ error)
|
|
14
|
-
# - Use specific patterns: line-start anchors, known failure formats
|
|
15
|
-
#
|
|
16
|
-
# exit 0 = silent pass (normal for Post hooks)
|
|
17
|
-
# exit 2 + stderr = inject reminder into Claude's context
|
|
18
|
-
|
|
19
|
-
RAW_INPUT=$(cat)
|
|
20
|
-
|
|
21
|
-
# Extract tool name and response
|
|
22
|
-
TOOL_NAME=""
|
|
23
|
-
OUTPUT=""
|
|
24
|
-
|
|
25
|
-
if command -v jq &>/dev/null; then
|
|
26
|
-
TOOL_NAME=$(echo "$RAW_INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
27
|
-
# PostToolUseFailure uses .error, PostToolUse uses .tool_response
|
|
28
|
-
OUTPUT=$(echo "$RAW_INPUT" | jq -r '.error // empty' 2>/dev/null)
|
|
29
|
-
if [ -z "$OUTPUT" ]; then
|
|
30
|
-
OUTPUT=$(echo "$RAW_INPUT" | jq -r '.tool_response.output // empty' 2>/dev/null)
|
|
31
|
-
fi
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
# Fallback if jq unavailable
|
|
35
|
-
if [ -z "$OUTPUT" ]; then
|
|
36
|
-
OUTPUT="$RAW_INPUT"
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# --- Early exits for known non-error situations ---
|
|
40
|
-
|
|
41
|
-
# Only check Bash tool output
|
|
42
|
-
if [ -n "$TOOL_NAME" ] && [ "$TOOL_NAME" != "Bash" ]; then
|
|
43
|
-
exit 0
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# Truncated output is not an error
|
|
47
|
-
if echo "$OUTPUT" | grep -q "Output too large"; then
|
|
48
|
-
exit 0
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
# Successful git operations
|
|
52
|
-
if echo "$OUTPUT" | grep -qE "^\[main |^\[master |^On branch |^nothing to commit|^Already up to date"; then
|
|
53
|
-
exit 0
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# --- Detect REAL error patterns ---
|
|
57
|
-
|
|
58
|
-
HAS_ERROR=false
|
|
59
|
-
ERROR_TYPE=""
|
|
60
|
-
|
|
61
|
-
# Build/compile errors (line-start or known formats)
|
|
62
|
-
if echo "$OUTPUT" | grep -qE "^ERROR |^\[ERROR\]|BUILD FAILURE|compilation error|cannot find symbol|package .* does not exist"; then
|
|
63
|
-
HAS_ERROR=true
|
|
64
|
-
ERROR_TYPE="build"
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# Test failures (Maven/Gradle/Jest specific formats)
|
|
68
|
-
if echo "$OUTPUT" | grep -qE "Tests run:.*Failures: [1-9]|Tests:.*failed|FAILED!|BUILD FAILURE.*test"; then
|
|
69
|
-
HAS_ERROR=true
|
|
70
|
-
ERROR_TYPE="test"
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
# Runtime crashes
|
|
74
|
-
if echo "$OUTPUT" | grep -qiE "segmentation fault|bus error|core dumped|SIGABRT|SIGSEGV|killed.*signal"; then
|
|
75
|
-
HAS_ERROR=true
|
|
76
|
-
ERROR_TYPE="crash"
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
# Shell errors (command failures)
|
|
80
|
-
if echo "$OUTPUT" | grep -qE "command not found|permission denied|cannot execute|not a git repository"; then
|
|
81
|
-
HAS_ERROR=true
|
|
82
|
-
ERROR_TYPE="system"
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
# Stack traces (Java, Python, Node)
|
|
86
|
-
if echo "$OUTPUT" | grep -qE "^Exception in thread|^Traceback \(most recent|^Caused by:.*Exception"; then
|
|
87
|
-
HAS_ERROR=true
|
|
88
|
-
ERROR_TYPE="exception"
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
# npm/yarn explicit failure
|
|
92
|
-
if echo "$OUTPUT" | grep -qE "^npm ERR!|^error .*ENOENT|FATAL ERROR"; then
|
|
93
|
-
HAS_ERROR=true
|
|
94
|
-
ERROR_TYPE="build"
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# --- Emit reminder if error detected ---
|
|
98
|
-
|
|
99
|
-
if [ "$HAS_ERROR" = true ]; then
|
|
100
|
-
REMINDER="Error encountered — debugging checklist: 1. Resist modifying code immediately 2. Fully understand the error message 3. List >=3 possible causes — label them hypotheses, not root cause 4. Record in docs/debug-log.md 5. Eliminate >=2 hypotheses with evidence before identifying root cause 6. Only then fix. Important: after seeing one error, the first explanation is a hypothesis, not a conclusion. Say 'possible cause' until you have elimination evidence."
|
|
101
|
-
|
|
102
|
-
if [ "$ERROR_TYPE" = "test" ]; then
|
|
103
|
-
REMINDER="$REMINDER Note: Test failure — before changing the test, first determine if it's a code bug or an outdated test."
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
if [ "$ERROR_TYPE" = "crash" ]; then
|
|
107
|
-
REMINDER="$REMINDER Note: Crash/segfault — may involve memory issues."
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
echo "$REMINDER" >&2
|
|
111
|
-
exit 2
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
exit 0
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# post-error-remind.sh — PostToolUse / PostToolUseFailure hook
|
|
3
|
+
# Detects error patterns in Bash output and reminds Claude to follow debugging discipline
|
|
4
|
+
#
|
|
5
|
+
# NOTE: We use exit 2 intentionally for Post hooks.
|
|
6
|
+
# exit 0 is completely silent — no way to inject text from Post hooks.
|
|
7
|
+
# exit 2 + stderr is the ONLY mechanism to show reminders to Claude after tool execution.
|
|
8
|
+
# The tool has already executed — exit 2 doesn't block execution, it injects the message.
|
|
9
|
+
#
|
|
10
|
+
# Design principles:
|
|
11
|
+
# - Only match REAL error patterns, not the word "error" in any context
|
|
12
|
+
# - Skip non-Bash tools (Edit/Write/Read have their own error handling)
|
|
13
|
+
# - Skip truncated output (large output ≠ error)
|
|
14
|
+
# - Use specific patterns: line-start anchors, known failure formats
|
|
15
|
+
#
|
|
16
|
+
# exit 0 = silent pass (normal for Post hooks)
|
|
17
|
+
# exit 2 + stderr = inject reminder into Claude's context
|
|
18
|
+
|
|
19
|
+
RAW_INPUT=$(cat)
|
|
20
|
+
|
|
21
|
+
# Extract tool name and response
|
|
22
|
+
TOOL_NAME=""
|
|
23
|
+
OUTPUT=""
|
|
24
|
+
|
|
25
|
+
if command -v jq &>/dev/null; then
|
|
26
|
+
TOOL_NAME=$(echo "$RAW_INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
27
|
+
# PostToolUseFailure uses .error, PostToolUse uses .tool_response
|
|
28
|
+
OUTPUT=$(echo "$RAW_INPUT" | jq -r '.error // empty' 2>/dev/null)
|
|
29
|
+
if [ -z "$OUTPUT" ]; then
|
|
30
|
+
OUTPUT=$(echo "$RAW_INPUT" | jq -r '.tool_response.output // empty' 2>/dev/null)
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Fallback if jq unavailable
|
|
35
|
+
if [ -z "$OUTPUT" ]; then
|
|
36
|
+
OUTPUT="$RAW_INPUT"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# --- Early exits for known non-error situations ---
|
|
40
|
+
|
|
41
|
+
# Only check Bash tool output
|
|
42
|
+
if [ -n "$TOOL_NAME" ] && [ "$TOOL_NAME" != "Bash" ]; then
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Truncated output is not an error
|
|
47
|
+
if echo "$OUTPUT" | grep -q "Output too large"; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Successful git operations
|
|
52
|
+
if echo "$OUTPUT" | grep -qE "^\[main |^\[master |^On branch |^nothing to commit|^Already up to date"; then
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# --- Detect REAL error patterns ---
|
|
57
|
+
|
|
58
|
+
HAS_ERROR=false
|
|
59
|
+
ERROR_TYPE=""
|
|
60
|
+
|
|
61
|
+
# Build/compile errors (line-start or known formats)
|
|
62
|
+
if echo "$OUTPUT" | grep -qE "^ERROR |^\[ERROR\]|BUILD FAILURE|compilation error|cannot find symbol|package .* does not exist"; then
|
|
63
|
+
HAS_ERROR=true
|
|
64
|
+
ERROR_TYPE="build"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Test failures (Maven/Gradle/Jest specific formats)
|
|
68
|
+
if echo "$OUTPUT" | grep -qE "Tests run:.*Failures: [1-9]|Tests:.*failed|FAILED!|BUILD FAILURE.*test"; then
|
|
69
|
+
HAS_ERROR=true
|
|
70
|
+
ERROR_TYPE="test"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Runtime crashes
|
|
74
|
+
if echo "$OUTPUT" | grep -qiE "segmentation fault|bus error|core dumped|SIGABRT|SIGSEGV|killed.*signal"; then
|
|
75
|
+
HAS_ERROR=true
|
|
76
|
+
ERROR_TYPE="crash"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Shell errors (command failures)
|
|
80
|
+
if echo "$OUTPUT" | grep -qE "command not found|permission denied|cannot execute|not a git repository"; then
|
|
81
|
+
HAS_ERROR=true
|
|
82
|
+
ERROR_TYPE="system"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Stack traces (Java, Python, Node)
|
|
86
|
+
if echo "$OUTPUT" | grep -qE "^Exception in thread|^Traceback \(most recent|^Caused by:.*Exception"; then
|
|
87
|
+
HAS_ERROR=true
|
|
88
|
+
ERROR_TYPE="exception"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# npm/yarn explicit failure
|
|
92
|
+
if echo "$OUTPUT" | grep -qE "^npm ERR!|^error .*ENOENT|FATAL ERROR"; then
|
|
93
|
+
HAS_ERROR=true
|
|
94
|
+
ERROR_TYPE="build"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# --- Emit reminder if error detected ---
|
|
98
|
+
|
|
99
|
+
if [ "$HAS_ERROR" = true ]; then
|
|
100
|
+
REMINDER="Error encountered — debugging checklist: 1. Resist modifying code immediately 2. Fully understand the error message 3. List >=3 possible causes — label them hypotheses, not root cause 4. Record in docs/debug-log.md 5. Eliminate >=2 hypotheses with evidence before identifying root cause 6. Only then fix. Important: after seeing one error, the first explanation is a hypothesis, not a conclusion. Say 'possible cause' until you have elimination evidence."
|
|
101
|
+
|
|
102
|
+
if [ "$ERROR_TYPE" = "test" ]; then
|
|
103
|
+
REMINDER="$REMINDER Note: Test failure — before changing the test, first determine if it's a code bug or an outdated test."
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ "$ERROR_TYPE" = "crash" ]; then
|
|
107
|
+
REMINDER="$REMINDER Note: Crash/segfault — may involve memory issues."
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
echo "$REMINDER" >&2
|
|
111
|
+
exit 2
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
exit 0
|
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# pre-edit-guard.sh — PreToolUse hook
|
|
3
|
-
# Checks that Claude has finished debugging before editing source code
|
|
4
|
-
#
|
|
5
|
-
# Exit 0 + no output = allow edit
|
|
6
|
-
# Exit 2 + stderr = block edit, stderr shown to Claude
|
|
7
|
-
|
|
8
|
-
# Read the tool input from stdin (JSON)
|
|
9
|
-
INPUT=$(cat)
|
|
10
|
-
|
|
11
|
-
# Extract fields
|
|
12
|
-
if command -v jq &>/dev/null; then
|
|
13
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
14
|
-
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
15
|
-
else
|
|
16
|
-
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":\s*"[^"]*"' | head -1 | sed 's/"file_path":\s*"//;s/"//')
|
|
17
|
-
CWD=$(echo "$INPUT" | grep -o '"cwd":\s*"[^"]*"' | head -1 | sed 's/"cwd":\s*"//;s/"//')
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# Extract just the filename for pattern matching (avoid false matches on directory names)
|
|
21
|
-
BASENAME=$(basename "$FILE_PATH")
|
|
22
|
-
|
|
23
|
-
# Allow edits to docs/
|
|
24
|
-
# Note: use -E (extended regex) for portability — BSD grep (macOS) doesn't support \| in basic mode
|
|
25
|
-
if echo "$FILE_PATH" | grep -qE "^docs/|/docs/"; then
|
|
26
|
-
exit 0
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# Allow edits to test files (match filename only, not directory path)
|
|
30
|
-
if echo "$BASENAME" | grep -qiE "test|spec"; then
|
|
31
|
-
exit 0
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
# Allow edits to config/meta files
|
|
35
|
-
if echo "$BASENAME" | grep -qiE "\.(md|json|yaml|yml|toml|cfg|ini)$"; then
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# Check for unresolved hypotheses in debug-log.md
|
|
40
|
-
DEBUG_LOG=""
|
|
41
|
-
if [ -n "$CWD" ] && [ -f "$CWD/docs/debug-log.md" ]; then
|
|
42
|
-
DEBUG_LOG="$CWD/docs/debug-log.md"
|
|
43
|
-
elif [ -f "docs/debug-log.md" ]; then
|
|
44
|
-
DEBUG_LOG="docs/debug-log.md"
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
if [ -n "$DEBUG_LOG" ]; then
|
|
48
|
-
# Count only in actual table rows, strip HTML comments (both single-line and multi-line)
|
|
49
|
-
CLEAN=$(awk '/<!--.*-->/{next} /<!--/{skip=1} /-->/{skip=0;next} !skip{print}' "$DEBUG_LOG" 2>/dev/null)
|
|
50
|
-
PENDING=$(echo "$CLEAN" | grep -c '| pending |' 2>/dev/null) || PENDING=0
|
|
51
|
-
CONFIRMED=$(echo "$CLEAN" | grep -c '| confirmed |' 2>/dev/null) || CONFIRMED=0
|
|
52
|
-
|
|
53
|
-
if [ "$PENDING" -gt "$CONFIRMED" ] 2>/dev/null; then
|
|
54
|
-
cat >&2 <<EOF
|
|
55
|
-
docs/debug-log.md has $((PENDING - CONFIRMED)) unverified hypotheses.
|
|
56
|
-
Please complete the debugging process (verify or eliminate hypotheses) before editing source code.
|
|
57
|
-
If you have confirmed the root cause, update debug-log.md first.
|
|
58
|
-
EOF
|
|
59
|
-
exit 2
|
|
60
|
-
fi
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# ─── Large diff warning ───
|
|
64
|
-
HAS_JQ=false
|
|
65
|
-
command -v jq &>/dev/null && HAS_JQ=true
|
|
66
|
-
|
|
67
|
-
if [ "$HAS_JQ" = true ]; then
|
|
68
|
-
NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
|
|
69
|
-
OLD_STRING=$(echo "$INPUT" | jq -r '.tool_input.old_string // ""')
|
|
70
|
-
NEW_LINES=$(echo "$NEW_STRING" | wc -l | tr -d ' ')
|
|
71
|
-
OLD_LINES=$(echo "$OLD_STRING" | wc -l | tr -d ' ')
|
|
72
|
-
DIFF_LINES=$((NEW_LINES > OLD_LINES ? NEW_LINES : OLD_LINES))
|
|
73
|
-
if [ "$DIFF_LINES" -gt 200 ]; then
|
|
74
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"LARGE EDIT NOTE: This edit involves ${DIFF_LINES} lines. Consider: is this the minimal change needed? Could it be broken into smaller, more focused edits?\"}}"
|
|
75
|
-
exit 0
|
|
76
|
-
fi
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
# ─── New tool/script detection ───
|
|
80
|
-
# When creating a script file via Write, remind to register in CLAUDE.md
|
|
81
|
-
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
82
|
-
if [ "$TOOL_NAME" = "Write" ] && echo "$BASENAME" | grep -qiE "\.(sh|py|js|ts|rb|pl)$"; then
|
|
83
|
-
# Check if file already exists (new file = tool creation)
|
|
84
|
-
FULL_PATH="$FILE_PATH"
|
|
85
|
-
if [ ! -f "$FULL_PATH" ]; then
|
|
86
|
-
cat <<JSONEOF
|
|
87
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"NEW SCRIPT DETECTED: You are creating $BASENAME. If this is a reusable tool/helper, register it in CLAUDE.md under 'Project Tools' now (path, purpose, usage, date) — capturing it while context is fresh saves time later."}}
|
|
88
|
-
JSONEOF
|
|
89
|
-
exit 0
|
|
90
|
-
fi
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
# ─── Bug-fix sanity check ───
|
|
94
|
-
# Inject a lightweight reminder on source file edits:
|
|
95
|
-
# "If this is a bug fix, have you eliminated alternative hypotheses?"
|
|
96
|
-
# Uses additionalContext (non-blocking) so it doesn't slow down normal edits.
|
|
97
|
-
cat <<JSONEOF
|
|
98
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"If this edit is a bug fix: have you listed >=3 possible causes and eliminated >=2 with evidence? Thorough elimination before fixing prevents wasted cycles. Use 'possible cause' until elimination evidence confirms the root cause."}}
|
|
99
|
-
JSONEOF
|
|
100
|
-
exit 0
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# pre-edit-guard.sh — PreToolUse hook
|
|
3
|
+
# Checks that Claude has finished debugging before editing source code
|
|
4
|
+
#
|
|
5
|
+
# Exit 0 + no output = allow edit
|
|
6
|
+
# Exit 2 + stderr = block edit, stderr shown to Claude
|
|
7
|
+
|
|
8
|
+
# Read the tool input from stdin (JSON)
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
# Extract fields
|
|
12
|
+
if command -v jq &>/dev/null; then
|
|
13
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
14
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
15
|
+
else
|
|
16
|
+
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":\s*"[^"]*"' | head -1 | sed 's/"file_path":\s*"//;s/"//')
|
|
17
|
+
CWD=$(echo "$INPUT" | grep -o '"cwd":\s*"[^"]*"' | head -1 | sed 's/"cwd":\s*"//;s/"//')
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Extract just the filename for pattern matching (avoid false matches on directory names)
|
|
21
|
+
BASENAME=$(basename "$FILE_PATH")
|
|
22
|
+
|
|
23
|
+
# Allow edits to docs/
|
|
24
|
+
# Note: use -E (extended regex) for portability — BSD grep (macOS) doesn't support \| in basic mode
|
|
25
|
+
if echo "$FILE_PATH" | grep -qE "^docs/|/docs/"; then
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Allow edits to test files (match filename only, not directory path)
|
|
30
|
+
if echo "$BASENAME" | grep -qiE "test|spec"; then
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Allow edits to config/meta files
|
|
35
|
+
if echo "$BASENAME" | grep -qiE "\.(md|json|yaml|yml|toml|cfg|ini)$"; then
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Check for unresolved hypotheses in debug-log.md
|
|
40
|
+
DEBUG_LOG=""
|
|
41
|
+
if [ -n "$CWD" ] && [ -f "$CWD/docs/debug-log.md" ]; then
|
|
42
|
+
DEBUG_LOG="$CWD/docs/debug-log.md"
|
|
43
|
+
elif [ -f "docs/debug-log.md" ]; then
|
|
44
|
+
DEBUG_LOG="docs/debug-log.md"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if [ -n "$DEBUG_LOG" ]; then
|
|
48
|
+
# Count only in actual table rows, strip HTML comments (both single-line and multi-line)
|
|
49
|
+
CLEAN=$(awk '/<!--.*-->/{next} /<!--/{skip=1} /-->/{skip=0;next} !skip{print}' "$DEBUG_LOG" 2>/dev/null)
|
|
50
|
+
PENDING=$(echo "$CLEAN" | grep -c '| pending |' 2>/dev/null) || PENDING=0
|
|
51
|
+
CONFIRMED=$(echo "$CLEAN" | grep -c '| confirmed |' 2>/dev/null) || CONFIRMED=0
|
|
52
|
+
|
|
53
|
+
if [ "$PENDING" -gt "$CONFIRMED" ] 2>/dev/null; then
|
|
54
|
+
cat >&2 <<EOF
|
|
55
|
+
docs/debug-log.md has $((PENDING - CONFIRMED)) unverified hypotheses.
|
|
56
|
+
Please complete the debugging process (verify or eliminate hypotheses) before editing source code.
|
|
57
|
+
If you have confirmed the root cause, update debug-log.md first.
|
|
58
|
+
EOF
|
|
59
|
+
exit 2
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# ─── Large diff warning ───
|
|
64
|
+
HAS_JQ=false
|
|
65
|
+
command -v jq &>/dev/null && HAS_JQ=true
|
|
66
|
+
|
|
67
|
+
if [ "$HAS_JQ" = true ]; then
|
|
68
|
+
NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
|
|
69
|
+
OLD_STRING=$(echo "$INPUT" | jq -r '.tool_input.old_string // ""')
|
|
70
|
+
NEW_LINES=$(echo "$NEW_STRING" | wc -l | tr -d ' ')
|
|
71
|
+
OLD_LINES=$(echo "$OLD_STRING" | wc -l | tr -d ' ')
|
|
72
|
+
DIFF_LINES=$((NEW_LINES > OLD_LINES ? NEW_LINES : OLD_LINES))
|
|
73
|
+
if [ "$DIFF_LINES" -gt 200 ]; then
|
|
74
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"LARGE EDIT NOTE: This edit involves ${DIFF_LINES} lines. Consider: is this the minimal change needed? Could it be broken into smaller, more focused edits?\"}}"
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# ─── New tool/script detection ───
|
|
80
|
+
# When creating a script file via Write, remind to register in CLAUDE.md
|
|
81
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
82
|
+
if [ "$TOOL_NAME" = "Write" ] && echo "$BASENAME" | grep -qiE "\.(sh|py|js|ts|rb|pl)$"; then
|
|
83
|
+
# Check if file already exists (new file = tool creation)
|
|
84
|
+
FULL_PATH="$FILE_PATH"
|
|
85
|
+
if [ ! -f "$FULL_PATH" ]; then
|
|
86
|
+
cat <<JSONEOF
|
|
87
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"NEW SCRIPT DETECTED: You are creating $BASENAME. If this is a reusable tool/helper, register it in CLAUDE.md under 'Project Tools' now (path, purpose, usage, date) — capturing it while context is fresh saves time later."}}
|
|
88
|
+
JSONEOF
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ─── Bug-fix sanity check ───
|
|
94
|
+
# Inject a lightweight reminder on source file edits:
|
|
95
|
+
# "If this is a bug fix, have you eliminated alternative hypotheses?"
|
|
96
|
+
# Uses additionalContext (non-blocking) so it doesn't slow down normal edits.
|
|
97
|
+
cat <<JSONEOF
|
|
98
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"If this edit is a bug fix: have you listed >=3 possible causes and eliminated >=2 with evidence? Thorough elimination before fixing prevents wasted cycles. Use 'possible cause' until elimination evidence confirms the root cause."}}
|
|
99
|
+
JSONEOF
|
|
100
|
+
exit 0
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# cc-discipline: SessionStart hook
|
|
3
|
-
# Injects project state + discipline reminders into Claude's context.
|
|
4
|
-
# stdout → context (Claude can see and act on it)
|
|
5
|
-
# Fires on: startup, resume, clear, compact
|
|
6
|
-
|
|
7
|
-
# Reset action counter for this session
|
|
8
|
-
INPUT=$(cat)
|
|
9
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
|
|
10
|
-
if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ]; then
|
|
11
|
-
rm -f "/tmp/cc-discipline-${SESSION_ID}/action-count"
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
# Read project state
|
|
15
|
-
PROGRESS=""
|
|
16
|
-
if [ -f "docs/progress.md" ]; then
|
|
17
|
-
PROGRESS=$(tail -20 docs/progress.md)
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
cat <<'HEADER'
|
|
21
|
-
[cc-discipline] Session initialized.
|
|
22
|
-
HEADER
|
|
23
|
-
|
|
24
|
-
if [ -n "$PROGRESS" ]; then
|
|
25
|
-
cat <<EOF
|
|
26
|
-
|
|
27
|
-
Project state (from docs/progress.md):
|
|
28
|
-
$PROGRESS
|
|
29
|
-
|
|
30
|
-
Verify project status by reading files or asking — don't assume beyond what is stated above.
|
|
31
|
-
EOF
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
cat <<'EOF'
|
|
35
|
-
|
|
36
|
-
Reminders:
|
|
37
|
-
- /self-check available for periodic monitoring. For complex tasks: /loop 10m /self-check
|
|
38
|
-
- Before editing: root cause identified? scope respected? change recorded?
|
|
39
|
-
- 3 consecutive failures → pause and regroup with the user
|
|
40
|
-
- Confirm the approach with the user before starting implementation
|
|
41
|
-
- Verify project state (phase, status, dependencies) by reading files or asking
|
|
42
|
-
EOF
|
|
43
|
-
|
|
44
|
-
exit 0
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# cc-discipline: SessionStart hook
|
|
3
|
+
# Injects project state + discipline reminders into Claude's context.
|
|
4
|
+
# stdout → context (Claude can see and act on it)
|
|
5
|
+
# Fires on: startup, resume, clear, compact
|
|
6
|
+
|
|
7
|
+
# Reset action counter for this session
|
|
8
|
+
INPUT=$(cat)
|
|
9
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
|
|
10
|
+
if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ]; then
|
|
11
|
+
rm -f "/tmp/cc-discipline-${SESSION_ID}/action-count"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Read project state
|
|
15
|
+
PROGRESS=""
|
|
16
|
+
if [ -f "docs/progress.md" ]; then
|
|
17
|
+
PROGRESS=$(tail -20 docs/progress.md)
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
cat <<'HEADER'
|
|
21
|
+
[cc-discipline] Session initialized.
|
|
22
|
+
HEADER
|
|
23
|
+
|
|
24
|
+
if [ -n "$PROGRESS" ]; then
|
|
25
|
+
cat <<EOF
|
|
26
|
+
|
|
27
|
+
Project state (from docs/progress.md):
|
|
28
|
+
$PROGRESS
|
|
29
|
+
|
|
30
|
+
Verify project status by reading files or asking — don't assume beyond what is stated above.
|
|
31
|
+
EOF
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
cat <<'EOF'
|
|
35
|
+
|
|
36
|
+
Reminders:
|
|
37
|
+
- /self-check available for periodic monitoring. For complex tasks: /loop 10m /self-check
|
|
38
|
+
- Before editing: root cause identified? scope respected? change recorded?
|
|
39
|
+
- 3 consecutive failures → pause and regroup with the user
|
|
40
|
+
- Confirm the approach with the user before starting implementation
|
|
41
|
+
- Verify project state (phase, status, dependencies) by reading files or asking
|
|
42
|
+
EOF
|
|
43
|
+
|
|
44
|
+
exit 0
|