cc-discipline 2.0.0

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/README.zh-CN.md +207 -0
  4. package/bin/cli.sh +96 -0
  5. package/global/CLAUDE.md +43 -0
  6. package/init.sh +486 -0
  7. package/lib/doctor.sh +145 -0
  8. package/lib/stack-remove.sh +68 -0
  9. package/lib/status.sh +95 -0
  10. package/package.json +34 -0
  11. package/templates/.claude/agents/investigator.md +44 -0
  12. package/templates/.claude/agents/reviewer.md +46 -0
  13. package/templates/.claude/hooks/action-counter.sh +28 -0
  14. package/templates/.claude/hooks/phase-gate.sh +10 -0
  15. package/templates/.claude/hooks/post-error-remind.sh +109 -0
  16. package/templates/.claude/hooks/pre-edit-guard.sh +79 -0
  17. package/templates/.claude/hooks/session-start.sh +43 -0
  18. package/templates/.claude/hooks/streak-breaker.sh +111 -0
  19. package/templates/.claude/rules/00-core-principles.md +12 -0
  20. package/templates/.claude/rules/01-debugging.md +22 -0
  21. package/templates/.claude/rules/02-before-edit.md +11 -0
  22. package/templates/.claude/rules/03-context-mgmt.md +19 -0
  23. package/templates/.claude/rules/04-no-mole-whacking.md +26 -0
  24. package/templates/.claude/rules/05-phase-discipline.md +13 -0
  25. package/templates/.claude/rules/06-multi-task.md +11 -0
  26. package/templates/.claude/rules/07-integrity.md +81 -0
  27. package/templates/.claude/rules/stacks/embedded.md +24 -0
  28. package/templates/.claude/rules/stacks/js-ts.md +21 -0
  29. package/templates/.claude/rules/stacks/mobile.md +16 -0
  30. package/templates/.claude/rules/stacks/python.md +20 -0
  31. package/templates/.claude/rules/stacks/rtl.md +24 -0
  32. package/templates/.claude/settings.json +75 -0
  33. package/templates/.claude/skills/commit/SKILL.md +40 -0
  34. package/templates/.claude/skills/evaluate/SKILL.md +57 -0
  35. package/templates/.claude/skills/self-check/SKILL.md +62 -0
  36. package/templates/CLAUDE.md +80 -0
  37. package/templates/docs/debug-log.md +48 -0
  38. package/templates/docs/progress.md +29 -0
  39. package/templates/memory/MEMORY.md +23 -0
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ # cc-discipline remove-stack — remove stack rules
3
+ set -e
4
+
5
+ GREEN='\033[0;32m'
6
+ YELLOW='\033[1;33m'
7
+ RED='\033[0;31m'
8
+ NC='\033[0m'
9
+
10
+ if [ -z "$1" ]; then
11
+ echo "Usage: cc-discipline remove-stack <numbers>"
12
+ echo " e.g.: cc-discipline remove-stack 1 2"
13
+ echo ""
14
+ echo "Stacks: 1=RTL 2=Embedded 3=Python 4=JS/TS 5=Mobile"
15
+ exit 1
16
+ fi
17
+
18
+ if [ ! -f ".claude/.cc-discipline-version" ]; then
19
+ echo -e "${RED}cc-discipline is not installed in this project.${NC}"
20
+ exit 1
21
+ fi
22
+
23
+ for choice in "$@"; do
24
+ case $choice in
25
+ 1)
26
+ FILE=".claude/rules/stacks/rtl.md"
27
+ NAME="RTL"
28
+ ;;
29
+ 2)
30
+ FILE=".claude/rules/stacks/embedded.md"
31
+ NAME="Embedded"
32
+ ;;
33
+ 3)
34
+ FILE=".claude/rules/stacks/python.md"
35
+ NAME="Python"
36
+ ;;
37
+ 4)
38
+ FILE=".claude/rules/stacks/js-ts.md"
39
+ NAME="JS/TS"
40
+ ;;
41
+ 5)
42
+ FILE=".claude/rules/stacks/mobile.md"
43
+ NAME="Mobile"
44
+ ;;
45
+ *)
46
+ echo -e "${YELLOW}Unknown stack: $choice (valid: 1-5)${NC}"
47
+ continue
48
+ ;;
49
+ esac
50
+
51
+ if [ -f "$FILE" ]; then
52
+ rm "$FILE"
53
+ echo -e "${GREEN}✓${NC} Removed $NAME stack ($FILE)"
54
+ else
55
+ echo -e "${YELLOW}!${NC} $NAME stack not installed, skipping"
56
+ fi
57
+ done
58
+
59
+ echo ""
60
+ echo "Done. Remaining stacks:"
61
+ REMAINING=$(ls .claude/rules/stacks/*.md 2>/dev/null || true)
62
+ if [ -n "$REMAINING" ]; then
63
+ for f in $REMAINING; do
64
+ echo " - $(basename "$f" .md)"
65
+ done
66
+ else
67
+ echo " (none)"
68
+ fi
package/lib/status.sh ADDED
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+ # cc-discipline status — show what's installed in the current project
3
+ set -e
4
+
5
+ GREEN='\033[0;32m'
6
+ YELLOW='\033[1;33m'
7
+ RED='\033[0;31m'
8
+ NC='\033[0m'
9
+
10
+ echo "cc-discipline status"
11
+ echo "──────────────────────"
12
+ echo ""
13
+
14
+ # Version
15
+ if [ -f ".claude/.cc-discipline-version" ]; then
16
+ echo -e "Version: ${GREEN}$(cat .claude/.cc-discipline-version)${NC}"
17
+ else
18
+ echo -e "Version: ${RED}not installed${NC}"
19
+ echo ""
20
+ echo "Run 'npx cc-discipline init' to install."
21
+ exit 0
22
+ fi
23
+
24
+ # Stacks
25
+ echo -n "Stacks: "
26
+ STACKS=""
27
+ [ -f ".claude/rules/stacks/rtl.md" ] && STACKS="${STACKS}RTL "
28
+ [ -f ".claude/rules/stacks/embedded.md" ] && STACKS="${STACKS}Embedded "
29
+ [ -f ".claude/rules/stacks/python.md" ] && STACKS="${STACKS}Python "
30
+ [ -f ".claude/rules/stacks/js-ts.md" ] && STACKS="${STACKS}JS/TS "
31
+ [ -f ".claude/rules/stacks/mobile.md" ] && STACKS="${STACKS}Mobile "
32
+ if [ -n "$STACKS" ]; then
33
+ echo -e "${GREEN}${STACKS}${NC}"
34
+ else
35
+ echo -e "${YELLOW}General (no stack rules)${NC}"
36
+ fi
37
+
38
+ # Core rules
39
+ CORE_COUNT=0
40
+ for f in .claude/rules/0*.md; do
41
+ [ -f "$f" ] && CORE_COUNT=$((CORE_COUNT + 1))
42
+ done
43
+ echo -e "Rules: ${GREEN}${CORE_COUNT} core rules${NC}"
44
+
45
+ # Hooks
46
+ echo -n "Hooks: "
47
+ HOOKS=""
48
+ [ -f ".claude/hooks/pre-edit-guard.sh" ] && HOOKS="${HOOKS}pre-edit-guard "
49
+ [ -f ".claude/hooks/streak-breaker.sh" ] && HOOKS="${HOOKS}streak-breaker "
50
+ [ -f ".claude/hooks/post-error-remind.sh" ] && HOOKS="${HOOKS}post-error-remind "
51
+ [ -f ".claude/hooks/session-start.sh" ] && HOOKS="${HOOKS}session-start "
52
+ [ -f ".claude/hooks/phase-gate.sh" ] && HOOKS="${HOOKS}phase-gate "
53
+ [ -f ".claude/hooks/action-counter.sh" ] && HOOKS="${HOOKS}action-counter "
54
+ HOOK_COUNT=$(echo "$HOOKS" | wc -w | tr -d ' ')
55
+ echo -e "${GREEN}${HOOK_COUNT}/6${NC} (${HOOKS% })"
56
+
57
+ # Agents
58
+ echo -n "Agents: "
59
+ AGENTS=""
60
+ [ -f ".claude/agents/reviewer.md" ] && AGENTS="${AGENTS}reviewer "
61
+ [ -f ".claude/agents/investigator.md" ] && AGENTS="${AGENTS}investigator "
62
+ AGENT_COUNT=$(echo "$AGENTS" | wc -w | tr -d ' ')
63
+ echo -e "${GREEN}${AGENT_COUNT}/2${NC} (${AGENTS% })"
64
+
65
+ # Skills
66
+ echo -n "Skills: "
67
+ SKILLS=""
68
+ [ -d ".claude/skills/commit" ] && SKILLS="${SKILLS}/commit "
69
+ [ -d ".claude/skills/self-check" ] && SKILLS="${SKILLS}/self-check "
70
+ [ -d ".claude/skills/evaluate" ] && SKILLS="${SKILLS}/evaluate "
71
+ SKILL_COUNT=$(echo "$SKILLS" | wc -w | tr -d ' ')
72
+ echo -e "${GREEN}${SKILL_COUNT}/3${NC} (${SKILLS% })"
73
+
74
+ # Settings
75
+ echo -n "Settings: "
76
+ if [ -f ".claude/settings.json" ]; then
77
+ if command -v jq &>/dev/null; then
78
+ HOOK_EVENTS=$(jq -r '.hooks // {} | keys[]' .claude/settings.json 2>/dev/null | tr '\n' ' ')
79
+ echo -e "${GREEN}registered${NC} (events: ${HOOK_EVENTS% })"
80
+ else
81
+ echo -e "${GREEN}exists${NC}"
82
+ fi
83
+ else
84
+ echo -e "${RED}missing${NC}"
85
+ fi
86
+
87
+ # CLAUDE.md
88
+ echo -n "CLAUDE.md: "
89
+ if [ -f "CLAUDE.md" ]; then
90
+ echo -e "${GREEN}exists${NC}"
91
+ else
92
+ echo -e "${YELLOW}missing${NC}"
93
+ fi
94
+
95
+ echo ""
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "cc-discipline",
3
+ "version": "2.0.0",
4
+ "description": "Discipline framework for Claude Code — rules, hooks, and agents that keep AI on track",
5
+ "bin": {
6
+ "cc-discipline": "./bin/cli.sh"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "lib/",
11
+ "init.sh",
12
+ "templates/",
13
+ "global/",
14
+ "LICENSE",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "claude",
19
+ "claude-code",
20
+ "ai-discipline",
21
+ "hooks",
22
+ "rules",
23
+ "agents",
24
+ "code-quality",
25
+ "developer-tools"
26
+ ],
27
+ "author": "techhu",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/techhu/cc-discipline"
32
+ },
33
+ "homepage": "https://github.com/techhu/cc-discipline#readme"
34
+ }
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: investigator
3
+ description: "Code investigator. Invoke when deep codebase research is needed. Explores in an independent context, returns a structured summary without polluting the main conversation."
4
+ model: sonnet
5
+ tools: Read, Grep, Glob, Bash
6
+ ---
7
+
8
+ You are a code investigator. Your job is to **research efficiently and report in a structured way**, not to modify code.
9
+
10
+ ## Workflow
11
+
12
+ 1. After receiving a research task, first plan your search strategy (what to search, where, what you expect to find)
13
+ 2. Search systematically, don't miss anything (use Grep for references, Glob for files, Read for content)
14
+ 3. Compile findings into a structured report
15
+
16
+ ## Report Format
17
+
18
+ ```
19
+ INVESTIGATION REPORT: [task title]
20
+
21
+ ## Key Findings
22
+ - [Most important finding, one sentence]
23
+ - [Second most important finding]
24
+
25
+ ## Relevant Files
26
+ | File | Role | Key Content |
27
+ |------|------|-------------|
28
+ | path/to/file | [what it does] | [key code/logic description] |
29
+
30
+ ## Dependencies
31
+ [Describe inter-module call relationships and data flow]
32
+
33
+ ## Potential Risks
34
+ [Issues discovered during research that need attention]
35
+
36
+ ## Recommended Next Steps
37
+ [Based on findings, suggest how the main conversation should proceed]
38
+ ```
39
+
40
+ ## Code of Conduct
41
+ - Search thoroughly — better to read a few extra files than to miss something
42
+ - Report concisely — the main conversation only needs conclusions and key information, not the process of what you read
43
+ - Proactively report problems — even if outside the task scope
44
+ - You have Bash permission but only for read-only operations (grep/find/cat etc.), do not modify any files
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: reviewer
3
+ description: "Code reviewer. Invoke after a modification plan is decided but before execution. Reviews plan soundness in an independent context."
4
+ model: sonnet
5
+ tools: Read, Grep, Glob
6
+ ---
7
+
8
+ You are a strict code reviewer. Your job is to **challenge and gatekeep**, not to agree.
9
+
10
+ ## After receiving a modification plan, you must answer each of the following:
11
+
12
+ ### 1. Root Cause vs Symptom
13
+ Does this plan address the root cause or is it patching a symptom?
14
+ If it's symptom patching, point out what the real root cause might be.
15
+
16
+ ### 2. Alternatives
17
+ Is there a simpler, safer alternative approach?
18
+ List at least one alternative, even if you think the original plan is viable.
19
+
20
+ ### 3. Impact Scope
21
+ What could this change break? List all potentially affected modules and features.
22
+ Pay special attention to: interface changes, state management changes, data format changes.
23
+
24
+ ### 4. Edge Cases
25
+ Are there missing edge cases? Null values? Concurrency? Extreme inputs? Resource exhaustion?
26
+
27
+ ### 5. Reversibility
28
+ If this change causes problems in production, can it be quickly rolled back?
29
+
30
+ ## Output Format
31
+
32
+ ```
33
+ Review verdict: APPROVE / APPROVE WITH CHANGES / REJECT
34
+
35
+ Root cause analysis: [your judgment]
36
+ Alternatives: [at least one]
37
+ Risk points: [list them]
38
+ Missing edge cases: [list them, or "none found"]
39
+ Recommendations: [specific improvement suggestions]
40
+ ```
41
+
42
+ ## Code of Conduct
43
+ - Don't be polite, don't be encouraging — just be honest
44
+ - If the plan has obvious issues, say "REJECT" directly
45
+ - Better to be over-cautious than to miss potential problems
46
+ - You have no permission to modify code — you can only provide review opinions
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # cc-discipline: Action counter for periodic self-check
3
+ # Counts action-type tool calls per session, injects self-check every N actions.
4
+ # PreToolUse on Edit|Write|MultiEdit|Bash|Agent — additionalContext injection.
5
+
6
+ THRESHOLD=25
7
+
8
+ INPUT=$(cat)
9
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
10
+ if [ -z "$SESSION_ID" ] || [ "$SESSION_ID" = "null" ]; then
11
+ SESSION_ID="unknown"
12
+ fi
13
+
14
+ COUNT_DIR="/tmp/cc-discipline-${SESSION_ID}"
15
+ COUNT_FILE="${COUNT_DIR}/action-count"
16
+ mkdir -p "$COUNT_DIR"
17
+
18
+ COUNT=$(cat "$COUNT_FILE" 2>/dev/null) || COUNT=0
19
+ COUNT=$((COUNT + 1))
20
+ echo "$COUNT" > "$COUNT_FILE"
21
+
22
+ if [ $((COUNT % THRESHOLD)) -eq 0 ]; then
23
+ cat <<JSONEOF
24
+ {"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"AUTO SELF-CHECK (#${COUNT} actions): Pause and verify: (1) Am I still serving the user's original request, or have I drifted? (2) Am I fixing the same thing repeatedly (mole-whacking)? (3) Have I claimed anything as 'verified' without actually running it? (4) Am I making changes the user didn't ask for? (5) Have I updated docs/progress.md with current status and completed milestones? If progress.md is stale, update it NOW before continuing. If ANY answer is concerning, STOP and report to the user before continuing."}}
25
+ JSONEOF
26
+ fi
27
+
28
+ exit 0
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # cc-discipline: Phase gate hook
3
+ # Injects reminder when Claude attempts to exit plan mode.
4
+ # PreToolUse on ExitPlanMode — additionalContext injection, non-blocking.
5
+
6
+ cat <<'JSONEOF'
7
+ {"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"PHASE GATE REMINDER: You are about to exit plan mode and begin implementation. Before proceeding, confirm: (1) Has the user EXPLICITLY approved this plan? Look for words like 'approved', 'go ahead', 'ok', 'do it', 'yes'. (2) Are all key assumptions listed and verified? (3) Is the scope clearly bounded? If the user has NOT explicitly approved, STOP and present your plan for review first."}}
8
+ JSONEOF
9
+
10
+ exit 0
@@ -0,0 +1,109 @@
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
+ # Design principles:
6
+ # - Only match REAL error patterns, not the word "error" in any context
7
+ # - Skip non-Bash tools (Edit/Write/Read have their own error handling)
8
+ # - Skip truncated output (large output ≠ error)
9
+ # - Use specific patterns: line-start anchors, known failure formats
10
+ #
11
+ # exit 0 = silent pass (normal for Post hooks)
12
+ # exit 2 + stderr = inject reminder into Claude's context
13
+
14
+ RAW_INPUT=$(cat)
15
+
16
+ # Extract tool name and response
17
+ TOOL_NAME=""
18
+ OUTPUT=""
19
+
20
+ if command -v jq &>/dev/null; then
21
+ TOOL_NAME=$(echo "$RAW_INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
22
+ # PostToolUseFailure uses .error, PostToolUse uses .tool_response
23
+ OUTPUT=$(echo "$RAW_INPUT" | jq -r '.error // empty' 2>/dev/null)
24
+ if [ -z "$OUTPUT" ]; then
25
+ OUTPUT=$(echo "$RAW_INPUT" | jq -r '.tool_response.output // empty' 2>/dev/null)
26
+ fi
27
+ fi
28
+
29
+ # Fallback if jq unavailable
30
+ if [ -z "$OUTPUT" ]; then
31
+ OUTPUT="$RAW_INPUT"
32
+ fi
33
+
34
+ # --- Early exits for known non-error situations ---
35
+
36
+ # Only check Bash tool output
37
+ if [ -n "$TOOL_NAME" ] && [ "$TOOL_NAME" != "Bash" ]; then
38
+ exit 0
39
+ fi
40
+
41
+ # Truncated output is not an error
42
+ if echo "$OUTPUT" | grep -q "Output too large"; then
43
+ exit 0
44
+ fi
45
+
46
+ # Successful git operations
47
+ if echo "$OUTPUT" | grep -qE "^\[main |^\[master |^On branch |^nothing to commit|^Already up to date"; then
48
+ exit 0
49
+ fi
50
+
51
+ # --- Detect REAL error patterns ---
52
+
53
+ HAS_ERROR=false
54
+ ERROR_TYPE=""
55
+
56
+ # Build/compile errors (line-start or known formats)
57
+ if echo "$OUTPUT" | grep -qE "^ERROR |^\[ERROR\]|BUILD FAILURE|compilation error|cannot find symbol|package .* does not exist"; then
58
+ HAS_ERROR=true
59
+ ERROR_TYPE="build"
60
+ fi
61
+
62
+ # Test failures (Maven/Gradle/Jest specific formats)
63
+ if echo "$OUTPUT" | grep -qE "Tests run:.*Failures: [1-9]|Tests:.*failed|FAILED!|BUILD FAILURE.*test"; then
64
+ HAS_ERROR=true
65
+ ERROR_TYPE="test"
66
+ fi
67
+
68
+ # Runtime crashes
69
+ if echo "$OUTPUT" | grep -qiE "segmentation fault|bus error|core dumped|SIGABRT|SIGSEGV|killed.*signal"; then
70
+ HAS_ERROR=true
71
+ ERROR_TYPE="crash"
72
+ fi
73
+
74
+ # Shell errors (command failures)
75
+ if echo "$OUTPUT" | grep -qE "command not found|permission denied|cannot execute|not a git repository"; then
76
+ HAS_ERROR=true
77
+ ERROR_TYPE="system"
78
+ fi
79
+
80
+ # Stack traces (Java, Python, Node)
81
+ if echo "$OUTPUT" | grep -qE "^Exception in thread|^Traceback \(most recent|^Caused by:.*Exception"; then
82
+ HAS_ERROR=true
83
+ ERROR_TYPE="exception"
84
+ fi
85
+
86
+ # npm/yarn explicit failure
87
+ if echo "$OUTPUT" | grep -qE "^npm ERR!|^error .*ENOENT|FATAL ERROR"; then
88
+ HAS_ERROR=true
89
+ ERROR_TYPE="build"
90
+ fi
91
+
92
+ # --- Emit reminder if error detected ---
93
+
94
+ if [ "$HAS_ERROR" = true ]; then
95
+ REMINDER="ERROR DETECTED. Follow debugging discipline: 1. Do NOT modify code immediately 2. Fully understand the error message 3. List possible causes (>=2) 4. Record in docs/debug-log.md 5. Verify hypotheses before fixing"
96
+
97
+ if [ "$ERROR_TYPE" = "test" ]; then
98
+ REMINDER="$REMINDER. WARNING: Test failure — do NOT change the test to make it pass! First determine if it's a code bug or an outdated test."
99
+ fi
100
+
101
+ if [ "$ERROR_TYPE" = "crash" ]; then
102
+ REMINDER="$REMINDER. WARNING: Crash/segfault — may involve memory issues."
103
+ fi
104
+
105
+ echo "$REMINDER" >&2
106
+ exit 2
107
+ fi
108
+
109
+ exit 0
@@ -0,0 +1,79 @@
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 WARNING: This edit involves ${DIFF_LINES} lines. Is this the minimal change needed? Could it be broken into smaller, more focused edits?\"}}"
75
+ exit 0
76
+ fi
77
+ fi
78
+
79
+ exit 0
@@ -0,0 +1,43 @@
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
+ Do NOT assume project status 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 → stop and report to user
40
+ - Do NOT jump to implementation before user confirms the approach
41
+ EOF
42
+
43
+ exit 0
@@ -0,0 +1,111 @@
1
+ #!/bin/bash
2
+ # streak-breaker.sh — PreToolUse hook
3
+ # Tracks how many times the same file has been edited in this session
4
+ # Forces a stop when the pattern suggests mole-whacking
5
+ #
6
+ # Design:
7
+ # - Source code files (.java/.ts/.py/.go etc): warn at 3, stop at 5
8
+ # - Config/doc files (.md/.json/.yaml/.xml etc): warn at 6, stop at 10
9
+ # - docs/ directory: exempt (always allow)
10
+ #
11
+ # Exit 0 + no output = silent allow
12
+ # Exit 0 + JSON stdout = allow with context injected to Claude
13
+ # Exit 2 + stderr = block operation, stderr shown to Claude
14
+
15
+ # Read tool input from stdin (JSON)
16
+ INPUT=$(cat)
17
+
18
+ # Extract session_id and file_path
19
+ if command -v jq &>/dev/null; then
20
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
21
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
22
+ else
23
+ SESSION_ID=$(echo "$INPUT" | grep -o '"session_id":\s*"[^"]*"' | head -1 | sed 's/"session_id":\s*"//;s/"//')
24
+ FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":\s*"[^"]*"' | head -1 | sed 's/"file_path":\s*"//;s/"//')
25
+ fi
26
+
27
+ SESSION_ID="${SESSION_ID:-unknown-session}"
28
+ COUNTER_DIR="/tmp/cc-discipline-${SESSION_ID}"
29
+ mkdir -p "$COUNTER_DIR" 2>/dev/null
30
+
31
+ if [ -z "$FILE_PATH" ]; then
32
+ exit 0
33
+ fi
34
+
35
+ # Always allow docs/ files (progress logs, debug logs)
36
+ if echo "$FILE_PATH" | grep -qE "^docs/|/docs/"; then
37
+ exit 0
38
+ fi
39
+
40
+ # Determine file type and set thresholds
41
+ BASENAME=$(basename "$FILE_PATH")
42
+
43
+ if echo "$BASENAME" | grep -qiE "\.(md|json|yaml|yml|toml|xml|cfg|ini|properties|gitignore)$"; then
44
+ # Config/doc files: higher thresholds (many independent sections to fill)
45
+ WARN_THRESHOLD=6
46
+ STOP_THRESHOLD=10
47
+ else
48
+ # Source code files: strict thresholds (repeated edits likely mole-whacking)
49
+ WARN_THRESHOLD=3
50
+ STOP_THRESHOLD=5
51
+ fi
52
+
53
+ # Counter logic
54
+ SAFE_NAME=$(echo "$FILE_PATH" | tr '/' '_')
55
+ COUNTER_FILE="$COUNTER_DIR/$SAFE_NAME"
56
+ CONFIRMED_FILE="${COUNTER_FILE}.confirmed"
57
+
58
+ # If user previously confirmed planned edits, double thresholds
59
+ if [ -f "$CONFIRMED_FILE" ]; then
60
+ WARN_THRESHOLD=$((WARN_THRESHOLD * 2))
61
+ STOP_THRESHOLD=$((STOP_THRESHOLD * 2))
62
+ fi
63
+
64
+ if [ -f "$COUNTER_FILE" ]; then
65
+ COUNT=$(cat "$COUNTER_FILE")
66
+ else
67
+ COUNT=0
68
+ fi
69
+
70
+ COUNT=$((COUNT + 1))
71
+ echo "$COUNT" > "$COUNTER_FILE"
72
+
73
+ # Hard stop: exit 2 + stderr
74
+ if [ "$COUNT" -ge "$STOP_THRESHOLD" ]; then
75
+ if [ -f "$CONFIRMED_FILE" ]; then
76
+ RESET_MSG="Thresholds were already doubled. To continue, the user must explicitly confirm again:
77
+ rm \"$CONFIRMED_FILE\" \"$COUNTER_FILE\" && echo confirmed > \"$CONFIRMED_FILE\""
78
+ else
79
+ RESET_MSG="If this is planned work (refactoring, building a complex file), ask the user to confirm. Then run:
80
+ echo confirmed > \"$CONFIRMED_FILE\" && echo 0 > \"$COUNTER_FILE\"
81
+ This doubles the thresholds for this file (warn=${WARN_THRESHOLD}x2, stop=${STOP_THRESHOLD}x2)."
82
+ fi
83
+ cat >&2 <<EOF
84
+ MOLE-WHACKING ALERT (hard stop)
85
+ File $FILE_PATH has been edited $COUNT times.
86
+ You are repeatedly patching symptoms instead of solving the root cause.
87
+ Required actions:
88
+ 1. Stop editing this file immediately
89
+ 2. Review the purpose of all $COUNT edits
90
+ 3. Look for the common root cause
91
+ 4. Report findings to the user and wait for guidance
92
+ $RESET_MSG
93
+ EOF
94
+ exit 2
95
+ fi
96
+
97
+ # Warning: exit 0 + JSON stdout with additionalContext
98
+ if [ "$COUNT" -ge "$WARN_THRESHOLD" ]; then
99
+ cat <<EOF
100
+ {
101
+ "hookSpecificOutput": {
102
+ "hookEventName": "PreToolUse",
103
+ "additionalContext": "WARNING: File $FILE_PATH has been edited $COUNT times. Are you mole-whacking? Do these edits point to the same root cause? $((STOP_THRESHOLD - COUNT)) more edits will trigger a hard stop."
104
+ }
105
+ }
106
+ EOF
107
+ exit 0
108
+ fi
109
+
110
+ # Silent allow
111
+ exit 0