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.
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/README.zh-CN.md +207 -0
- package/bin/cli.sh +96 -0
- package/global/CLAUDE.md +43 -0
- package/init.sh +486 -0
- package/lib/doctor.sh +145 -0
- package/lib/stack-remove.sh +68 -0
- package/lib/status.sh +95 -0
- package/package.json +34 -0
- package/templates/.claude/agents/investigator.md +44 -0
- package/templates/.claude/agents/reviewer.md +46 -0
- package/templates/.claude/hooks/action-counter.sh +28 -0
- package/templates/.claude/hooks/phase-gate.sh +10 -0
- package/templates/.claude/hooks/post-error-remind.sh +109 -0
- package/templates/.claude/hooks/pre-edit-guard.sh +79 -0
- package/templates/.claude/hooks/session-start.sh +43 -0
- package/templates/.claude/hooks/streak-breaker.sh +111 -0
- package/templates/.claude/rules/00-core-principles.md +12 -0
- package/templates/.claude/rules/01-debugging.md +22 -0
- package/templates/.claude/rules/02-before-edit.md +11 -0
- package/templates/.claude/rules/03-context-mgmt.md +19 -0
- package/templates/.claude/rules/04-no-mole-whacking.md +26 -0
- package/templates/.claude/rules/05-phase-discipline.md +13 -0
- package/templates/.claude/rules/06-multi-task.md +11 -0
- package/templates/.claude/rules/07-integrity.md +81 -0
- package/templates/.claude/rules/stacks/embedded.md +24 -0
- package/templates/.claude/rules/stacks/js-ts.md +21 -0
- package/templates/.claude/rules/stacks/mobile.md +16 -0
- package/templates/.claude/rules/stacks/python.md +20 -0
- package/templates/.claude/rules/stacks/rtl.md +24 -0
- package/templates/.claude/settings.json +75 -0
- package/templates/.claude/skills/commit/SKILL.md +40 -0
- package/templates/.claude/skills/evaluate/SKILL.md +57 -0
- package/templates/.claude/skills/self-check/SKILL.md +62 -0
- package/templates/CLAUDE.md +80 -0
- package/templates/docs/debug-log.md +48 -0
- package/templates/docs/progress.md +29 -0
- 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
|