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.
- package/README.md +298 -196
- package/bin/cli.js +45 -18
- package/package.json +1 -1
- package/src/descriptions.js +57 -0
- package/src/detect-browser.js +164 -0
- package/src/detect-package-manager.js +107 -0
- package/src/detect-project.js +44 -6
- package/src/doctor.js +12 -188
- package/src/init.js +192 -17
- package/src/merge-settings.js +63 -7
- package/src/remove.js +28 -4
- package/src/setup.js +405 -0
- package/src/ui.js +168 -0
- package/src/update.js +62 -5
- package/src/version-check.js +130 -0
- package/template/.claude/agents/archer.md +46 -51
- package/template/.claude/agents/rogue.md +43 -49
- package/template/.claude/agents/warrior.md +48 -53
- package/template/.claude/agents/wizard.md +65 -67
- package/template/.claude/hooks/raid-lib.sh +182 -0
- package/template/.claude/hooks/raid-pre-compact.sh +41 -0
- package/template/.claude/hooks/raid-session-end.sh +116 -0
- package/template/.claude/hooks/raid-session-start.sh +52 -0
- package/template/.claude/hooks/raid-stop.sh +68 -0
- package/template/.claude/hooks/raid-task-completed.sh +37 -0
- package/template/.claude/hooks/raid-task-created.sh +40 -0
- package/template/.claude/hooks/raid-teammate-idle.sh +28 -0
- package/template/.claude/hooks/validate-browser-cleanup.sh +36 -0
- package/template/.claude/hooks/validate-browser-tests-exist.sh +52 -0
- package/template/.claude/hooks/validate-commit.sh +130 -0
- package/template/.claude/hooks/validate-dungeon.sh +114 -0
- package/template/.claude/hooks/validate-file-naming.sh +13 -27
- package/template/.claude/hooks/validate-no-placeholders.sh +11 -21
- package/template/.claude/hooks/validate-write-gate.sh +60 -0
- package/template/.claude/raid-rules.md +27 -18
- package/template/.claude/skills/raid-browser/SKILL.md +186 -0
- package/template/.claude/skills/raid-browser-chrome/SKILL.md +189 -0
- package/template/.claude/skills/raid-browser-playwright/SKILL.md +163 -0
- package/template/.claude/skills/raid-debugging/SKILL.md +6 -6
- package/template/.claude/skills/raid-design/SKILL.md +10 -10
- package/template/.claude/skills/raid-finishing/SKILL.md +11 -3
- package/template/.claude/skills/raid-implementation/SKILL.md +26 -11
- package/template/.claude/skills/raid-implementation-plan/SKILL.md +15 -4
- package/template/.claude/skills/raid-protocol/SKILL.md +57 -32
- package/template/.claude/skills/raid-review/SKILL.md +42 -13
- package/template/.claude/skills/raid-tdd/SKILL.md +45 -3
- package/template/.claude/skills/raid-verification/SKILL.md +12 -1
- package/template/.claude/hooks/validate-commit-message.sh +0 -78
- package/template/.claude/hooks/validate-phase-gate.sh +0 -60
- package/template/.claude/hooks/validate-tests-pass.sh +0 -43
- 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
|
-
#
|
|
4
|
+
# Sources raid-lib.sh for config and input parsing
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/raid-lib.sh"
|
|
9
9
|
|
|
10
|
-
|
|
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 [ "$
|
|
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 "$
|
|
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
|
|
55
|
-
|
|
56
|
-
if [ -
|
|
57
|
-
|
|
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
|
-
#
|
|
4
|
+
# Sources raid-lib.sh for config and input parsing
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/raid-lib.sh"
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
exit 0
|
|
12
|
-
fi
|
|
10
|
+
raid_read_input
|
|
13
11
|
|
|
14
|
-
|
|
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 "$
|
|
29
|
-
"$
|
|
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 "$
|
|
39
|
-
CONTENT=$(cat "$
|
|
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" "$
|
|
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
|
|