cc-safe-setup 3.4.0 → 3.6.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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  **One command to make Claude Code safe for autonomous operation.**
8
8
 
9
- Not just a destructive command blocker 8 hooks covering safety, quality, monitoring, and developer experience.
9
+ 8 built-in hooks + 32 installable examples. Audit, create, lint, diff, watch, and learn. [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/cheatsheet.html) · [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Troubleshooting](TROUBLESHOOTING.md)
10
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -218,11 +218,17 @@ Or browse all available examples in [`examples/`](examples/):
218
218
  - **tmp-cleanup.sh** — Clean up accumulated `/tmp/claude-*-cwd` files on session end ([#8856](https://github.com/anthropics/claude-code/issues/8856))
219
219
  - **session-checkpoint.sh** — Save session state to mission file before context compaction ([#37866](https://github.com/anthropics/claude-code/issues/37866))
220
220
  - **verify-before-commit.sh** — Block git commit when lint/test commands haven't been run ([#37818](https://github.com/anthropics/claude-code/issues/37818))
221
+ - **hook-debug-wrapper.sh** — Wrap any hook to log input/output/exit code/timing to `~/.claude/hook-debug.log`
222
+ - **loop-detector.sh** — Detect and break command repetition loops (warn at 3, block at 5 repeats)
221
223
 
222
224
  ## Safety Checklist
223
225
 
224
226
  **[SAFETY_CHECKLIST.md](SAFETY_CHECKLIST.md)** — Copy-paste checklist for before/during/after autonomous sessions.
225
227
 
228
+ ## Troubleshooting
229
+
230
+ **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** — "Hook doesn't work" → step-by-step diagnosis. Covers every common failure pattern.
231
+
226
232
  ## settings.json Reference
227
233
 
228
234
  **[SETTINGS_REFERENCE.md](SETTINGS_REFERENCE.md)** — Complete reference for permissions, hooks, modes, and common configurations. Includes known limitations and workarounds.
@@ -0,0 +1,212 @@
1
+ # Troubleshooting Claude Code Hooks
2
+
3
+ Your hook isn't working. Here's how to fix it, starting with the most common causes.
4
+
5
+ ## Quick Diagnosis
6
+
7
+ ```bash
8
+ npx cc-safe-setup --doctor
9
+ ```
10
+
11
+ This checks jq, settings.json, file permissions, shebangs, and common misconfigurations. If it says "All checks passed" but hooks still don't fire, read on.
12
+
13
+ ## "Hook doesn't block anything"
14
+
15
+ ### 1. Did you restart Claude Code?
16
+
17
+ Hooks are loaded on startup. After installing or modifying hooks, close Claude Code completely and reopen it.
18
+
19
+ ### 2. Is the hook registered in settings.json?
20
+
21
+ ```bash
22
+ cat ~/.claude/settings.json | jq '.hooks'
23
+ ```
24
+
25
+ You should see your hook's path under the correct trigger. If not:
26
+
27
+ ```bash
28
+ npx cc-safe-setup # Re-registers all hooks
29
+ ```
30
+
31
+ ### 3. Is the hook file executable?
32
+
33
+ ```bash
34
+ ls -la ~/.claude/hooks/your-hook.sh
35
+ # Should show -rwxr-xr-x
36
+ ```
37
+
38
+ Fix: `chmod +x ~/.claude/hooks/your-hook.sh`
39
+
40
+ ### 4. Is jq installed?
41
+
42
+ Most hooks use jq to parse JSON input.
43
+
44
+ ```bash
45
+ jq --version
46
+ # Should print: jq-1.x
47
+ ```
48
+
49
+ Install: `brew install jq` (macOS) / `apt install jq` (Linux/WSL)
50
+
51
+ ### 5. Does the hook work manually?
52
+
53
+ Test it outside Claude Code:
54
+
55
+ ```bash
56
+ echo '{"tool_input":{"command":"rm -rf /"}}' | bash ~/.claude/hooks/destructive-guard.sh
57
+ echo $?
58
+ # Should print: 2 (blocked)
59
+ ```
60
+
61
+ If exit code is 0, the hook isn't matching the pattern.
62
+
63
+ ### 6. Wrong exit code
64
+
65
+ | Exit Code | Meaning |
66
+ |-----------|---------|
67
+ | **0** | Allow (or no opinion) |
68
+ | **2** | Block — the only code that stops execution |
69
+ | **1** | Error (treated as allow, not block!) |
70
+
71
+ Common mistake: using `exit 1` instead of `exit 2` to block. Only exit 2 blocks.
72
+
73
+ ## "Hook blocks everything"
74
+
75
+ ### 1. Overly broad grep pattern
76
+
77
+ ```bash
78
+ # BAD: matches ANY command containing "rm"
79
+ grep -q 'rm'
80
+
81
+ # GOOD: matches only rm with -rf flags
82
+ grep -qE 'rm\s+(-[rf]+\s+)*/'
83
+ ```
84
+
85
+ ### 2. Missing empty-input guard
86
+
87
+ Every hook should handle empty input:
88
+
89
+ ```bash
90
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
91
+ [ -z "$COMMAND" ] && exit 0 # ← This line is critical
92
+ ```
93
+
94
+ Without this, the hook may exit 2 on tools that don't have `.tool_input.command` (like Read or Glob).
95
+
96
+ ### 3. Wrong matcher
97
+
98
+ If your hook is for Bash commands but the matcher is empty, it runs on every tool call:
99
+
100
+ ```json
101
+ {"matcher": "Bash"} ← Correct: only Bash commands
102
+ {"matcher": ""} ← Runs on EVERY tool (Read, Edit, Glob, etc.)
103
+ ```
104
+
105
+ ## "Hook fires but doesn't auto-approve"
106
+
107
+ ### 1. JSON output format is wrong
108
+
109
+ Auto-approve requires exact JSON structure:
110
+
111
+ ```json
112
+ {
113
+ "hookSpecificOutput": {
114
+ "hookEventName": "PreToolUse",
115
+ "permissionDecision": "allow",
116
+ "permissionDecisionReason": "your reason"
117
+ }
118
+ }
119
+ ```
120
+
121
+ Missing any field = permission system ignores it.
122
+
123
+ ### 2. jq output is going to stderr
124
+
125
+ ```bash
126
+ # BAD: output goes to stderr
127
+ jq -n '...' >&2
128
+
129
+ # GOOD: output goes to stdout
130
+ jq -n '...'
131
+ ```
132
+
133
+ Auto-approve JSON must go to stdout.
134
+
135
+ ## "Permission prompts still appear for compound commands"
136
+
137
+ This is a known Claude Code limitation, not a hook issue. `Bash(git:*)` doesn't match `cd /path && git log`.
138
+
139
+ Fix:
140
+
141
+ ```bash
142
+ npx cc-safe-setup --install-example compound-command-approver
143
+ ```
144
+
145
+ ## "Hooks slow down Claude Code"
146
+
147
+ ### 1. Check execution time
148
+
149
+ ```bash
150
+ npx cc-safe-setup --install-example hook-debug-wrapper
151
+ # Then wrap your slow hook to see timing
152
+ ```
153
+
154
+ Hooks should complete in <50ms. If a hook takes >200ms, it's noticeable.
155
+
156
+ ### 2. Too many hooks on empty matcher
157
+
158
+ Hooks with `"matcher": ""` run on every single tool call. Move heavy checks to specific matchers:
159
+
160
+ ```json
161
+ {"matcher": "Bash"} ← Only when Bash runs
162
+ {"matcher": "Edit|Write"} ← Only when files are edited
163
+ ```
164
+
165
+ ### 3. Use --lint to find issues
166
+
167
+ ```bash
168
+ npx cc-safe-setup --lint
169
+ ```
170
+
171
+ Reports performance warnings and configuration issues.
172
+
173
+ ## "Hooks work locally but not for teammates"
174
+
175
+ ### 1. Compare settings
176
+
177
+ ```bash
178
+ npx cc-safe-setup --diff teammate-settings.json
179
+ ```
180
+
181
+ Shows exactly what's different between your setups.
182
+
183
+ ### 2. Export and share
184
+
185
+ ```bash
186
+ npx cc-safe-setup --export # Creates cc-safe-setup-export.json
187
+ # Send to teammate
188
+ npx cc-safe-setup --import cc-safe-setup-export.json
189
+ ```
190
+
191
+ ### 3. Different jq versions
192
+
193
+ Some hooks use jq features not available in older versions. Check: `jq --version`
194
+
195
+ ## "Hooks run but don't log"
196
+
197
+ Hooks write to stderr for user-visible messages. For persistent logging:
198
+
199
+ ```bash
200
+ # Add to your hook
201
+ LOG="$HOME/.claude/blocked-commands.log"
202
+ echo "[$(date -Iseconds)] BLOCKED: reason | cmd: $COMMAND" >> "$LOG"
203
+ ```
204
+
205
+ Then view with: `npx cc-safe-setup --watch` or `npx cc-safe-setup --stats`
206
+
207
+ ## Still Stuck?
208
+
209
+ 1. Wrap the hook with debug wrapper: `npx cc-safe-setup --install-example hook-debug-wrapper`
210
+ 2. Check `~/.claude/hook-debug.log` for detailed I/O traces
211
+ 3. Run `npx cc-safe-setup --doctor` for automated checks
212
+ 4. Open an issue: [cc-safe-setup issues](https://github.com/yurukusa/cc-safe-setup/issues)
@@ -598,6 +598,21 @@ exit 0`
598
598
 
599
599
  return scripts[id] || '#!/bin/bash\nexit 0';
600
600
  }
601
+
602
+ // Auto-load from URL parameter: ?config=base64encodedJSON
603
+ (function() {
604
+ const params = new URLSearchParams(window.location.search);
605
+ const config = params.get('config');
606
+ if (config) {
607
+ try {
608
+ const json = atob(config);
609
+ document.getElementById('settings').value = json;
610
+ runAudit();
611
+ } catch(e) {
612
+ console.error('Invalid config parameter');
613
+ }
614
+ }
615
+ })();
601
616
  </script>
602
617
  </body>
603
618
  </html>
package/docs/index.html CHANGED
@@ -598,6 +598,21 @@ exit 0`
598
598
 
599
599
  return scripts[id] || '#!/bin/bash\nexit 0';
600
600
  }
601
+
602
+ // Auto-load from URL parameter: ?config=base64encodedJSON
603
+ (function() {
604
+ const params = new URLSearchParams(window.location.search);
605
+ const config = params.get('config');
606
+ if (config) {
607
+ try {
608
+ const json = atob(config);
609
+ document.getElementById('settings').value = json;
610
+ runAudit();
611
+ } catch(e) {
612
+ console.error('Invalid config parameter');
613
+ }
614
+ }
615
+ })();
601
616
  </script>
602
617
  </body>
603
618
  </html>
@@ -0,0 +1,89 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # commit-quality-gate.sh — Enforce commit message quality
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code generates commit messages that are often too long,
7
+ # too vague ("update code"), or contain the full diff summary.
8
+ # This hook enforces minimum quality standards.
9
+ #
10
+ # TRIGGER: PreToolUse
11
+ # MATCHER: "Bash"
12
+ #
13
+ # CHECKS:
14
+ # 1. Subject line length (max 72 chars, warn at 50)
15
+ # 2. No vague subjects ("update", "fix", "changes", "misc")
16
+ # 3. No mega-commits (subject line shouldn't list 5+ changes)
17
+ # 4. Body line length (max 72 chars per line)
18
+ # 5. No empty subject line
19
+ #
20
+ # CONFIGURATION:
21
+ # CC_COMMIT_MAX_SUBJECT=72
22
+ # CC_COMMIT_WARN_SUBJECT=50
23
+ # ================================================================
24
+
25
+ INPUT=$(cat)
26
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
27
+
28
+ if [[ -z "$COMMAND" ]]; then
29
+ exit 0
30
+ fi
31
+
32
+ # Only check git commit commands
33
+ if ! echo "$COMMAND" | grep -qE '^\s*git\s+commit'; then
34
+ exit 0
35
+ fi
36
+
37
+ # Skip --amend (modifying existing) and --allow-empty
38
+ if echo "$COMMAND" | grep -qE '\-\-amend|\-\-allow-empty'; then
39
+ exit 0
40
+ fi
41
+
42
+ # Extract commit message
43
+ MSG=""
44
+ if echo "$COMMAND" | grep -qE '\-m\s'; then
45
+ # -m "message" or -m 'message'
46
+ MSG=$(echo "$COMMAND" | grep -oP "\-m\s+['\"]?\K[^'\"]+(?=['\"]?)" | head -1)
47
+ fi
48
+
49
+ if [[ -z "$MSG" ]]; then
50
+ exit 0 # No inline message (might use editor)
51
+ fi
52
+
53
+ MAX_SUBJECT="${CC_COMMIT_MAX_SUBJECT:-72}"
54
+ WARN_SUBJECT="${CC_COMMIT_WARN_SUBJECT:-50}"
55
+
56
+ # Get subject line (first line before any newline)
57
+ SUBJECT=$(echo "$MSG" | head -1)
58
+ SUBJECT_LEN=${#SUBJECT}
59
+
60
+ # Check 1: Empty subject
61
+ if [[ -z "$SUBJECT" ]] || [[ "$SUBJECT_LEN" -lt 3 ]]; then
62
+ echo "WARNING: Commit subject is empty or too short." >&2
63
+ exit 0 # Warn only
64
+ fi
65
+
66
+ # Check 2: Subject too long
67
+ if [[ "$SUBJECT_LEN" -gt "$MAX_SUBJECT" ]]; then
68
+ echo "WARNING: Commit subject is $SUBJECT_LEN chars (max $MAX_SUBJECT)." >&2
69
+ echo "Subject: $(echo "$SUBJECT" | head -c 80)..." >&2
70
+ echo "Tip: Keep the subject under $MAX_SUBJECT chars. Use the body for details." >&2
71
+ fi
72
+
73
+ # Check 3: Vague subjects
74
+ SUBJECT_LOWER=$(echo "$SUBJECT" | tr '[:upper:]' '[:lower:]')
75
+ VAGUE_PATTERNS="^(update|fix|change|misc|wip|tmp|test|stuff|things|minor|cleanup)$|^(update|fix|change)\s+(code|file|stuff|things)$"
76
+ if echo "$SUBJECT_LOWER" | grep -qiE "$VAGUE_PATTERNS"; then
77
+ echo "WARNING: Commit subject is too vague: \"$SUBJECT\"" >&2
78
+ echo "Be specific about what changed and why." >&2
79
+ fi
80
+
81
+ # Check 4: Mega-commit (too many changes listed)
82
+ COMMA_COUNT=$(echo "$SUBJECT" | grep -o ',' | wc -l)
83
+ AND_COUNT=$(echo "$SUBJECT_LOWER" | grep -o ' and ' | wc -l)
84
+ if [[ $((COMMA_COUNT + AND_COUNT)) -ge 4 ]]; then
85
+ echo "WARNING: Commit subject lists many changes. Consider splitting into smaller commits." >&2
86
+ echo "Subject: $(echo "$SUBJECT" | head -c 80)" >&2
87
+ fi
88
+
89
+ exit 0
@@ -0,0 +1,89 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # hook-debug-wrapper.sh — Debug wrapper for any Claude Code hook
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Wraps any existing hook script and logs its input, output,
7
+ # exit code, and execution time. Invaluable for debugging hooks
8
+ # that silently fail or produce unexpected results.
9
+ #
10
+ # USAGE:
11
+ # Instead of:
12
+ # "command": "~/.claude/hooks/destructive-guard.sh"
13
+ # Use:
14
+ # "command": "~/.claude/hooks/hook-debug-wrapper.sh ~/.claude/hooks/destructive-guard.sh"
15
+ #
16
+ # Or set CC_HOOK_DEBUG=1 to log all hooks (requires wrapper for each).
17
+ #
18
+ # WHAT IT LOGS:
19
+ # - Timestamp
20
+ # - Hook script path
21
+ # - Input JSON (truncated to 500 chars)
22
+ # - Exit code
23
+ # - stdout (truncated)
24
+ # - stderr (truncated)
25
+ # - Execution time in ms
26
+ #
27
+ # LOG LOCATION: ~/.claude/hook-debug.log
28
+ #
29
+ # TRIGGER: Any (wraps any hook)
30
+ # MATCHER: Any
31
+ # ================================================================
32
+
33
+ HOOK_SCRIPT="$1"
34
+ DEBUG_LOG="${CC_HOOK_DEBUG_LOG:-$HOME/.claude/hook-debug.log}"
35
+
36
+ if [[ -z "$HOOK_SCRIPT" ]] || [[ ! -f "$HOOK_SCRIPT" ]]; then
37
+ echo "Usage: hook-debug-wrapper.sh <hook-script>" >&2
38
+ exit 0
39
+ fi
40
+
41
+ # Read input
42
+ INPUT=$(cat)
43
+
44
+ # Record start time
45
+ START_MS=$(($(date +%s%N) / 1000000))
46
+
47
+ # Run the actual hook, capturing all output
48
+ STDOUT_FILE=$(mktemp)
49
+ STDERR_FILE=$(mktemp)
50
+ echo "$INPUT" | bash "$HOOK_SCRIPT" > "$STDOUT_FILE" 2> "$STDERR_FILE"
51
+ EXIT_CODE=$?
52
+
53
+ END_MS=$(($(date +%s%N) / 1000000))
54
+ ELAPSED=$((END_MS - START_MS))
55
+
56
+ # Read outputs
57
+ STDOUT_CONTENT=$(cat "$STDOUT_FILE")
58
+ STDERR_CONTENT=$(cat "$STDERR_FILE")
59
+ rm -f "$STDOUT_FILE" "$STDERR_FILE"
60
+
61
+ # Log
62
+ HOOK_NAME=$(basename "$HOOK_SCRIPT")
63
+ INPUT_PREVIEW=$(echo "$INPUT" | head -c 500)
64
+ STDOUT_PREVIEW=$(echo "$STDOUT_CONTENT" | head -c 300)
65
+ STDERR_PREVIEW=$(echo "$STDERR_CONTENT" | head -c 300)
66
+
67
+ mkdir -p "$(dirname "$DEBUG_LOG")" 2>/dev/null
68
+ {
69
+ echo "=== $(date -Iseconds) === ${HOOK_NAME} ==="
70
+ echo "exit: ${EXIT_CODE} (${ELAPSED}ms)"
71
+ if [[ -n "$STDERR_PREVIEW" ]]; then
72
+ echo "stderr: ${STDERR_PREVIEW}"
73
+ fi
74
+ if [[ -n "$STDOUT_PREVIEW" ]]; then
75
+ echo "stdout: ${STDOUT_PREVIEW}"
76
+ fi
77
+ echo "input: ${INPUT_PREVIEW}"
78
+ echo ""
79
+ } >> "$DEBUG_LOG"
80
+
81
+ # Pass through the original output
82
+ if [[ -n "$STDOUT_CONTENT" ]]; then
83
+ echo "$STDOUT_CONTENT"
84
+ fi
85
+ if [[ -n "$STDERR_CONTENT" ]]; then
86
+ echo "$STDERR_CONTENT" >&2
87
+ fi
88
+
89
+ exit "$EXIT_CODE"
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # loop-detector.sh — Detect and break command repetition loops
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes gets stuck repeating the same command
7
+ # or cycle of commands. This hook detects repetition and warns
8
+ # before the loop wastes context and time.
9
+ #
10
+ # HOW IT WORKS:
11
+ # 1. Records last N commands in a state file
12
+ # 2. Checks if the current command matches recent commands
13
+ # 3. If same command appears 3+ times in last 5 calls → warn
14
+ # 4. If same command appears 5+ times → block
15
+ #
16
+ # TRIGGER: PreToolUse
17
+ # MATCHER: "Bash"
18
+ #
19
+ # CONFIGURATION:
20
+ # CC_LOOP_WARN=3 — warn after this many repeats (default: 3)
21
+ # CC_LOOP_BLOCK=5 — block after this many repeats (default: 5)
22
+ # CC_LOOP_WINDOW=10 — number of recent commands to track (default: 10)
23
+ # ================================================================
24
+
25
+ INPUT=$(cat)
26
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
27
+
28
+ if [[ -z "$COMMAND" ]]; then
29
+ exit 0
30
+ fi
31
+
32
+ STATE_FILE="/tmp/cc-loop-detector-history"
33
+ WARN_THRESHOLD="${CC_LOOP_WARN:-3}"
34
+ BLOCK_THRESHOLD="${CC_LOOP_BLOCK:-5}"
35
+ WINDOW="${CC_LOOP_WINDOW:-10}"
36
+
37
+ # Normalize command (strip whitespace variations)
38
+ NORMALIZED=$(echo "$COMMAND" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')
39
+
40
+ # Record command
41
+ echo "$NORMALIZED" >> "$STATE_FILE"
42
+
43
+ # Keep only last N entries
44
+ if [ -f "$STATE_FILE" ]; then
45
+ tail -n "$WINDOW" "$STATE_FILE" > "${STATE_FILE}.tmp"
46
+ mv "${STATE_FILE}.tmp" "$STATE_FILE"
47
+ fi
48
+
49
+ # Count occurrences of current command in history
50
+ COUNT=$(grep -cF "$NORMALIZED" "$STATE_FILE" 2>/dev/null || echo 0)
51
+
52
+ if [ "$COUNT" -ge "$BLOCK_THRESHOLD" ]; then
53
+ echo "BLOCKED: Command repeated $COUNT times in last $WINDOW calls." >&2
54
+ echo "" >&2
55
+ echo "Command: $COMMAND" >&2
56
+ echo "" >&2
57
+ echo "This looks like an infinite loop. Try a different approach." >&2
58
+ echo "To reset: rm /tmp/cc-loop-detector-history" >&2
59
+ exit 2
60
+ elif [ "$COUNT" -ge "$WARN_THRESHOLD" ]; then
61
+ echo "WARNING: Command repeated $COUNT times. Possible loop." >&2
62
+ echo "Command: $(echo "$COMMAND" | head -c 100)" >&2
63
+ fi
64
+
65
+ exit 0
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # session-handoff.sh — Auto-save session state for next session
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # When a Claude Code session ends (Stop event), automatically
7
+ # captures the current state and writes a handoff file that the
8
+ # next session can read to resume work.
9
+ #
10
+ # Solves the "I forgot what I was doing" problem after /compact
11
+ # or session restart.
12
+ #
13
+ # TRIGGER: Stop
14
+ # MATCHER: ""
15
+ #
16
+ # WHAT IT CAPTURES:
17
+ # - Current git branch and uncommitted changes
18
+ # - Last 5 commands from shell history
19
+ # - Modified files (git diff --stat)
20
+ # - Current working directory
21
+ # - Timestamp
22
+ #
23
+ # OUTPUT: ~/.claude/session-handoff.md
24
+ # Next session can read this to understand where the last one left off.
25
+ #
26
+ # CONFIGURATION:
27
+ # CC_HANDOFF_FILE — path to handoff file
28
+ # default: ~/.claude/session-handoff.md
29
+ # ================================================================
30
+
31
+ HANDOFF="${CC_HANDOFF_FILE:-$HOME/.claude/session-handoff.md}"
32
+
33
+ # Detect project directory from recent tool calls
34
+ PROJECT_DIR=""
35
+ if [ -d ".git" ]; then
36
+ PROJECT_DIR="$(pwd)"
37
+ elif [ -f "$HOME/.claude/projects" ]; then
38
+ PROJECT_DIR=$(ls -td "$HOME/.claude/projects/"*/ 2>/dev/null | head -1)
39
+ fi
40
+
41
+ {
42
+ echo "# Session Handoff"
43
+ echo ""
44
+ echo "**Last session ended:** $(date -Iseconds)"
45
+ echo ""
46
+
47
+ # Git state
48
+ if command -v git &>/dev/null && [ -d ".git" ]; then
49
+ BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
50
+ echo "## Git State"
51
+ echo "- Branch: \`$BRANCH\`"
52
+
53
+ MODIFIED=$(git diff --stat 2>/dev/null | tail -1)
54
+ if [ -n "$MODIFIED" ]; then
55
+ echo "- Changes: $MODIFIED"
56
+ echo ""
57
+ echo "### Modified files"
58
+ echo '```'
59
+ git diff --name-only 2>/dev/null | head -10
60
+ echo '```'
61
+ else
62
+ echo "- Changes: clean working tree"
63
+ fi
64
+
65
+ STAGED=$(git diff --cached --stat 2>/dev/null | tail -1)
66
+ if [ -n "$STAGED" ]; then
67
+ echo "- Staged: $STAGED"
68
+ fi
69
+
70
+ LAST_COMMIT=$(git log --oneline -1 2>/dev/null)
71
+ echo "- Last commit: $LAST_COMMIT"
72
+ echo ""
73
+ fi
74
+
75
+ # Recent blocked commands (if any)
76
+ BLOCK_LOG="$HOME/.claude/blocked-commands.log"
77
+ if [ -f "$BLOCK_LOG" ]; then
78
+ RECENT_BLOCKS=$(tail -3 "$BLOCK_LOG" 2>/dev/null)
79
+ if [ -n "$RECENT_BLOCKS" ]; then
80
+ echo "## Recent Blocks"
81
+ echo '```'
82
+ echo "$RECENT_BLOCKS"
83
+ echo '```'
84
+ echo ""
85
+ fi
86
+ fi
87
+
88
+ # Session errors
89
+ ERROR_LOG="$HOME/.claude/session-errors.log"
90
+ if [ -f "$ERROR_LOG" ]; then
91
+ RECENT_ERRORS=$(tail -3 "$ERROR_LOG" 2>/dev/null)
92
+ if [ -n "$RECENT_ERRORS" ]; then
93
+ echo "## Recent Errors"
94
+ echo '```'
95
+ echo "$RECENT_ERRORS"
96
+ echo '```'
97
+ echo ""
98
+ fi
99
+ fi
100
+
101
+ echo "---"
102
+ echo "*Auto-generated by session-handoff hook. Read this at session start to resume.*"
103
+
104
+ } > "$HANDOFF"
105
+
106
+ exit 0
package/index.mjs CHANGED
@@ -82,6 +82,7 @@ const JSON_OUTPUT = process.argv.includes('--json');
82
82
  const LINT = process.argv.includes('--lint');
83
83
  const DIFF_IDX = process.argv.findIndex(a => a === '--diff');
84
84
  const DIFF_FILE = DIFF_IDX !== -1 ? process.argv[DIFF_IDX + 1] : null;
85
+ const SHARE = process.argv.includes('--share');
85
86
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
86
87
  const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
87
88
 
@@ -103,6 +104,7 @@ if (HELP) {
103
104
  npx cc-safe-setup --audit --json Machine-readable output for CI/CD
104
105
  npx cc-safe-setup --scan Detect tech stack, recommend hooks
105
106
  npx cc-safe-setup --learn Learn from your block history
107
+ npx cc-safe-setup --share Generate shareable URL for your setup
106
108
  npx cc-safe-setup --diff <file> Compare your settings with another file
107
109
  npx cc-safe-setup --lint Static analysis of hook configuration
108
110
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
@@ -319,6 +321,8 @@ function examples() {
319
321
  'test-before-push.sh': 'Block git push when tests have not passed',
320
322
  'timeout-guard.sh': 'Warn before long-running commands (servers, watchers)',
321
323
  'git-config-guard.sh': 'Block git config --global modifications',
324
+ 'case-sensitive-guard.sh': 'Detect case-insensitive FS collisions (exFAT/NTFS/HFS+)',
325
+ 'compound-command-approver.sh': 'Auto-approve safe compound commands (cd && git log)',
322
326
  },
323
327
  'Auto-Approve': {
324
328
  'auto-approve-build.sh': 'Auto-approve npm/yarn/cargo/go build, test, lint',
@@ -339,15 +343,19 @@ function examples() {
339
343
  'Recovery': {
340
344
  'auto-checkpoint.sh': 'Auto-commit after edits for rollback protection',
341
345
  'auto-snapshot.sh': 'Auto-save file snapshots before edits (rollback protection)',
346
+ 'session-checkpoint.sh': 'Save session state before context compaction',
342
347
  },
343
348
  'UX': {
344
349
  'notify-waiting.sh': 'Desktop notification when Claude waits for input',
350
+ 'tmp-cleanup.sh': 'Clean up /tmp/claude-*-cwd files on session end',
351
+ 'hook-debug-wrapper.sh': 'Wrap any hook to log input/output/exit/timing',
352
+ 'loop-detector.sh': 'Detect and break command repetition loops',
345
353
  },
346
354
  };
347
355
 
348
356
  console.log();
349
357
  console.log(c.bold + ' cc-safe-setup --examples' + c.reset);
350
- console.log(c.dim + ' 25 hooks beyond the 8 built-in ones' + c.reset);
358
+ console.log(c.dim + ' 30 hooks beyond the 8 built-in ones' + c.reset);
351
359
  console.log();
352
360
 
353
361
  for (const [cat, hooks] of Object.entries(CATEGORIES)) {
@@ -772,6 +780,50 @@ async function fullSetup() {
772
780
  console.log();
773
781
  }
774
782
 
783
+ function share() {
784
+ console.log();
785
+ console.log(c.bold + ' cc-safe-setup --share' + c.reset);
786
+ console.log();
787
+
788
+ if (!existsSync(SETTINGS_PATH)) {
789
+ console.log(c.red + ' No settings.json found.' + c.reset);
790
+ process.exit(1);
791
+ }
792
+
793
+ const settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
794
+
795
+ // Strip sensitive data — only keep hooks and permissions structure
796
+ const shareable = {
797
+ hooks: settings.hooks || {},
798
+ permissions: settings.permissions || {},
799
+ defaultMode: settings.defaultMode,
800
+ };
801
+
802
+ // Remove full file paths, keep only script names
803
+ for (const trigger of Object.keys(shareable.hooks)) {
804
+ for (const entry of shareable.hooks[trigger]) {
805
+ for (const h of (entry.hooks || [])) {
806
+ if (h.command) {
807
+ // Keep only the filename
808
+ h.command = h.command.split('/').pop();
809
+ }
810
+ }
811
+ }
812
+ }
813
+
814
+ const json = JSON.stringify(shareable);
815
+ const encoded = Buffer.from(json).toString('base64');
816
+ const url = 'https://yurukusa.github.io/cc-safe-setup/?config=' + encoded;
817
+
818
+ console.log(c.green + ' Shareable URL:' + c.reset);
819
+ console.log();
820
+ console.log(' ' + url);
821
+ console.log();
822
+ console.log(c.dim + ' Anyone with this URL can audit your hook setup in their browser.' + c.reset);
823
+ console.log(c.dim + ' Only hook names and permissions are shared (no file paths or secrets).' + c.reset);
824
+ console.log();
825
+ }
826
+
775
827
  function diff(otherFile) {
776
828
  console.log();
777
829
  console.log(c.bold + ' cc-safe-setup --diff' + c.reset);
@@ -1932,6 +1984,7 @@ async function main() {
1932
1984
  if (FULL) return fullSetup();
1933
1985
  if (DOCTOR) return doctor();
1934
1986
  if (WATCH) return watch();
1987
+ if (SHARE) return share();
1935
1988
  if (DIFF_FILE) return diff(DIFF_FILE);
1936
1989
  if (LINT) return lint();
1937
1990
  if (CREATE_DESC) return createHook(CREATE_DESC);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "3.4.0",
4
- "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 30 installable examples. Destructive blocker, branch guard, compound command approver, database wipe protection, tmp cleanup, and more.",
3
+ "version": "3.6.0",
4
+ "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 34 examples. Create, audit, lint, diff, share, watch, learn. Session handoff, loop detection, commit quality gate.",
5
5
  "main": "index.mjs",
6
6
  "bin": {
7
7
  "cc-safe-setup": "index.mjs"