cc-discipline 2.1.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 +44 -10
- package/lib/doctor.sh +2 -2
- package/lib/status.sh +2 -1
- package/package.json +1 -1
- package/templates/.claude/hooks/git-guard.sh +62 -0
- package/templates/.claude/settings.json +9 -0
package/init.sh
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
set -e
|
|
9
9
|
|
|
10
|
-
# ─── Version ───
|
|
11
|
-
|
|
10
|
+
# ─── Version (single source: package.json) ───
|
|
11
|
+
_INIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
if command -v node &>/dev/null && [ -f "$_INIT_DIR/package.json" ]; then
|
|
13
|
+
VERSION=$(node -p "require('$_INIT_DIR/package.json').version" 2>/dev/null)
|
|
14
|
+
fi
|
|
15
|
+
VERSION="${VERSION:-unknown}"
|
|
12
16
|
|
|
13
17
|
# ─── Parse CLI arguments ───
|
|
14
18
|
ARG_STACK=""
|
|
@@ -276,6 +280,7 @@ cp "$SCRIPT_DIR/templates/.claude/hooks/post-error-remind.sh" .claude/hooks/
|
|
|
276
280
|
cp "$SCRIPT_DIR/templates/.claude/hooks/streak-breaker.sh" .claude/hooks/
|
|
277
281
|
cp "$SCRIPT_DIR/templates/.claude/hooks/session-start.sh" .claude/hooks/
|
|
278
282
|
cp "$SCRIPT_DIR/templates/.claude/hooks/phase-gate.sh" .claude/hooks/
|
|
283
|
+
cp "$SCRIPT_DIR/templates/.claude/hooks/git-guard.sh" .claude/hooks/
|
|
279
284
|
cp "$SCRIPT_DIR/templates/.claude/hooks/action-counter.sh" .claude/hooks/
|
|
280
285
|
chmod +x .claude/hooks/*.sh
|
|
281
286
|
|
|
@@ -390,16 +395,45 @@ if [ ! -f "docs/debug-log.md" ]; then
|
|
|
390
395
|
cp "$SCRIPT_DIR/templates/docs/debug-log.md" docs/
|
|
391
396
|
fi
|
|
392
397
|
|
|
393
|
-
# ─── Install auto memory ───
|
|
398
|
+
# ─── Install auto memory (symlink to .claude/memory/) ───
|
|
394
399
|
echo -e "${GREEN}Installing auto memory...${NC}"
|
|
395
400
|
MEMORY_PROJECT_KEY=$(echo "$PROJECT_DIR" | sed 's|/|-|g')
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
+
MEMORY_CLAUDE_DIR="$HOME/.claude/projects/${MEMORY_PROJECT_KEY}"
|
|
402
|
+
MEMORY_LOCAL_DIR="$PROJECT_DIR/.claude/memory"
|
|
403
|
+
|
|
404
|
+
# Ensure local .claude/memory/ exists in repo
|
|
405
|
+
mkdir -p "$MEMORY_LOCAL_DIR"
|
|
406
|
+
|
|
407
|
+
# If MEMORY.md doesn't exist locally, seed from template
|
|
408
|
+
if [ ! -f "$MEMORY_LOCAL_DIR/MEMORY.md" ]; then
|
|
409
|
+
cp "$SCRIPT_DIR/templates/memory/MEMORY.md" "$MEMORY_LOCAL_DIR/MEMORY.md"
|
|
410
|
+
echo " ✓ Memory template installed to .claude/memory/MEMORY.md"
|
|
411
|
+
else
|
|
412
|
+
echo -e " ${YELLOW}.claude/memory/MEMORY.md already exists (preserved)${NC}"
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
# Create symlink: ~/.claude/projects/<key>/memory → .claude/memory/
|
|
416
|
+
mkdir -p "$MEMORY_CLAUDE_DIR"
|
|
417
|
+
if [ -L "$MEMORY_CLAUDE_DIR/memory" ]; then
|
|
418
|
+
# Symlink already exists — update it
|
|
419
|
+
rm "$MEMORY_CLAUDE_DIR/memory"
|
|
420
|
+
ln -s "$MEMORY_LOCAL_DIR" "$MEMORY_CLAUDE_DIR/memory"
|
|
421
|
+
echo " ✓ Memory symlink updated"
|
|
422
|
+
elif [ -d "$MEMORY_CLAUDE_DIR/memory" ]; then
|
|
423
|
+
# Existing real directory — migrate files then replace with symlink
|
|
424
|
+
for f in "$MEMORY_CLAUDE_DIR/memory/"*; do
|
|
425
|
+
[ -f "$f" ] || continue
|
|
426
|
+
fname=$(basename "$f")
|
|
427
|
+
if [ ! -f "$MEMORY_LOCAL_DIR/$fname" ]; then
|
|
428
|
+
cp "$f" "$MEMORY_LOCAL_DIR/$fname"
|
|
429
|
+
fi
|
|
430
|
+
done
|
|
431
|
+
rm -rf "$MEMORY_CLAUDE_DIR/memory"
|
|
432
|
+
ln -s "$MEMORY_LOCAL_DIR" "$MEMORY_CLAUDE_DIR/memory"
|
|
433
|
+
echo " ✓ Existing memory migrated to .claude/memory/ and symlinked"
|
|
401
434
|
else
|
|
402
|
-
|
|
435
|
+
ln -s "$MEMORY_LOCAL_DIR" "$MEMORY_CLAUDE_DIR/memory"
|
|
436
|
+
echo " ✓ Memory symlinked: ~/.claude/projects/.../ → .claude/memory/"
|
|
403
437
|
fi
|
|
404
438
|
|
|
405
439
|
# ─── Install global rules (optional) ───
|
|
@@ -446,7 +480,7 @@ if [ "$INSTALL_MODE" = "fresh" ]; then
|
|
|
446
480
|
echo -e " ${GREEN}.claude/settings.json${NC} ← Hooks configuration"
|
|
447
481
|
echo -e " ${GREEN}docs/progress.md${NC} ← Progress log (maintained by Claude)"
|
|
448
482
|
echo -e " ${GREEN}docs/debug-log.md${NC} ← Debug log (maintained by Claude)"
|
|
449
|
-
echo -e " ${GREEN}
|
|
483
|
+
echo -e " ${GREEN}.claude/memory/${NC} ← Auto memory (symlinked, lives in repo)"
|
|
450
484
|
echo ""
|
|
451
485
|
echo -e "${YELLOW}Next steps:${NC}"
|
|
452
486
|
echo " 1. Edit CLAUDE.md and fill in the [TODO] sections with project info"
|
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}/
|
|
56
|
+
echo -e "${GREEN}${HOOK_COUNT}/7${NC} (${HOOKS% })"
|
|
56
57
|
|
|
57
58
|
# Agents
|
|
58
59
|
echo -n "Agents: "
|
package/package.json
CHANGED
|
@@ -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
|