claude-raid 0.1.1 → 0.1.3

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.
Files changed (51) hide show
  1. package/README.md +298 -196
  2. package/bin/cli.js +45 -18
  3. package/package.json +1 -1
  4. package/src/descriptions.js +57 -0
  5. package/src/detect-browser.js +164 -0
  6. package/src/detect-package-manager.js +107 -0
  7. package/src/detect-project.js +44 -6
  8. package/src/doctor.js +12 -188
  9. package/src/init.js +192 -17
  10. package/src/merge-settings.js +63 -7
  11. package/src/remove.js +28 -4
  12. package/src/setup.js +405 -0
  13. package/src/ui.js +168 -0
  14. package/src/update.js +62 -5
  15. package/src/version-check.js +130 -0
  16. package/template/.claude/agents/archer.md +46 -51
  17. package/template/.claude/agents/rogue.md +43 -49
  18. package/template/.claude/agents/warrior.md +48 -53
  19. package/template/.claude/agents/wizard.md +65 -67
  20. package/template/.claude/hooks/raid-lib.sh +182 -0
  21. package/template/.claude/hooks/raid-pre-compact.sh +41 -0
  22. package/template/.claude/hooks/raid-session-end.sh +116 -0
  23. package/template/.claude/hooks/raid-session-start.sh +52 -0
  24. package/template/.claude/hooks/raid-stop.sh +68 -0
  25. package/template/.claude/hooks/raid-task-completed.sh +37 -0
  26. package/template/.claude/hooks/raid-task-created.sh +40 -0
  27. package/template/.claude/hooks/raid-teammate-idle.sh +28 -0
  28. package/template/.claude/hooks/validate-browser-cleanup.sh +36 -0
  29. package/template/.claude/hooks/validate-browser-tests-exist.sh +52 -0
  30. package/template/.claude/hooks/validate-commit.sh +130 -0
  31. package/template/.claude/hooks/validate-dungeon.sh +114 -0
  32. package/template/.claude/hooks/validate-file-naming.sh +13 -27
  33. package/template/.claude/hooks/validate-no-placeholders.sh +11 -21
  34. package/template/.claude/hooks/validate-write-gate.sh +60 -0
  35. package/template/.claude/raid-rules.md +27 -18
  36. package/template/.claude/skills/raid-browser/SKILL.md +186 -0
  37. package/template/.claude/skills/raid-browser-chrome/SKILL.md +189 -0
  38. package/template/.claude/skills/raid-browser-playwright/SKILL.md +163 -0
  39. package/template/.claude/skills/raid-debugging/SKILL.md +6 -6
  40. package/template/.claude/skills/raid-design/SKILL.md +10 -10
  41. package/template/.claude/skills/raid-finishing/SKILL.md +11 -3
  42. package/template/.claude/skills/raid-implementation/SKILL.md +26 -11
  43. package/template/.claude/skills/raid-implementation-plan/SKILL.md +15 -4
  44. package/template/.claude/skills/raid-protocol/SKILL.md +57 -32
  45. package/template/.claude/skills/raid-review/SKILL.md +42 -13
  46. package/template/.claude/skills/raid-tdd/SKILL.md +45 -3
  47. package/template/.claude/skills/raid-verification/SKILL.md +12 -1
  48. package/template/.claude/hooks/validate-commit-message.sh +0 -78
  49. package/template/.claude/hooks/validate-phase-gate.sh +0 -60
  50. package/template/.claude/hooks/validate-tests-pass.sh +0 -43
  51. package/template/.claude/hooks/validate-verification.sh +0 -70
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # Raid lifecycle hook: Stop
3
+ # Detects phase transitions and injects human confirmation gate.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$SCRIPT_DIR/raid-lib.sh"
8
+
9
+ if [ "$RAID_ACTIVE" != "true" ]; then
10
+ exit 0
11
+ fi
12
+
13
+ if [ "$RAID_LIFECYCLE_PHASE_CONFIRM" != "true" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ STORED_PHASE="$RAID_PHASE"
18
+
19
+ # Phase ordering: name → rank for comparison
20
+ phase_rank() {
21
+ case "$1" in
22
+ design) echo 1 ;;
23
+ plan) echo 2 ;;
24
+ implementation) echo 3 ;;
25
+ review) echo 4 ;;
26
+ finishing) echo 5 ;;
27
+ *) echo 0 ;;
28
+ esac
29
+ }
30
+
31
+ # Detect current phase from Dungeon file
32
+ # Only matches structured markers: <!-- RAID_PHASE: plan -->
33
+ DETECTED_PHASE="$STORED_PHASE"
34
+ if [ -f ".claude/raid-dungeon.md" ]; then
35
+ BEST_RANK=0
36
+ BEST_PHASE="$STORED_PHASE"
37
+ for phase_name in $(grep -oE '<!-- RAID_PHASE: (design|plan|implementation|review|finishing) -->' ".claude/raid-dungeon.md" 2>/dev/null | grep -oE '(design|plan|implementation|review|finishing)'); do
38
+ RANK=$(phase_rank "$phase_name")
39
+ if [ "$RANK" -gt "$BEST_RANK" ]; then
40
+ BEST_RANK=$RANK
41
+ BEST_PHASE=$phase_name
42
+ fi
43
+ done
44
+ DETECTED_PHASE="$BEST_PHASE"
45
+ fi
46
+
47
+ # Compare phases by rank
48
+ STORED_RANK=$(phase_rank "$STORED_PHASE")
49
+ DETECTED_RANK=$(phase_rank "$DETECTED_PHASE")
50
+
51
+ if [ "$DETECTED_RANK" -gt "$STORED_RANK" ] 2>/dev/null; then
52
+ # Update raid-session with new phase name
53
+ if command -v jq >/dev/null 2>&1; then
54
+ jq --arg phase "$DETECTED_PHASE" '.phase = $phase' ".claude/raid-session" > ".claude/raid-session.tmp" 2>/dev/null && \
55
+ mv ".claude/raid-session.tmp" ".claude/raid-session"
56
+ fi
57
+
58
+ cat <<ENDJSON
59
+ {
60
+ "hookSpecificOutput": {
61
+ "hookEventName": "Stop",
62
+ "additionalContext": "Phase transition detected ($STORED_PHASE → $DETECTED_PHASE). The Wizard must confirm with the human before opening the next phase."
63
+ }
64
+ }
65
+ ENDJSON
66
+ fi
67
+
68
+ exit 0
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # Raid lifecycle hook: TaskCompleted
3
+ # Blocks task completion if tests haven't run recently.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$SCRIPT_DIR/raid-lib.sh"
8
+
9
+ if [ "$RAID_ACTIVE" != "true" ]; then
10
+ exit 0
11
+ fi
12
+
13
+ if [ "$RAID_LIFECYCLE_COMPLETION_GATE" != "true" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ TEST_RUN_FILE=".claude/raid-last-test-run"
18
+
19
+ if [ ! -f "$TEST_RUN_FILE" ]; then
20
+ raid_block "Tests must pass before marking a task complete. No test run recorded — run your test command first."
21
+ fi
22
+
23
+ LAST_RUN=$(cat "$TEST_RUN_FILE" 2>/dev/null | tr -d '[:space:]')
24
+ NOW=$(date +%s)
25
+ WINDOW=$((RAID_LIFECYCLE_TEST_WINDOW * 60))
26
+ # Guard against corrupted/non-numeric timestamp
27
+ case "$LAST_RUN" in
28
+ ''|*[!0-9]*) raid_block "Tests must pass before marking a task complete. Test run timestamp is invalid — run your test command first." ;;
29
+ esac
30
+ AGE=$((NOW - LAST_RUN))
31
+
32
+ if [ "$AGE" -gt "$WINDOW" ]; then
33
+ MINS_AGO=$((AGE / 60))
34
+ raid_block "Tests last ran $MINS_AGO minutes ago (window is $RAID_LIFECYCLE_TEST_WINDOW min). Run tests again before marking this task complete."
35
+ fi
36
+
37
+ exit 0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # Raid lifecycle hook: TaskCreated
3
+ # Validates task subjects are descriptive enough.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$SCRIPT_DIR/raid-lib.sh"
8
+
9
+ if [ "$RAID_ACTIVE" != "true" ]; then
10
+ exit 0
11
+ fi
12
+
13
+ if [ "$RAID_LIFECYCLE_TASK_VALIDATION" != "true" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ raid_read_lifecycle_input
18
+
19
+ SUBJECT=$(echo "$RAID_HOOK_INPUT" | jq -r '.task_subject // ""')
20
+
21
+ if [ -z "$SUBJECT" ]; then
22
+ raid_block "Task subject cannot be empty."
23
+ fi
24
+
25
+ if [ "${#SUBJECT}" -lt 10 ]; then
26
+ raid_block "Task subject too short (${#SUBJECT} chars). Be more descriptive (min 10 chars)."
27
+ fi
28
+
29
+ FIRST_WORD=$(echo "$SUBJECT" | awk '{print tolower($1)}')
30
+ WORD_COUNT=$(echo "$SUBJECT" | wc -w | tr -d ' ')
31
+
32
+ if [ "$WORD_COUNT" -le 1 ]; then
33
+ case "$FIRST_WORD" in
34
+ fix|update|task|do|change|add|remove|delete)
35
+ raid_block "Task subject '$SUBJECT' is too generic. Describe what specifically needs to be done."
36
+ ;;
37
+ esac
38
+ fi
39
+
40
+ exit 0
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # Raid lifecycle hook: TeammateIdle
3
+ # Nudges idle agents to pick up unclaimed tasks.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$SCRIPT_DIR/raid-lib.sh"
8
+
9
+ if [ "$RAID_ACTIVE" != "true" ]; then
10
+ exit 0
11
+ fi
12
+
13
+ if [ "$RAID_LIFECYCLE_NUDGE" != "true" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ raid_read_lifecycle_input
18
+ TEAMMATE=$(echo "$RAID_HOOK_INPUT" | jq -r '.teammate_name // "Agent"')
19
+
20
+ cat <<ENDJSON
21
+ {
22
+ "hookSpecificOutput": {
23
+ "hookEventName": "TeammateIdle",
24
+ "additionalContext": "$TEAMMATE: Unclaimed tasks remain on the board. Pick up the next available task and report your plan before starting."
25
+ }
26
+ }
27
+ ENDJSON
28
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # Raid quality gate: warns if browser testing ports are still occupied
3
+ # PostToolUse hook for Bash commands
4
+ # Checks portRange from .claude/raid.json
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$SCRIPT_DIR/raid-lib.sh"
9
+
10
+ # Skip if no active Raid session or browser testing not enabled
11
+ if [ "$RAID_ACTIVE" != "true" ] || [ "$RAID_BROWSER_ENABLED" != "true" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Skip if no port range configured
16
+ if [ -z "$RAID_BROWSER_PORT_START" ] || [ -z "$RAID_BROWSER_PORT_END" ]; then
17
+ exit 0
18
+ fi
19
+
20
+ # Check if any ports in range are still occupied
21
+ LEAKED_PORTS=""
22
+ for PORT in $(seq "$RAID_BROWSER_PORT_START" "$RAID_BROWSER_PORT_END"); do
23
+ if lsof -i :"$PORT" > /dev/null 2>&1; then
24
+ LEAKED_PORTS="${LEAKED_PORTS} ${PORT}"
25
+ fi
26
+ done
27
+
28
+ if [ -n "$LEAKED_PORTS" ]; then
29
+ MSG="Raid Quality Check:\nWARNING: Browser testing ports still in use:${LEAKED_PORTS}\nThis may indicate a failed cleanup. To free them:\n"
30
+ for PORT in $LEAKED_PORTS; do
31
+ MSG="${MSG} kill \$(lsof -t -i :${PORT})\n"
32
+ done
33
+ raid_warn "$MSG"
34
+ fi
35
+
36
+ exit 0
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # Raid quality gate: warns if browser-facing code is committed without Playwright tests
3
+ # PreToolUse hook for Bash commands containing 'git commit'
4
+ # Checks for test files in the Playwright test directory
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$SCRIPT_DIR/raid-lib.sh"
9
+
10
+ raid_read_input
11
+
12
+ # Only check git commit commands
13
+ if ! echo "$RAID_COMMAND" | grep -qE 'git commit'; then
14
+ exit 0
15
+ fi
16
+
17
+ # Skip if no active Raid session or browser testing not enabled
18
+ if [ "$RAID_ACTIVE" != "true" ] || [ "$RAID_BROWSER_ENABLED" != "true" ]; then
19
+ exit 0
20
+ fi
21
+
22
+ # Get staged files that are browser-facing code
23
+ BROWSER_FILES=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null | \
24
+ grep -E '\.(tsx|jsx|vue|svelte)$' | \
25
+ grep -E '^(src|app|pages|components)/' || true)
26
+
27
+ if [ -z "$BROWSER_FILES" ]; then
28
+ exit 0
29
+ fi
30
+
31
+ # Check if any Playwright test files exist
32
+ HAS_BROWSER_TESTS=false
33
+ for DIR in tests/e2e e2e test/e2e; do
34
+ if [ -d "$DIR" ]; then
35
+ SPEC_COUNT=$(find "$DIR" -name '*.spec.ts' -o -name '*.spec.js' 2>/dev/null | wc -l | tr -d ' ')
36
+ if [ "$SPEC_COUNT" -gt "0" ]; then
37
+ HAS_BROWSER_TESTS=true
38
+ break
39
+ fi
40
+ fi
41
+ done
42
+
43
+ if [ "$HAS_BROWSER_TESTS" = false ]; then
44
+ MSG="Raid Quality Check:\nWARNING: Browser-facing code modified but no Playwright tests found.\nChanged files:\n"
45
+ while IFS= read -r FILE; do
46
+ MSG="${MSG} - ${FILE}\n"
47
+ done <<< "$BROWSER_FILES"
48
+ MSG="${MSG}\nIf this component doesn't need browser testing, this is safe to ignore."
49
+ raid_warn "$MSG"
50
+ fi
51
+
52
+ exit 0
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env bash
2
+ # Raid quality gate: consolidated commit validation hook
3
+ # PreToolUse hook for Bash commands containing 'git commit'
4
+ # Consolidates: validate-commit-message.sh, validate-tests-pass.sh, validate-verification.sh
5
+ # Cross-platform: uses grep -E (not grep -P)
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/raid-lib.sh"
10
+
11
+ raid_read_input
12
+
13
+ # Only check git commit commands
14
+ if ! echo "$RAID_COMMAND" | grep -qE 'git commit'; then
15
+ exit 0
16
+ fi
17
+
18
+ # --- Extract commit message ---
19
+ MSG=""
20
+ if echo "$RAID_COMMAND" | grep -qE -- '-m '; then
21
+ # Try double-quoted: -m "..."
22
+ MSG=$(echo "$RAID_COMMAND" | sed -n 's/.*-m "\([^"]*\)".*/\1/p' | head -1)
23
+ # Try single-quoted: -m '...'
24
+ if [ -z "$MSG" ]; then
25
+ MSG=$(echo "$RAID_COMMAND" | sed -n "s/.*-m '\\([^']*\\)'.*/\\1/p" | head -1)
26
+ fi
27
+ fi
28
+
29
+ # Try heredoc pattern
30
+ if [ -z "$MSG" ]; then
31
+ MSG=$(echo "$RAID_COMMAND" | sed -n 's/.*cat <<.*//;n;s/^ *//;p' | head -1)
32
+ fi
33
+
34
+ # If no message found (editor mode), allow
35
+ if [ -z "$MSG" ]; then
36
+ exit 0
37
+ fi
38
+
39
+ # Use first line only for checks
40
+ MSG=$(echo "$MSG" | head -1)
41
+
42
+ # ============================================================
43
+ # Check 1: Message format (always active, no session required)
44
+ # ============================================================
45
+
46
+ # Conventional commit format
47
+ if ! echo "$MSG" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .+'; then
48
+ raid_block "COMMIT: Message must follow conventional commit format (type(scope): description). Got: '$MSG'"
49
+ fi
50
+
51
+ # Minimum length
52
+ MIN_LENGTH="${RAID_COMMIT_MIN_LENGTH:-15}"
53
+ MSG_LENGTH=${#MSG}
54
+ if [ "$MSG_LENGTH" -lt "$MIN_LENGTH" ]; then
55
+ raid_block "COMMIT: Message too short (${MSG_LENGTH} chars, minimum ${MIN_LENGTH})."
56
+ fi
57
+
58
+ # Block generic messages
59
+ LOWER_MSG=$(echo "$MSG" | tr '[:upper:]' '[:lower:]')
60
+ case "$LOWER_MSG" in
61
+ update|fix|change|modify|edit|wip|temp|test|stuff|things|misc)
62
+ raid_block "COMMIT: Message is too generic. Describe WHAT changed and WHY."
63
+ ;;
64
+ esac
65
+
66
+ # ============================================================
67
+ # Check 2: Tests pass (Raid-session only)
68
+ # ============================================================
69
+
70
+ if [ "$RAID_ACTIVE" = "true" ] && [ -n "$RAID_TEST_CMD" ]; then
71
+ # TRUST: RAID_TEST_CMD comes from project-local raid.json — user-controlled, not untrusted input
72
+ set +e
73
+ (eval "$RAID_TEST_CMD") > /dev/null 2>&1
74
+ _test_rc=$?
75
+ set -e
76
+ if [ "$_test_rc" -ne 0 ]; then
77
+ raid_block "TESTS: Tests failed. Fix before committing. Command: $RAID_TEST_CMD"
78
+ fi
79
+ # Run browser tests if enabled and Playwright is installed
80
+ if [ "$RAID_BROWSER_ENABLED" = "true" ] && [ -n "$RAID_BROWSER_PW_CONFIG" ] && [ -f "$RAID_BROWSER_PW_CONFIG" ]; then
81
+ set +e
82
+ ($RAID_BROWSER_EXEC_CMD playwright test --reporter=list) > /dev/null 2>&1
83
+ _pw_rc=$?
84
+ set -e
85
+ if [ "$_pw_rc" -ne 0 ]; then
86
+ raid_block "BROWSER TESTS: Playwright tests failed. Fix before committing. Command: $RAID_BROWSER_EXEC_CMD playwright test"
87
+ fi
88
+ fi
89
+
90
+ # Write timestamp on success (only when ALL tests pass — unit AND browser)
91
+ mkdir -p .claude
92
+ date +%s > .claude/raid-last-test-run
93
+ fi
94
+
95
+ # ============================================================
96
+ # Check 3: Verification (Raid-session only, completion commits)
97
+ # ============================================================
98
+
99
+ if [ "$RAID_ACTIVE" = "true" ]; then
100
+ HAS_COMPLETION=false
101
+ for WORD in "complete" "done" "finish" "final"; do
102
+ if echo "$LOWER_MSG" | grep -qiw "$WORD"; then
103
+ HAS_COMPLETION=true
104
+ break
105
+ fi
106
+ done
107
+
108
+ if [ "$HAS_COMPLETION" = "true" ]; then
109
+ TIMESTAMP_FILE=".claude/raid-last-test-run"
110
+ MAX_AGE=600
111
+
112
+ if [ ! -f "$TIMESTAMP_FILE" ]; then
113
+ raid_block "VERIFICATION: Commit claims completion but no test run evidence found. Run tests before claiming work is complete."
114
+ fi
115
+
116
+ LAST_RUN=$(cat "$TIMESTAMP_FILE" | tr -d '[:space:]')
117
+ NOW=$(date +%s)
118
+ # Guard against corrupted/non-numeric timestamp
119
+ case "$LAST_RUN" in
120
+ ''|*[!0-9]*) raid_block "VERIFICATION: Test run timestamp is corrupted. Run tests again before claiming completion." ;;
121
+ esac
122
+ AGE=$((NOW - LAST_RUN))
123
+
124
+ if [ "$AGE" -gt "$MAX_AGE" ]; then
125
+ raid_block "VERIFICATION: Last test run was $((AGE / 60)) minutes ago. Run tests again before claiming completion."
126
+ fi
127
+ fi
128
+ fi
129
+
130
+ exit 0
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # Raid dungeon discipline: validates Dungeon entry format, evidence, and phase consistency
3
+ # PostToolUse hook for Write|Edit operations on Dungeon files.
4
+ set -euo pipefail
5
+
6
+ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ source "$HOOK_DIR/raid-lib.sh"
8
+
9
+ raid_read_input
10
+
11
+ # No file path — nothing to validate
12
+ if [ -z "${RAID_FILE_PATH:-}" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ # Only check Dungeon files
17
+ case "$RAID_FILE_PATH" in
18
+ */.claude/raid-dungeon.md|*/.claude/raid-dungeon-phase-*.md) ;;
19
+ .claude/raid-dungeon.md|.claude/raid-dungeon-phase-*.md) ;;
20
+ *) exit 0 ;;
21
+ esac
22
+
23
+ # No active session — skip validation
24
+ if [ "$RAID_ACTIVE" = "false" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ # Read the actual file content
29
+ if [ ! -f "$RAID_FILE_PATH" ]; then
30
+ exit 0
31
+ fi
32
+
33
+ issues=""
34
+
35
+ while IFS= read -r line; do
36
+ # Skip empty lines
37
+ [ -z "$line" ] && continue
38
+
39
+ # Skip header lines (lines starting with #)
40
+ case "$line" in
41
+ \#*) continue ;;
42
+ esac
43
+
44
+ # Layer 1: Format check — must have a recognized prefix
45
+ has_prefix=false
46
+ entry_type=""
47
+ content_after_prefix=""
48
+
49
+ case "$line" in
50
+ "DUNGEON:"*|"📌 DUNGEON:"*)
51
+ has_prefix=true
52
+ entry_type="DUNGEON"
53
+ content_after_prefix="${line#*DUNGEON:}"
54
+ ;;
55
+ "UNRESOLVED:"*|"⚠️ UNRESOLVED:"*)
56
+ has_prefix=true
57
+ entry_type="UNRESOLVED"
58
+ ;;
59
+ "RESOLVED:"*|"✅ RESOLVED:"*)
60
+ has_prefix=true
61
+ entry_type="RESOLVED"
62
+ ;;
63
+ "TASK:"*|"📋 TASK:"*)
64
+ has_prefix=true
65
+ entry_type="TASK"
66
+ ;;
67
+ esac
68
+
69
+ if [ "$has_prefix" = "false" ]; then
70
+ issues="${issues}
71
+ - Unrecognized prefix on line: ${line:0:60}"
72
+ continue
73
+ fi
74
+
75
+ # Layer 2: Evidence check — only for pinned entries
76
+ if [ "$entry_type" = "DUNGEON" ]; then
77
+ # Strip leading whitespace from content after prefix
78
+ content_after_prefix="$(echo "$content_after_prefix" | sed 's/^[[:space:]]*//')"
79
+ content_len=${#content_after_prefix}
80
+ if [ "$content_len" -lt 50 ]; then
81
+ issues="${issues}
82
+ - Pinned entry too short. Include evidence."
83
+ fi
84
+
85
+ # Check that pinned entries reference at least two agents (survived challenge)
86
+ agent_count=0
87
+ echo "$content_after_prefix" | grep -qi "warrior" && agent_count=$((agent_count + 1))
88
+ echo "$content_after_prefix" | grep -qi "archer" && agent_count=$((agent_count + 1))
89
+ echo "$content_after_prefix" | grep -qi "rogue" && agent_count=$((agent_count + 1))
90
+ echo "$content_after_prefix" | grep -qi "wizard" && agent_count=$((agent_count + 1))
91
+ if [ "$agent_count" -lt 2 ]; then
92
+ issues="${issues}
93
+ - Pinned entry must reference at least 2 agents who verified it (e.g., 'verified by @Warrior and @Archer')."
94
+ fi
95
+ fi
96
+
97
+ # Layer 3: Phase consistency
98
+ if [ "$entry_type" = "TASK" ]; then
99
+ case "${RAID_PHASE:-}" in
100
+ design|implementation|review)
101
+ issues="${issues}
102
+ - TASK entries belong in Plan phase, not ${RAID_PHASE}."
103
+ ;;
104
+ esac
105
+ fi
106
+
107
+ done < "$RAID_FILE_PATH"
108
+
109
+ # Report all issues together
110
+ if [ -n "$issues" ]; then
111
+ raid_block "Dungeon validation failed:${issues}"
112
+ fi
113
+
114
+ exit 0
@@ -1,26 +1,20 @@
1
1
  #!/usr/bin/env bash
2
2
  # Raid quality gate: validates file naming conventions
3
3
  # PostToolUse hook for Write and Edit operations
4
- # Reads conventions from .claude/raid.json
4
+ # Sources raid-lib.sh for config and input parsing
5
5
  set -euo pipefail
6
6
 
7
- INPUT=$(cat)
8
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$SCRIPT_DIR/raid-lib.sh"
9
9
 
10
- if [ -z "$FILE_PATH" ]; then
10
+ raid_read_input
11
+
12
+ if [ -z "$RAID_FILE_PATH" ]; then
11
13
  exit 0
12
14
  fi
13
15
 
14
- RAID_CONFIG=".claude/raid.json"
15
16
  ISSUES=""
16
-
17
- # Read naming convention from config (default: none)
18
- NAMING="none"
19
- if [ -f "$RAID_CONFIG" ]; then
20
- NAMING=$(jq -r '.conventions.fileNaming // "none"' "$RAID_CONFIG")
21
- fi
22
-
23
- BASENAME=$(basename "$FILE_PATH")
17
+ BASENAME=$(basename "$RAID_FILE_PATH")
24
18
 
25
19
  # Check 1: No spaces in filenames (always enforced)
26
20
  if echo "$BASENAME" | grep -qE '[[:space:]]'; then
@@ -28,11 +22,11 @@ if echo "$BASENAME" | grep -qE '[[:space:]]'; then
28
22
  fi
29
23
 
30
24
  # Check 2: Naming convention (if configured)
31
- if [ "$NAMING" != "none" ]; then
25
+ if [ "$RAID_NAMING" != "none" ]; then
32
26
  # Strip extension for naming check
33
27
  NAME_PART=$(echo "$BASENAME" | sed 's/\.[^.]*$//')
34
28
 
35
- case "$NAMING" in
29
+ case "$RAID_NAMING" in
36
30
  kebab-case)
37
31
  if ! echo "$NAME_PART" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
38
32
  ISSUES="${ISSUES}NAMING: File '$BASENAME' does not follow kebab-case convention.\n"
@@ -51,18 +45,10 @@ if [ "$NAMING" != "none" ]; then
51
45
  esac
52
46
  fi
53
47
 
54
- # Check 3: Directory depth (default max 8)
55
- MAX_DEPTH=8
56
- if [ -f "$RAID_CONFIG" ]; then
57
- CONFIGURED_DEPTH=$(jq -r '.conventions.maxDepth // empty' "$RAID_CONFIG")
58
- if [ -n "$CONFIGURED_DEPTH" ]; then
59
- MAX_DEPTH="$CONFIGURED_DEPTH"
60
- fi
61
- fi
62
-
63
- DEPTH=$(echo "$FILE_PATH" | awk -F'/' '{print NF}')
64
- if [ "$DEPTH" -gt "$MAX_DEPTH" ]; then
65
- ISSUES="${ISSUES}STRUCTURE: File at depth $DEPTH ($FILE_PATH). Maximum is $MAX_DEPTH.\n"
48
+ # Check 3: Directory depth
49
+ DEPTH=$(echo "$RAID_FILE_PATH" | awk -F'/' '{print NF}')
50
+ if [ "$DEPTH" -gt "$RAID_MAX_DEPTH" ]; then
51
+ ISSUES="${ISSUES}STRUCTURE: File at depth $DEPTH ($RAID_FILE_PATH). Maximum is $RAID_MAX_DEPTH.\n"
66
52
  fi
67
53
 
68
54
  if [ -n "$ISSUES" ]; then
@@ -1,32 +1,22 @@
1
1
  #!/usr/bin/env bash
2
2
  # Raid quality gate: blocks placeholder text in specs and plans
3
3
  # PostToolUse hook for Write and Edit operations
4
- # Only checks files within configured specs/plans paths
4
+ # Sources raid-lib.sh for config and input parsing
5
5
  set -euo pipefail
6
6
 
7
- INPUT=$(cat)
8
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$SCRIPT_DIR/raid-lib.sh"
9
9
 
10
- if [ -z "$FILE_PATH" ]; then
11
- exit 0
12
- fi
10
+ raid_read_input
13
11
 
14
- RAID_CONFIG=".claude/raid.json"
15
-
16
- # Read paths from config (defaults)
17
- SPECS_PATH="docs/raid/specs"
18
- PLANS_PATH="docs/raid/plans"
19
- if [ -f "$RAID_CONFIG" ]; then
20
- CONFIGURED_SPECS=$(jq -r '.paths.specs // empty' "$RAID_CONFIG")
21
- CONFIGURED_PLANS=$(jq -r '.paths.plans // empty' "$RAID_CONFIG")
22
- [ -n "$CONFIGURED_SPECS" ] && SPECS_PATH="$CONFIGURED_SPECS"
23
- [ -n "$CONFIGURED_PLANS" ] && PLANS_PATH="$CONFIGURED_PLANS"
12
+ if [ -z "$RAID_FILE_PATH" ]; then
13
+ exit 0
24
14
  fi
25
15
 
26
16
  # Only check files in specs or plans directories
27
17
  IS_RAID_DOC=false
28
- case "$FILE_PATH" in
29
- "$SPECS_PATH"/*|"$PLANS_PATH"/*) IS_RAID_DOC=true ;;
18
+ case "$RAID_FILE_PATH" in
19
+ "$RAID_SPECS_PATH"/*|"$RAID_PLANS_PATH"/*) IS_RAID_DOC=true ;;
30
20
  esac
31
21
 
32
22
  if [ "$IS_RAID_DOC" = false ]; then
@@ -35,8 +25,8 @@ fi
35
25
 
36
26
  # Read the actual file content (tool_output is the tool's response message, not file content)
37
27
  CONTENT=""
38
- if [ -f "$FILE_PATH" ]; then
39
- CONTENT=$(cat "$FILE_PATH")
28
+ if [ -f "$RAID_FILE_PATH" ]; then
29
+ CONTENT=$(cat "$RAID_FILE_PATH")
40
30
  fi
41
31
 
42
32
  if [ -z "$CONTENT" ]; then
@@ -60,7 +50,7 @@ while IFS= read -r line; do
60
50
  done <<< "$CONTENT"
61
51
 
62
52
  if [ -n "$ISSUES" ]; then
63
- printf "Raid Placeholder Check:\nBLOCKED: Placeholders found in %s:\n%b\nRemove all placeholders before proceeding.\n" "$FILE_PATH" "$ISSUES" >&2
53
+ printf "Raid Placeholder Check:\nBLOCKED: Placeholders found in %s:\n%b\nRemove all placeholders before proceeding.\n" "$RAID_FILE_PATH" "$ISSUES" >&2
64
54
  exit 2
65
55
  fi
66
56