cc-safe-setup 29.6.10 → 29.6.12

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 CHANGED
@@ -117,7 +117,7 @@ Install any of these: `npx cc-safe-setup --install-example <name>`
117
117
  | `--scan [--apply]` | Tech stack detection |
118
118
  | `--export / --import` | Team config sharing |
119
119
  | `--verify` | Test each hook |
120
- | `--install-example <name>` | Install from 413 examples |
120
+ | `--install-example <name>` | Install from 421 examples |
121
121
  | `--examples [filter]` | Browse examples by keyword |
122
122
  | `--full` | All-in-one setup |
123
123
  | `--status` | Check installed hooks |
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # file-edit-backup.sh — Auto-backup files before Edit/Write overwrites them
3
+ #
4
+ # Solves: Claude Code overwrites important files and the changes are hard
5
+ # to reverse. This creates a timestamped backup before each edit,
6
+ # so you can always recover the previous version.
7
+ #
8
+ # Real incidents:
9
+ # #37478 — .bashrc overwritten without permission
10
+ # #32938 — 11h of inference output deleted
11
+ # #36339 — C:\Users directory wiped (NTFS junction traversal)
12
+ #
13
+ # Backups go to ~/.claude/file-backups/ with timestamps.
14
+ # Old backups (>7 days) are auto-cleaned to prevent disk bloat.
15
+ #
16
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
17
+
18
+ INPUT=$(cat)
19
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
20
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
21
+
22
+ [ -z "$FILE" ] && exit 0
23
+ [ ! -f "$FILE" ] && exit 0 # New file, nothing to backup
24
+
25
+ BACKUP_DIR="$HOME/.claude/file-backups"
26
+ mkdir -p "$BACKUP_DIR"
27
+
28
+ # Create backup with timestamp
29
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
30
+ SAFE_NAME=$(echo "$FILE" | tr '/' '_' | sed 's/^_//')
31
+ BACKUP_PATH="${BACKUP_DIR}/${SAFE_NAME}.${TIMESTAMP}"
32
+
33
+ cp "$FILE" "$BACKUP_PATH" 2>/dev/null
34
+
35
+ # Clean old backups (>7 days)
36
+ find "$BACKUP_DIR" -type f -mtime +7 -delete 2>/dev/null
37
+
38
+ exit 0
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ # git-checkout-uncommitted-guard.sh — Block branch switching with uncommitted changes
3
+ #
4
+ # Solves: Claude Code switching branches while uncommitted changes exist,
5
+ # causing silent data loss when the target branch overwrites modified files.
6
+ # Real incident: #39394 — hours of work lost when Claude decided to
7
+ # organize commits on a different branch.
8
+ #
9
+ # Why this matters: git checkout <branch> silently overwrites modified files
10
+ # if the target branch has different versions. Unlike "git checkout -- .",
11
+ # the user doesn't intend to discard changes — they just wanted to switch
12
+ # branches. The data loss is a side effect, not the goal.
13
+ #
14
+ # Detects:
15
+ # git checkout <branch> (when uncommitted changes exist)
16
+ # git switch <branch> (modern equivalent)
17
+ #
18
+ # Does NOT block:
19
+ # git checkout -b <branch> (creating new branch — preserves changes)
20
+ # git switch -c <branch> (creating new branch — preserves changes)
21
+ # git checkout -- <files> (handled by uncommitted-discard-guard)
22
+ # Any checkout when working tree is clean
23
+ #
24
+ # TRIGGER: PreToolUse MATCHER: "Bash"
25
+
26
+ INPUT=$(cat)
27
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
28
+
29
+ [ -z "$COMMAND" ] && exit 0
30
+
31
+ # Only check git checkout/switch commands (not -b/-c which create branches)
32
+ IS_CHECKOUT=false
33
+ if echo "$COMMAND" | grep -qE 'git\s+checkout\s+[^-]' && ! echo "$COMMAND" | grep -qE 'git\s+checkout\s+(-b|-B)\s'; then
34
+ # Exclude "git checkout -- <files>" (handled elsewhere)
35
+ echo "$COMMAND" | grep -qE 'git\s+checkout\s+--\s' && exit 0
36
+ IS_CHECKOUT=true
37
+ fi
38
+ if echo "$COMMAND" | grep -qE 'git\s+switch\s+[^-]' && ! echo "$COMMAND" | grep -qE 'git\s+switch\s+(-c|-C)\s'; then
39
+ IS_CHECKOUT=true
40
+ fi
41
+
42
+ [ "$IS_CHECKOUT" = "false" ] && exit 0
43
+
44
+ # Check for uncommitted changes
45
+ DIRTY=$(git status --porcelain 2>/dev/null | head -1)
46
+ if [ -n "$DIRTY" ]; then
47
+ CHANGED=$(git status --porcelain 2>/dev/null | wc -l)
48
+ echo "BLOCKED: Cannot switch branches with $CHANGED uncommitted change(s)." >&2
49
+ echo "" >&2
50
+ echo "Command: $COMMAND" >&2
51
+ echo "" >&2
52
+ echo "Uncommitted changes would be overwritten by the target branch." >&2
53
+ echo "Options:" >&2
54
+ echo " git stash # save changes, switch, then git stash pop" >&2
55
+ echo " git commit -m 'WIP' # commit changes before switching" >&2
56
+ echo " git checkout -b <new-branch> # create branch (keeps changes)" >&2
57
+ exit 2
58
+ fi
59
+
60
+ exit 0
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # git-message-length-check.sh — Warn on too-short commit messages
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes writes very short commit messages like
7
+ # "fix" or "update". This PostToolUse hook checks the commit
8
+ # message length and warns if it's too short to be meaningful.
9
+ #
10
+ # TRIGGER: PostToolUse
11
+ # MATCHER: "Bash"
12
+ #
13
+ # CONFIGURATION:
14
+ # CC_COMMIT_MIN_LENGTH=10 (minimum message length, default: 10)
15
+ # ================================================================
16
+
17
+ INPUT=$(cat)
18
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
19
+ [ -z "$COMMAND" ] && exit 0
20
+
21
+ # Only check git commit commands
22
+ echo "$COMMAND" | grep -qE 'git\s+commit' || exit 0
23
+
24
+ MIN_LENGTH="${CC_COMMIT_MIN_LENGTH:-10}"
25
+
26
+ # Extract message from -m flag
27
+ MSG=$(echo "$COMMAND" | grep -oE '\-m\s+["'"'"'][^"'"'"']*["'"'"']' | sed "s/-m\s*[\"']\(.*\)[\"']/\1/")
28
+ [ -z "$MSG" ] && exit 0
29
+
30
+ LENGTH=${#MSG}
31
+
32
+ if [ "$LENGTH" -lt "$MIN_LENGTH" ]; then
33
+ echo "⚠ Commit message too short ($LENGTH chars, minimum: $MIN_LENGTH)" >&2
34
+ echo " Message: \"$MSG\"" >&2
35
+ echo " Write descriptive messages explaining WHY, not WHAT." >&2
36
+ fi
37
+
38
+ exit 0
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # plan-mode-edit-guard.sh — Warn when editing non-plan files during plan mode
3
+ #
4
+ # Solves: Claude editing source files while plan mode is active,
5
+ # bypassing the "plan only" constraint. Real incident: #38255 —
6
+ # Opus edited api/app/routers/ despite system reminders saying
7
+ # plan mode was active.
8
+ #
9
+ # How it works: Uses a flag file to track plan mode state.
10
+ # - Create ~/.claude/plan-mode-active to enable (touch the file)
11
+ # - Delete it to disable (rm the file)
12
+ # - When active, Edit/Write to non-plan files triggers a warning
13
+ #
14
+ # Integrate with your workflow:
15
+ # Before entering plan mode: touch ~/.claude/plan-mode-active
16
+ # After exiting plan mode: rm -f ~/.claude/plan-mode-active
17
+ #
18
+ # Does NOT block (warns only) because false positives during
19
+ # legitimate plan-mode file creation would be disruptive.
20
+ # Change exit 0 to exit 2 at the bottom to enforce strict blocking.
21
+ #
22
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
23
+
24
+ INPUT=$(cat)
25
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
26
+
27
+ FLAG="$HOME/.claude/plan-mode-active"
28
+
29
+ # Only check when plan mode flag exists
30
+ [ ! -f "$FLAG" ] && exit 0
31
+
32
+ # Get the file being edited
33
+ case "$TOOL" in
34
+ Edit)
35
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
36
+ ;;
37
+ Write)
38
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
39
+ ;;
40
+ *)
41
+ exit 0
42
+ ;;
43
+ esac
44
+
45
+ [ -z "$FILE" ] && exit 0
46
+
47
+ # Allow plan files
48
+ if echo "$FILE" | grep -qiE '\.plan\.md$|plan\.md$|/plans/|task_plan\.md|findings\.md|progress\.md'; then
49
+ exit 0
50
+ fi
51
+
52
+ # Allow CLAUDE.md and memory files (often updated during planning)
53
+ if echo "$FILE" | grep -qiE 'CLAUDE\.md$|MEMORY\.md$|memory/|\.claude/'; then
54
+ exit 0
55
+ fi
56
+
57
+ # Warn about non-plan file edits
58
+ echo "⚠ PLAN MODE ACTIVE: Editing non-plan file: $FILE" >&2
59
+ echo " Plan mode should only modify plan files." >&2
60
+ echo " Remove ~/.claude/plan-mode-active to disable this check." >&2
61
+
62
+ # Warning only (exit 0). Change to exit 2 to block.
63
+ exit 0
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # unicode-corruption-check.sh — Detect Unicode corruption after Edit/Write
3
+ #
4
+ # Solves: Claude Code's Edit tool corrupting non-ASCII Unicode characters
5
+ # (typographic quotes, em dashes, accented characters) in string literals.
6
+ # Real incident: #38765 — Edit tool replaced Unicode quotes with
7
+ # escaped sequences, breaking string comparisons.
8
+ #
9
+ # How it works: After Edit/Write, checks if the file contains common
10
+ # corruption patterns (replacement characters, broken UTF-8 sequences).
11
+ #
12
+ # TRIGGER: PostToolUse MATCHER: "Edit|Write"
13
+
14
+ INPUT=$(cat)
15
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
16
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
17
+
18
+ [ -z "$FILE" ] && exit 0
19
+ [ ! -f "$FILE" ] && exit 0
20
+
21
+ # Skip binary files
22
+ file "$FILE" 2>/dev/null | grep -q "text" || exit 0
23
+
24
+ # Check for Unicode replacement character (U+FFFD) — sign of broken encoding
25
+ if grep -Pq '\xef\xbf\xbd' "$FILE" 2>/dev/null; then
26
+ echo "⚠ Unicode corruption detected in $FILE" >&2
27
+ echo " Found U+FFFD replacement characters (broken encoding)." >&2
28
+ echo " Review the edit — non-ASCII characters may have been corrupted." >&2
29
+ fi
30
+
31
+ # Check for common corruption: escaped Unicode in places it shouldn't be
32
+ # e.g., \u2018 appearing in plain text files (not JSON/JS)
33
+ if ! echo "$FILE" | grep -qE '\.(json|js|ts|jsx|tsx)$'; then
34
+ if grep -qE '\\u[0-9a-fA-F]{4}' "$FILE" 2>/dev/null; then
35
+ echo "⚠ Possible Unicode escape in non-JS file: $FILE" >&2
36
+ echo " Found \\uXXXX sequences that may be corrupted characters." >&2
37
+ fi
38
+ fi
39
+
40
+ exit 0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "29.6.10",
4
- "description": "One command to make Claude Code safe. 420 example hooks + 8 built-in. 52 CLI commands. 5662 tests. Works with Auto Mode.",
3
+ "version": "29.6.12",
4
+ "description": "One command to make Claude Code safe. 425 example hooks + 8 built-in. 52 CLI commands. 5695 tests. Works with Auto Mode.",
5
5
  "main": "index.mjs",
6
6
  "bin": {
7
7
  "cc-safe-setup": "index.mjs"