cc-safe-setup 7.3.0 → 7.5.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/README.md +2 -2
- package/examples/conflict-marker-guard.sh +33 -0
- package/examples/disk-space-guard.sh +28 -0
- package/examples/fact-check-gate.sh +53 -0
- package/examples/memory-write-guard.sh +40 -0
- package/examples/overwrite-guard.sh +32 -0
- package/examples/prompt-injection-guard.sh +51 -0
- package/examples/test-deletion-guard.sh +48 -0
- package/examples/token-budget-guard.sh +54 -0
- package/examples/uncommitted-work-guard.sh +45 -0
- package/examples/verify-before-done.sh +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
|
|
8
8
|
|
|
9
|
-
8 built-in +
|
|
9
|
+
8 built-in + 68 examples = **76 hooks**. 28 CLI commands. 394 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/cheatsheet.html) · [Troubleshooting](TROUBLESHOOTING.md)
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx cc-safe-setup
|
|
@@ -87,7 +87,7 @@ Each hook exists because a real incident happened without it.
|
|
|
87
87
|
| `--scan [--apply]` | Tech stack detection |
|
|
88
88
|
| `--export / --import` | Team config sharing |
|
|
89
89
|
| `--verify` | Test each hook |
|
|
90
|
-
| `--install-example <name>` | Install from
|
|
90
|
+
| `--install-example <name>` | Install from 68 examples |
|
|
91
91
|
| `--examples [filter]` | Browse examples by keyword |
|
|
92
92
|
| `--full` | All-in-one setup |
|
|
93
93
|
| `--status` | Check installed hooks |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# conflict-marker-guard.sh — Block commits with conflict markers
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude sometimes resolves merge conflicts incorrectly, leaving
|
|
7
|
+
# <<<<<<< / ======= / >>>>>>> markers in files. This hook checks
|
|
8
|
+
# staged files for conflict markers before allowing a commit.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
11
|
+
# ================================================================
|
|
12
|
+
|
|
13
|
+
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
14
|
+
[ -z "$COMMAND" ] && exit 0
|
|
15
|
+
|
|
16
|
+
# Only check on git commit
|
|
17
|
+
echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
|
|
18
|
+
|
|
19
|
+
# Check staged files for conflict markers
|
|
20
|
+
CONFLICTS=$(git diff --cached --name-only 2>/dev/null | while read -r f; do
|
|
21
|
+
[ -f "$f" ] && grep -lE '^(<{7}|={7}|>{7})' "$f" 2>/dev/null
|
|
22
|
+
done)
|
|
23
|
+
|
|
24
|
+
if [ -n "$CONFLICTS" ]; then
|
|
25
|
+
COUNT=$(echo "$CONFLICTS" | wc -l)
|
|
26
|
+
echo "BLOCKED: $COUNT file(s) contain merge conflict markers:" >&2
|
|
27
|
+
echo "$CONFLICTS" | head -5 | sed 's/^/ /' >&2
|
|
28
|
+
echo "" >&2
|
|
29
|
+
echo "Resolve conflicts before committing." >&2
|
|
30
|
+
exit 2
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
exit 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# disk-space-guard.sh — Warn when disk space is running low
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Long Claude Code sessions can generate large files, logs, and
|
|
7
|
+
# build artifacts. This hook warns before writes when disk space
|
|
8
|
+
# is below a threshold.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Write|Bash"
|
|
11
|
+
#
|
|
12
|
+
# CONFIG:
|
|
13
|
+
# CC_DISK_WARN_PCT=90 (warn at this percentage used)
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
WARN_PCT="${CC_DISK_WARN_PCT:-90}"
|
|
17
|
+
|
|
18
|
+
# Check disk usage (percentage used on the working directory's partition)
|
|
19
|
+
USAGE=$(df --output=pcent . 2>/dev/null | tail -1 | tr -d ' %')
|
|
20
|
+
[ -z "$USAGE" ] && exit 0
|
|
21
|
+
|
|
22
|
+
if [ "$USAGE" -ge "$WARN_PCT" ]; then
|
|
23
|
+
AVAIL=$(df -h --output=avail . 2>/dev/null | tail -1 | tr -d ' ')
|
|
24
|
+
echo "WARNING: Disk usage is ${USAGE}% (${AVAIL} available)." >&2
|
|
25
|
+
echo "Consider cleaning up build artifacts, logs, or /tmp files." >&2
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# fact-check-gate.sh — Warn when docs reference unread source files
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude writes documentation that references source code without
|
|
7
|
+
# actually reading the files first. This leads to hallucinated
|
|
8
|
+
# function signatures, wrong parameter names, and false claims.
|
|
9
|
+
#
|
|
10
|
+
# This hook tracks which files were Read in the session, and warns
|
|
11
|
+
# when a doc edit mentions source files that weren't read.
|
|
12
|
+
#
|
|
13
|
+
# TRIGGER: PostToolUse MATCHER: "Edit|Write"
|
|
14
|
+
#
|
|
15
|
+
# Born from: https://github.com/anthropics/claude-code/issues/38057
|
|
16
|
+
# "Claude produces false claims in technical docs"
|
|
17
|
+
# ================================================================
|
|
18
|
+
|
|
19
|
+
INPUT=$(cat)
|
|
20
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
21
|
+
[ -z "$FILE" ] && exit 0
|
|
22
|
+
|
|
23
|
+
# Only check documentation files
|
|
24
|
+
case "$FILE" in
|
|
25
|
+
*.md|*.rst|*.txt|*/docs/*|*/doc/*|*README*|*CHANGELOG*|*CONTRIBUTING*)
|
|
26
|
+
;;
|
|
27
|
+
*)
|
|
28
|
+
exit 0
|
|
29
|
+
;;
|
|
30
|
+
esac
|
|
31
|
+
|
|
32
|
+
# Track reads in a session state file
|
|
33
|
+
STATE="/tmp/cc-fact-check-reads-$(echo "$PWD" | md5sum | cut -c1-8)"
|
|
34
|
+
|
|
35
|
+
# Get the content being written
|
|
36
|
+
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
|
|
37
|
+
[ -z "$CONTENT" ] && exit 0
|
|
38
|
+
|
|
39
|
+
# Extract referenced source files from the doc content
|
|
40
|
+
# Looks for: `filename.ext`, filename.ext, import from, require()
|
|
41
|
+
REFS=$(echo "$CONTENT" | grep -oE '`[a-zA-Z0-9_/-]+\.(js|ts|py|go|rs|java|rb|sh|mjs|cjs|jsx|tsx)`' | tr -d '`' | sort -u)
|
|
42
|
+
[ -z "$REFS" ] && exit 0
|
|
43
|
+
|
|
44
|
+
# Check if referenced files were read in this session
|
|
45
|
+
if [ ! -f "$STATE" ]; then
|
|
46
|
+
# No reads tracked yet — warn about all references
|
|
47
|
+
COUNT=$(echo "$REFS" | wc -l)
|
|
48
|
+
echo "WARNING: Doc references $COUNT source file(s) that may not have been read:" >&2
|
|
49
|
+
echo "$REFS" | head -5 | sed 's/^/ /' >&2
|
|
50
|
+
echo "Read the source files before documenting them to avoid hallucination." >&2
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
exit 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# memory-write-guard.sh — Log writes to ~/.claude/ directory
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude auto-writes to ~/.claude/projects/*/memory/ without
|
|
7
|
+
# user visibility. This hook logs all writes to ~/.claude/ paths
|
|
8
|
+
# so users know what's being stored.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Write|Edit"
|
|
11
|
+
#
|
|
12
|
+
# Born from: https://github.com/anthropics/claude-code/issues/38040
|
|
13
|
+
# "No way to enforce approval on all file modifications"
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$FILE" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Check if targeting ~/.claude/
|
|
21
|
+
case "$FILE" in
|
|
22
|
+
*/.claude/*|~/.claude/*)
|
|
23
|
+
# Log the write
|
|
24
|
+
LOG="$HOME/.claude/memory-writes.log"
|
|
25
|
+
echo "[$(date -Iseconds)] Write to: $FILE" >> "$LOG" 2>/dev/null
|
|
26
|
+
|
|
27
|
+
# Only warn (don't block) — memory writes are usually intentional
|
|
28
|
+
echo "NOTE: Writing to Claude config directory: $FILE" >&2
|
|
29
|
+
|
|
30
|
+
# Block writes to settings.json unless explicitly allowed
|
|
31
|
+
case "$FILE" in
|
|
32
|
+
*/settings.json|*/settings.local.json)
|
|
33
|
+
echo "WARNING: Modifying Claude Code settings file." >&2
|
|
34
|
+
echo "Verify this change is intentional." >&2
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
40
|
+
exit 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# overwrite-guard.sh — Warn before overwriting existing files
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude's Write tool can silently overwrite files without
|
|
7
|
+
# confirmation. This hook warns when a Write targets a file
|
|
8
|
+
# that already exists, giving visibility into potential data loss.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Write"
|
|
11
|
+
#
|
|
12
|
+
# Born from: https://github.com/anthropics/claude-code/issues/37595
|
|
13
|
+
# "/export overwrites existing files without warning"
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$FILE" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Expand ~ to home directory
|
|
21
|
+
FILE="${FILE/#\~/$HOME}"
|
|
22
|
+
|
|
23
|
+
if [ -f "$FILE" ]; then
|
|
24
|
+
SIZE=$(wc -c < "$FILE" 2>/dev/null || echo 0)
|
|
25
|
+
if [ "$SIZE" -gt 0 ]; then
|
|
26
|
+
LINES=$(wc -l < "$FILE" 2>/dev/null || echo 0)
|
|
27
|
+
echo "WARNING: Overwriting existing file: $FILE ($LINES lines, $SIZE bytes)" >&2
|
|
28
|
+
echo "Use Edit tool instead to make targeted changes." >&2
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
exit 0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# prompt-injection-guard.sh — Detect prompt injection in tool output
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# When Claude reads files or fetches web content, malicious
|
|
7
|
+
# instructions can be injected. This hook warns when tool output
|
|
8
|
+
# contains common prompt injection patterns.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PostToolUse MATCHER: ""
|
|
11
|
+
#
|
|
12
|
+
# Born from: https://github.com/anthropics/claude-code/issues/38046
|
|
13
|
+
# "Prompt Injection in /insights output"
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
OUTPUT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$OUTPUT" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Check for common prompt injection patterns
|
|
21
|
+
SUSPICIOUS=0
|
|
22
|
+
|
|
23
|
+
# "Ignore previous instructions" pattern
|
|
24
|
+
if echo "$OUTPUT" | grep -qiE 'ignore\s+(all\s+)?previous\s+instructions'; then
|
|
25
|
+
echo "WARNING: Possible prompt injection detected: 'ignore previous instructions'" >&2
|
|
26
|
+
SUSPICIOUS=1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# "You are now" role reassignment
|
|
30
|
+
if echo "$OUTPUT" | grep -qiE 'you\s+are\s+now\s+(a|an)\s+'; then
|
|
31
|
+
echo "WARNING: Possible prompt injection detected: role reassignment" >&2
|
|
32
|
+
SUSPICIOUS=1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# "System prompt" manipulation
|
|
36
|
+
if echo "$OUTPUT" | grep -qiE '(new|updated|override)\s+system\s+prompt'; then
|
|
37
|
+
echo "WARNING: Possible prompt injection detected: system prompt override" >&2
|
|
38
|
+
SUSPICIOUS=1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Hidden instructions in HTML comments or zero-width chars
|
|
42
|
+
if echo "$OUTPUT" | grep -qP '<!--.*(?:execute|run|delete|remove).*-->'; then
|
|
43
|
+
echo "WARNING: Possible prompt injection in HTML comment" >&2
|
|
44
|
+
SUSPICIOUS=1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if [ "$SUSPICIOUS" -eq 1 ]; then
|
|
48
|
+
echo "Review the output carefully before acting on it." >&2
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# test-deletion-guard.sh — Block deletion of test assertions
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude sometimes deletes or comments out failing tests instead
|
|
7
|
+
# of fixing the underlying code. This hook detects when an Edit
|
|
8
|
+
# to a test file removes test assertions.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Edit"
|
|
11
|
+
#
|
|
12
|
+
# Born from: https://github.com/anthropics/claude-code/issues/38050
|
|
13
|
+
# "Claude skips/deletes tests instead of fixing them"
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$FILE" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Only check test files
|
|
21
|
+
case "$FILE" in
|
|
22
|
+
*test*|*spec*|*__tests__*|*_test.go|*_test.py|*Test.java|*Test.kt)
|
|
23
|
+
;;
|
|
24
|
+
*)
|
|
25
|
+
exit 0
|
|
26
|
+
;;
|
|
27
|
+
esac
|
|
28
|
+
|
|
29
|
+
OLD=$(echo "$INPUT" | jq -r '.tool_input.old_string // empty' 2>/dev/null)
|
|
30
|
+
NEW=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty' 2>/dev/null)
|
|
31
|
+
[ -z "$OLD" ] && exit 0
|
|
32
|
+
|
|
33
|
+
# Count test assertions in old vs new
|
|
34
|
+
count_tests() {
|
|
35
|
+
echo "$1" | grep -cE '(it\(|test\(|describe\(|def test_|#\[test\]|@Test|assert|expect\(|should\b)' 2>/dev/null || echo 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
OLD_COUNT=$(count_tests "$OLD")
|
|
39
|
+
NEW_COUNT=$(count_tests "$NEW")
|
|
40
|
+
|
|
41
|
+
if [ "$OLD_COUNT" -gt 0 ] && [ "$NEW_COUNT" -lt "$OLD_COUNT" ]; then
|
|
42
|
+
REMOVED=$((OLD_COUNT - NEW_COUNT))
|
|
43
|
+
echo "WARNING: This edit removes $REMOVED test assertion(s) from $FILE." >&2
|
|
44
|
+
echo "If tests are failing, fix the code instead of deleting tests." >&2
|
|
45
|
+
echo "Old assertions: $OLD_COUNT → New assertions: $NEW_COUNT" >&2
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
exit 0
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# token-budget-guard.sh — Estimate and limit session token cost
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude Code sessions can consume hundreds of dollars in tokens
|
|
7
|
+
# without the user realizing it. This hook estimates cumulative
|
|
8
|
+
# cost and warns/blocks when a budget threshold is exceeded.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PostToolUse MATCHER: ""
|
|
11
|
+
#
|
|
12
|
+
# CONFIG:
|
|
13
|
+
# CC_TOKEN_BUDGET=10 (warn at $10 estimated cost)
|
|
14
|
+
# CC_TOKEN_BLOCK=50 (block at $50 estimated cost)
|
|
15
|
+
#
|
|
16
|
+
# Born from: https://github.com/anthropics/claude-code/issues/38029
|
|
17
|
+
# "652k output tokens ($342) without user input"
|
|
18
|
+
# ================================================================
|
|
19
|
+
|
|
20
|
+
WARN_BUDGET="${CC_TOKEN_BUDGET:-10}"
|
|
21
|
+
BLOCK_BUDGET="${CC_TOKEN_BLOCK:-50}"
|
|
22
|
+
STATE="/tmp/cc-token-budget-$(echo "$PWD" | md5sum | cut -c1-8)"
|
|
23
|
+
|
|
24
|
+
# Estimate tokens from tool output size
|
|
25
|
+
INPUT=$(cat)
|
|
26
|
+
OUTPUT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
|
|
27
|
+
OUTPUT_LEN=${#OUTPUT}
|
|
28
|
+
|
|
29
|
+
# Rough estimation: 1 token ≈ 4 chars, $15/M input + $75/M output for Opus
|
|
30
|
+
# This is approximate — actual costs depend on model and caching
|
|
31
|
+
TOKENS=$((OUTPUT_LEN / 4))
|
|
32
|
+
|
|
33
|
+
# Accumulate
|
|
34
|
+
TOTAL=0
|
|
35
|
+
[ -f "$STATE" ] && TOTAL=$(cat "$STATE" 2>/dev/null || echo 0)
|
|
36
|
+
TOTAL=$((TOTAL + TOKENS))
|
|
37
|
+
echo "$TOTAL" > "$STATE"
|
|
38
|
+
|
|
39
|
+
# Estimate cost (output tokens at $75/M for Opus)
|
|
40
|
+
# Using integer math: cost_cents = tokens * 75 / 10000
|
|
41
|
+
COST_CENTS=$((TOTAL * 75 / 10000))
|
|
42
|
+
|
|
43
|
+
if [ "$COST_CENTS" -ge "$((BLOCK_BUDGET * 100))" ]; then
|
|
44
|
+
echo "BLOCKED: Estimated session cost ~\$${COST_CENTS%??}.${COST_CENTS: -2} exceeds \$$BLOCK_BUDGET budget." >&2
|
|
45
|
+
echo "Reset: rm $STATE" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [ "$COST_CENTS" -ge "$((WARN_BUDGET * 100))" ]; then
|
|
50
|
+
echo "WARNING: Estimated session cost ~\$${COST_CENTS%??}.${COST_CENTS: -2} approaching \$$BLOCK_BUDGET limit." >&2
|
|
51
|
+
echo "Consider using /compact or starting a new session." >&2
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
exit 0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# uncommitted-work-guard.sh — Block destructive git when dirty
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude sometimes runs git checkout --, git reset --hard, or
|
|
7
|
+
# git stash drop when there are uncommitted changes, destroying
|
|
8
|
+
# hours of work. This hook checks git status before allowing
|
|
9
|
+
# destructive git commands.
|
|
10
|
+
#
|
|
11
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
12
|
+
#
|
|
13
|
+
# Born from: https://github.com/anthropics/claude-code/issues/37888
|
|
14
|
+
# "Claude runs forbidden destructive git commands, destroys work twice"
|
|
15
|
+
# ================================================================
|
|
16
|
+
|
|
17
|
+
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$COMMAND" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Only check destructive git commands
|
|
21
|
+
DESTRUCTIVE=0
|
|
22
|
+
echo "$COMMAND" | grep -qE '\bgit\s+checkout\s+--\s' && DESTRUCTIVE=1
|
|
23
|
+
echo "$COMMAND" | grep -qE '\bgit\s+checkout\s+\.\s*$' && DESTRUCTIVE=1
|
|
24
|
+
echo "$COMMAND" | grep -qE '\bgit\s+restore\s+--staged\s+\.' && DESTRUCTIVE=1
|
|
25
|
+
echo "$COMMAND" | grep -qE '\bgit\s+restore\s+\.\s*$' && DESTRUCTIVE=1
|
|
26
|
+
echo "$COMMAND" | grep -qE '\bgit\s+reset\s+--hard' && DESTRUCTIVE=1
|
|
27
|
+
echo "$COMMAND" | grep -qE '\bgit\s+clean\s+-[a-zA-Z]*f' && DESTRUCTIVE=1
|
|
28
|
+
echo "$COMMAND" | grep -qE '\bgit\s+stash\s+drop' && DESTRUCTIVE=1
|
|
29
|
+
|
|
30
|
+
[ "$DESTRUCTIVE" -eq 0 ] && exit 0
|
|
31
|
+
|
|
32
|
+
# Check for uncommitted changes
|
|
33
|
+
DIRTY=$(git status --porcelain 2>/dev/null | head -20)
|
|
34
|
+
if [ -n "$DIRTY" ]; then
|
|
35
|
+
COUNT=$(echo "$DIRTY" | wc -l)
|
|
36
|
+
echo "BLOCKED: Destructive git command with $COUNT uncommitted change(s)." >&2
|
|
37
|
+
echo "Changes that would be lost:" >&2
|
|
38
|
+
echo "$DIRTY" | head -10 | sed 's/^/ /' >&2
|
|
39
|
+
[ "$COUNT" -gt 10 ] && echo " ... and $((COUNT-10)) more" >&2
|
|
40
|
+
echo "" >&2
|
|
41
|
+
echo "Commit or stash your changes first, then retry." >&2
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
exit 0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# verify-before-done.sh — Warn when committing without running tests
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude Code often declares fixes "done" and commits without
|
|
7
|
+
# verifying the fix actually works. This hook warns when a commit
|
|
8
|
+
# is made in a project that has tests, but no test command was
|
|
9
|
+
# run recently in the session.
|
|
10
|
+
#
|
|
11
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
12
|
+
#
|
|
13
|
+
# Born from: https://github.com/anthropics/claude-code/issues/37818
|
|
14
|
+
# "Claude repeatedly declares fixes done without verification"
|
|
15
|
+
# ================================================================
|
|
16
|
+
|
|
17
|
+
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$COMMAND" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Only check on git commit
|
|
21
|
+
echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
|
|
22
|
+
|
|
23
|
+
# Track test execution via state file
|
|
24
|
+
STATE="/tmp/cc-tests-ran-$(pwd | md5sum | cut -c1-8)"
|
|
25
|
+
|
|
26
|
+
# Check if tests were run in this session
|
|
27
|
+
if [ ! -f "$STATE" ]; then
|
|
28
|
+
# Detect if project has tests
|
|
29
|
+
HAS_TESTS=0
|
|
30
|
+
[ -f "package.json" ] && grep -q '"test"' package.json 2>/dev/null && HAS_TESTS=1
|
|
31
|
+
[ -f "pytest.ini" ] || [ -f "setup.cfg" ] || [ -f "pyproject.toml" ] && HAS_TESTS=1
|
|
32
|
+
[ -f "Cargo.toml" ] && HAS_TESTS=1
|
|
33
|
+
[ -f "go.mod" ] && HAS_TESTS=1
|
|
34
|
+
[ -f "Makefile" ] && grep -q 'test:' Makefile 2>/dev/null && HAS_TESTS=1
|
|
35
|
+
|
|
36
|
+
if [ "$HAS_TESTS" -eq 1 ]; then
|
|
37
|
+
echo "WARNING: Committing without running tests first." >&2
|
|
38
|
+
echo "Run your test suite before committing to verify changes work." >&2
|
|
39
|
+
echo "To suppress: touch $STATE" >&2
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "One command to make Claude Code safe. 59 hooks (8 built-in + 51 examples). 26 CLI commands: dashboard, create, audit, lint, diff, migrate, compare, generate-ci. 284 tests.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|