cc-discipline 2.2.0 → 2.3.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/init.sh CHANGED
@@ -280,6 +280,7 @@ cp "$SCRIPT_DIR/templates/.claude/hooks/post-error-remind.sh" .claude/hooks/
280
280
  cp "$SCRIPT_DIR/templates/.claude/hooks/streak-breaker.sh" .claude/hooks/
281
281
  cp "$SCRIPT_DIR/templates/.claude/hooks/session-start.sh" .claude/hooks/
282
282
  cp "$SCRIPT_DIR/templates/.claude/hooks/phase-gate.sh" .claude/hooks/
283
+ cp "$SCRIPT_DIR/templates/.claude/hooks/git-guard.sh" .claude/hooks/
283
284
  cp "$SCRIPT_DIR/templates/.claude/hooks/action-counter.sh" .claude/hooks/
284
285
  chmod +x .claude/hooks/*.sh
285
286
 
package/lib/doctor.sh CHANGED
@@ -44,7 +44,7 @@ done
44
44
  # 3. Hooks
45
45
  echo ""
46
46
  echo "Hooks:"
47
- for hook in pre-edit-guard streak-breaker post-error-remind session-start phase-gate action-counter; do
47
+ for hook in pre-edit-guard streak-breaker post-error-remind session-start phase-gate action-counter git-guard; do
48
48
  if [ -f ".claude/hooks/${hook}.sh" ]; then
49
49
  if [ -x ".claude/hooks/${hook}.sh" ]; then
50
50
  ok "${hook}.sh"
@@ -62,7 +62,7 @@ echo "Hook registration:"
62
62
  if [ -f ".claude/settings.json" ]; then
63
63
  if command -v jq &>/dev/null; then
64
64
  CONTENT=$(cat .claude/settings.json)
65
- for hook in pre-edit-guard streak-breaker post-error-remind session-start phase-gate action-counter; do
65
+ for hook in pre-edit-guard streak-breaker post-error-remind session-start phase-gate action-counter git-guard; do
66
66
  if echo "$CONTENT" | grep -q "$hook"; then
67
67
  ok "${hook} registered"
68
68
  else
package/lib/status.sh CHANGED
@@ -51,8 +51,9 @@ HOOKS=""
51
51
  [ -f ".claude/hooks/session-start.sh" ] && HOOKS="${HOOKS}session-start "
52
52
  [ -f ".claude/hooks/phase-gate.sh" ] && HOOKS="${HOOKS}phase-gate "
53
53
  [ -f ".claude/hooks/action-counter.sh" ] && HOOKS="${HOOKS}action-counter "
54
+ [ -f ".claude/hooks/git-guard.sh" ] && HOOKS="${HOOKS}git-guard "
54
55
  HOOK_COUNT=$(echo "$HOOKS" | wc -w | tr -d ' ')
55
- echo -e "${GREEN}${HOOK_COUNT}/6${NC} (${HOOKS% })"
56
+ echo -e "${GREEN}${HOOK_COUNT}/7${NC} (${HOOKS% })"
56
57
 
57
58
  # Agents
58
59
  echo -n "Agents: "
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-discipline",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Discipline framework for Claude Code — rules, hooks, and agents that keep AI on track",
5
5
  "bin": {
6
6
  "cc-discipline": "bin/cli.sh"
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # cc-discipline: Guard against destructive git commands
3
+ # PreToolUse on Bash — blocks git checkout/restore/reset --hard/clean -f without confirmation.
4
+
5
+ INPUT=$(cat)
6
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
7
+
8
+ if [ "$TOOL_NAME" != "Bash" ]; then
9
+ exit 0
10
+ fi
11
+
12
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
13
+
14
+ # Normalize: collapse whitespace, trim
15
+ CMD_NORM=$(echo "$CMD" | tr '\n' ' ' | sed 's/ */ /g')
16
+
17
+ BLOCKED=""
18
+ SUGGESTION=""
19
+
20
+ # git checkout . / git checkout -- <file> (discard working tree changes)
21
+ # But allow: git checkout <branch>, git checkout -b <branch>
22
+ if echo "$CMD_NORM" | grep -qE 'git\s+checkout\s+(\.|--\s)'; then
23
+ BLOCKED="git checkout (discards uncommitted changes)"
24
+ SUGGESTION="git stash"
25
+ fi
26
+
27
+ # git restore . / git restore <file> (without --staged)
28
+ if echo "$CMD_NORM" | grep -qE 'git\s+restore\s' && ! echo "$CMD_NORM" | grep -qE 'git\s+restore\s+--staged'; then
29
+ BLOCKED="git restore (discards uncommitted changes)"
30
+ SUGGESTION="git stash"
31
+ fi
32
+
33
+ # git reset --hard
34
+ if echo "$CMD_NORM" | grep -qE 'git\s+reset\s+--hard'; then
35
+ BLOCKED="git reset --hard (destroys all uncommitted changes)"
36
+ SUGGESTION="git stash && git reset"
37
+ fi
38
+
39
+ # git clean -f / -fd / -fx
40
+ if echo "$CMD_NORM" | grep -qE 'git\s+clean\s+-[a-z]*f'; then
41
+ BLOCKED="git clean -f (permanently deletes untracked files)"
42
+ SUGGESTION="git stash --include-untracked"
43
+ fi
44
+
45
+ # git branch -D (force delete unmerged branch)
46
+ if echo "$CMD_NORM" | grep -qE 'git\s+branch\s+-D\s'; then
47
+ BLOCKED="git branch -D (deletes branch even if not merged)"
48
+ SUGGESTION="git branch -d (safe delete, fails if not merged)"
49
+ fi
50
+
51
+ # git push --force / -f (to main/master)
52
+ if echo "$CMD_NORM" | grep -qE 'git\s+push\s+.*(-f|--force)' && echo "$CMD_NORM" | grep -qE '(main|master)'; then
53
+ BLOCKED="git push --force to main/master (rewrites shared history)"
54
+ SUGGESTION="git push --force-with-lease"
55
+ fi
56
+
57
+ if [ -n "$BLOCKED" ]; then
58
+ echo "GIT GUARD: Blocked destructive command: $BLOCKED. This command can permanently destroy uncommitted work. Before running this: (1) Check git status and git diff to see what would be lost. (2) If changes should be kept, run: $SUGGESTION first. (3) If you are certain the changes should be discarded, tell the user what will be lost and ask for explicit confirmation." >&2
59
+ exit 2
60
+ fi
61
+
62
+ exit 0
@@ -39,6 +39,15 @@
39
39
  }
40
40
  ]
41
41
  },
42
+ {
43
+ "matcher": "Bash",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/git-guard.sh\""
48
+ }
49
+ ]
50
+ },
42
51
  {
43
52
  "matcher": "ExitPlanMode",
44
53
  "hooks": [