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
package/src/doctor.js CHANGED
@@ -1,201 +1,25 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const { execSync } = require('child_process');
3
+ const { runChecks, runSetup } = require('./setup');
4
+ const { banner, header } = require('./ui');
7
5
 
8
- function tryExec(cmd) {
9
- try {
10
- return execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
11
- } catch {
12
- return null;
13
- }
14
- }
15
-
16
- function parseVersion(str) {
17
- const match = str && str.match(/(\d+)\.(\d+)\.(\d+)/);
18
- if (!match) return null;
19
- return { major: +match[1], minor: +match[2], patch: +match[3] };
20
- }
21
-
22
- function versionGte(v, min) {
23
- if (v.major !== min.major) return v.major > min.major;
24
- if (v.minor !== min.minor) return v.minor > min.minor;
25
- return v.patch >= min.patch;
6
+ function diagnose(opts) {
7
+ return runChecks(opts);
26
8
  }
27
9
 
28
- const MIN_CLAUDE = { major: 2, minor: 1, patch: 32 };
10
+ async function run() {
11
+ const { referenceCard } = require('./ui');
29
12
 
30
- function checkNode() {
31
- const v = process.version;
32
- return { id: 'node', ok: true, label: 'Node.js', detail: v };
33
- }
13
+ console.log('\n' + banner());
14
+ console.log(header('Diagnosing Wounds...') + '\n');
34
15
 
35
- function checkClaude(exec) {
36
- const raw = exec('claude --version');
37
- if (!raw) {
38
- return {
39
- id: 'claude',
40
- ok: false,
41
- label: 'Claude Code',
42
- detail: 'not found',
43
- hint: 'Install: npm install -g @anthropic-ai/claude-code',
44
- };
45
- }
46
- const ver = parseVersion(raw);
47
- if (!ver) {
48
- return {
49
- id: 'claude',
50
- ok: false,
51
- label: 'Claude Code',
52
- detail: `unknown version: ${raw}`,
53
- hint: 'Expected semver from "claude --version"',
54
- };
55
- }
56
- const ok = versionGte(ver, MIN_CLAUDE);
57
- const tag = `v${ver.major}.${ver.minor}.${ver.patch}`;
58
- return {
59
- id: 'claude',
60
- ok,
61
- label: 'Claude Code',
62
- detail: ok
63
- ? `${tag} (≥ ${MIN_CLAUDE.major}.${MIN_CLAUDE.minor}.${MIN_CLAUDE.patch} required)`
64
- : `${tag} — update required (≥ ${MIN_CLAUDE.major}.${MIN_CLAUDE.minor}.${MIN_CLAUDE.patch})`,
65
- hint: ok ? undefined : 'Update: npm update -g @anthropic-ai/claude-code',
66
- };
67
- }
16
+ const result = await runSetup();
68
17
 
69
- function checkTmux(exec) {
70
- const raw = exec('command -v tmux');
71
- if (!raw) {
72
- return {
73
- id: 'tmux',
74
- ok: false,
75
- label: 'tmux',
76
- detail: 'not installed',
77
- hint: process.platform === 'darwin'
78
- ? 'Install: brew install tmux'
79
- : 'Install tmux via your package manager (apt, dnf, brew, etc.)',
80
- };
18
+ if (!result.allOk && !process.stdin.isTTY) {
19
+ process.exitCode = 1;
81
20
  }
82
- return { id: 'tmux', ok: true, label: 'tmux', detail: 'installed' };
83
- }
84
-
85
- function checkTeammateMode(homedir) {
86
- const configPath = path.join(homedir, '.claude.json');
87
- if (!fs.existsSync(configPath)) {
88
- return {
89
- id: 'teammate-mode',
90
- ok: false,
91
- label: 'teammateMode',
92
- detail: 'not set — ~/.claude.json not found',
93
- hint: 'Create ~/.claude.json with: { "teammateMode": "tmux" }',
94
- };
95
- }
96
- let config;
97
- try {
98
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
99
- } catch {
100
- return {
101
- id: 'teammate-mode',
102
- ok: false,
103
- label: 'teammateMode',
104
- detail: '~/.claude.json is not valid JSON',
105
- hint: 'Fix the JSON syntax, then add: "teammateMode": "tmux"',
106
- };
107
- }
108
- if (config.teammateMode === 'tmux') {
109
- return {
110
- id: 'teammate-mode',
111
- ok: true,
112
- label: 'teammateMode',
113
- detail: 'tmux',
114
- };
115
- }
116
- return {
117
- id: 'teammate-mode',
118
- ok: false,
119
- label: 'teammateMode',
120
- detail: config.teammateMode
121
- ? `set to "${config.teammateMode}" (expected "tmux")`
122
- : 'not set in ~/.claude.json',
123
- hint: 'Add "teammateMode": "tmux" to ~/.claude.json',
124
- };
125
- }
126
-
127
- function diagnose(opts = {}) {
128
- const homedir = opts.homedir || os.homedir();
129
- const exec = opts.exec || tryExec;
130
-
131
- const checks = [
132
- checkNode(),
133
- checkClaude(exec),
134
- checkTmux(exec),
135
- checkTeammateMode(homedir),
136
- ];
137
-
138
- return {
139
- checks,
140
- allOk: checks.every(c => c.ok),
141
- };
142
- }
143
-
144
- function formatChecks(checks) {
145
- const lines = [];
146
- const maxLabel = Math.max(...checks.map(c => c.label.length));
147
-
148
- for (const check of checks) {
149
- const icon = check.ok ? '✔' : '✖';
150
- const pad = ' '.repeat(maxLabel - check.label.length + 2);
151
- lines.push(` ${icon} ${check.label}${pad}${check.detail}`);
152
- if (check.hint) {
153
- lines.push(` → ${check.hint}`);
154
- }
155
- }
156
- return lines.join('\n');
157
- }
158
-
159
- function run() {
160
- const result = diagnose();
161
-
162
- const output = `
163
- claude-raid doctor — Environment & Quick Start
164
-
165
- ─── Prerequisites ───────────────────────────────
166
-
167
- ${formatChecks(result.checks)}
168
-
169
- ─── Quick Start ─────────────────────────────────
170
-
171
- In-process mode (any terminal):
172
-
173
- claude --agent wizard
174
-
175
- Split-pane mode (tmux):
176
-
177
- tmux new-session -s raid
178
- claude --agent wizard --teammate-mode tmux
179
-
180
- ─── Navigating Teammates ────────────────────────
181
-
182
- Shift+Down Cycle through teammates
183
- Enter View a teammate's session
184
- Escape Interrupt a teammate's turn
185
- Ctrl+T Toggle the shared task list
186
- Click pane Interact directly (split-pane mode)
187
-
188
- ─── Raid Modes ──────────────────────────────────
189
-
190
- Full Raid Warrior + Archer + Rogue (3 agents)
191
- Skirmish 2 agents, lightweight
192
- Scout Wizard solo review
193
-
194
- The Wizard recommends a mode based on task
195
- complexity. You confirm before agents spawn.
196
- `;
197
21
 
198
- console.log(output);
22
+ console.log('\n' + referenceCard() + '\n');
199
23
  }
200
24
 
201
25
  module.exports = { diagnose, run };
package/src/init.js CHANGED
@@ -4,6 +4,9 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { detectProject } = require('./detect-project');
6
6
  const { mergeSettings } = require('./merge-settings');
7
+ const { runSetup } = require('./setup');
8
+ const { banner, header, colors } = require('./ui');
9
+ const { AGENTS, HOOKS, SKILLS, CONFIG } = require('./descriptions');
7
10
 
8
11
  const TEMPLATE_DIR = path.join(__dirname, '..', 'template', '.claude');
9
12
 
@@ -58,6 +61,16 @@ function install(cwd) {
58
61
  }
59
62
  }
60
63
 
64
+ // Count copied files by category
65
+ const agentsDir = path.join(claudeDir, 'agents');
66
+ const hooksDir2 = path.join(claudeDir, 'hooks');
67
+ const skillsDir = path.join(claudeDir, 'skills');
68
+ result.counts = {
69
+ agents: fs.existsSync(agentsDir) ? fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).length : 0,
70
+ hooks: fs.existsSync(hooksDir2) ? fs.readdirSync(hooksDir2).filter(f => f.endsWith('.sh')).length : 0,
71
+ skills: fs.existsSync(skillsDir) ? fs.readdirSync(skillsDir).filter(f => !f.startsWith('.')).length : 0,
72
+ };
73
+
61
74
  // Generate raid.json (skip if it already exists to preserve user config)
62
75
  const raidConfigPath = path.join(claudeDir, 'raid.json');
63
76
  if (!fs.existsSync(raidConfigPath)) {
@@ -65,6 +78,10 @@ function install(cwd) {
65
78
  project: {
66
79
  name: detected.name || path.basename(cwd),
67
80
  language: detected.language,
81
+ packageManager: detected.packageManager || undefined,
82
+ runCommand: detected.runCommand || undefined,
83
+ execCommand: detected.execCommand || undefined,
84
+ installCommand: detected.installCommand || undefined,
68
85
  testCommand: detected.testCommand || '',
69
86
  lintCommand: detected.lintCommand || '',
70
87
  buildCommand: detected.buildCommand || '',
@@ -80,8 +97,39 @@ function install(cwd) {
80
97
  },
81
98
  raid: {
82
99
  defaultMode: 'full',
100
+ vault: {
101
+ path: '.claude/vault',
102
+ enabled: true,
103
+ },
104
+ lifecycle: {
105
+ autoSessionManagement: true,
106
+ teammateNudge: true,
107
+ taskValidation: true,
108
+ completionGate: true,
109
+ phaseTransitionConfirm: true,
110
+ compactBackup: true,
111
+ testWindowMinutes: 10,
112
+ },
83
113
  },
84
114
  };
115
+ Object.keys(raidConfig.project).forEach(key => {
116
+ if (raidConfig.project[key] === undefined) {
117
+ delete raidConfig.project[key];
118
+ }
119
+ });
120
+ if (detected.browser) {
121
+ raidConfig.browser = {
122
+ enabled: true,
123
+ framework: detected.browser.framework,
124
+ devCommand: detected.browser.devCommand,
125
+ baseUrl: `http://localhost:${detected.browser.defaultPort}`,
126
+ defaultPort: detected.browser.defaultPort,
127
+ portRange: [detected.browser.defaultPort + 1, detected.browser.defaultPort + 5],
128
+ playwrightConfig: 'playwright.config.ts',
129
+ auth: null,
130
+ startup: null,
131
+ };
132
+ }
85
133
  fs.writeFileSync(raidConfigPath, JSON.stringify(raidConfig, null, 2) + '\n');
86
134
  }
87
135
 
@@ -90,10 +138,20 @@ function install(cwd) {
90
138
 
91
139
  // Add raid-last-test-run to .gitignore
92
140
  const gitignorePath = path.join(cwd, '.gitignore');
93
- const ignoreEntries = ['.claude/raid-last-test-run', '.claude/raid-session', '.claude/raid-dungeon.md', '.claude/raid-dungeon-phase-*'];
141
+ const ignoreEntries = [
142
+ '.claude/raid-last-test-run',
143
+ '.claude/raid-session',
144
+ '.claude/raid-dungeon.md',
145
+ '.claude/raid-dungeon-phase-*',
146
+ '.claude/raid-dungeon-backup.md',
147
+ '.claude/raid-dungeon-phase-*-backup.md',
148
+ '.claude/vault/.draft/',
149
+ '.env.raid',
150
+ ];
94
151
  if (fs.existsSync(gitignorePath)) {
95
152
  let content = fs.readFileSync(gitignorePath, 'utf8');
96
- const toAdd = ignoreEntries.filter(e => !content.includes(e));
153
+ const lines = content.split('\n').map(l => l.trim());
154
+ const toAdd = ignoreEntries.filter(e => !lines.includes(e.trim()));
97
155
  if (toAdd.length > 0) {
98
156
  const sep = content.endsWith('\n') ? '' : '\n';
99
157
  fs.appendFileSync(gitignorePath, sep + toAdd.join('\n') + '\n');
@@ -105,34 +163,151 @@ function install(cwd) {
105
163
  return result;
106
164
  }
107
165
 
108
- function run() {
166
+ async function run() {
109
167
  const cwd = process.cwd();
110
- console.log('\nclaude-raid Installing The Raid\n');
168
+ const { bold, dim } = colors;
169
+
170
+ console.log('\n' + banner());
171
+ console.log(header('Summoning the Party...') + '\n');
111
172
 
112
173
  const result = install(cwd);
113
174
 
114
175
  if (result.alreadyInstalled) {
115
- console.log('The Raid is already installed. Use `claude-raid update` to update.');
116
- console.log('Proceeding with re-install...\n');
176
+ console.log(' The party is already here. Use ' + bold('claude-raid update') + ' to reforge.');
177
+ console.log(' Proceeding with re-summon...\n');
117
178
  }
118
179
 
119
- console.log(`Detected: ${result.detected.language}`);
180
+ // Detection summary
181
+ console.log(' Realm detected: ' + bold(result.detected.language));
120
182
  if (result.detected.testCommand) {
121
- console.log(`Test command: ${result.detected.testCommand}`);
183
+ console.log(' Test command: ' + bold(result.detected.testCommand));
184
+ }
185
+ if (result.detected.lintCommand) {
186
+ console.log(' Lint command: ' + bold(result.detected.lintCommand));
122
187
  }
188
+
189
+ // Agents
190
+ console.log('');
191
+ console.log(' ' + header('Agents') + dim(` ${result.counts.agents} files`));
192
+ console.log(' Copied wizard.md, warrior.md, archer.md, rogue.md');
193
+ console.log(dim(' AI teammates that challenge each other\'s work from'));
194
+ console.log(dim(' competing angles. Start a session with: claude --agent wizard'));
195
+
196
+ // Hooks
197
+ console.log('');
198
+ console.log(' ' + header('Hooks') + dim(` ${result.counts.hooks} files`));
199
+ console.log(' Copied ' + bold(`${HOOKS.lifecycle.length} lifecycle hooks`) + ' + ' + bold(`${HOOKS.gates.length} quality gates`));
200
+ console.log(dim(' Lifecycle hooks manage session state automatically.'));
201
+ console.log(dim(' Quality gates block bad commits, missing tests, and'));
202
+ console.log(dim(' placeholder text \u2014 only active during Raid sessions.'));
203
+
204
+ // Skills
205
+ console.log('');
206
+ console.log(' ' + header('Skills') + dim(` ${result.counts.skills} folders`));
207
+ const skillNames = Object.keys(SKILLS).join(', ');
208
+ console.log(' ' + dim(skillNames));
209
+ console.log(dim(' Phase-specific workflows that guide agent behavior.'));
210
+
211
+ // Config
212
+ console.log('');
213
+ console.log(' ' + header('Config'));
214
+ console.log(' Generated ' + bold('raid.json') + ' ' + dim('Project settings (editable)'));
215
+ console.log(' Copied ' + bold('raid-rules.md') + ' ' + dim('17 team rules (editable)'));
216
+ console.log(' Merged ' + bold('settings.json') + ' ' + dim('Backup at .pre-raid-backup'));
217
+
218
+ // Skipped files
123
219
  if (result.skipped.length > 0) {
124
- console.log(`\nSkipped (existing files):`);
125
- result.skipped.forEach(f => console.log(` - ${path.relative(cwd, f)}`));
220
+ console.log('');
221
+ console.log(' ' + dim('Preserved existing scrolls:'));
222
+ result.skipped.forEach(f => console.log(' ' + dim('\u2192 ' + path.relative(cwd, f))));
126
223
  }
127
224
 
128
- console.log(`
129
- The Raid is installed.
225
+ // Setup wizard
226
+ await runSetup();
227
+
228
+ // Reference card
229
+ const { referenceCard } = require('./ui');
230
+ console.log('\n' + referenceCard() + '\n');
231
+ }
232
+
233
+ function dryRun(cwd) {
234
+ const detected = detectProject(cwd);
235
+ const lines = [];
236
+
237
+ lines.push(header('Dry Run — nothing will be written') + '\n');
238
+
239
+ // Realm
240
+ lines.push(' Realm detected: ' + colors.bold(detected.language));
241
+ if (detected.testCommand) {
242
+ lines.push(' Test command: ' + colors.bold(detected.testCommand));
243
+ }
244
+ if (detected.lintCommand) {
245
+ lines.push(' Lint command: ' + colors.bold(detected.lintCommand));
246
+ }
247
+ lines.push('');
248
+
249
+ // Helper: check if a file already exists
250
+ const claudeDir = path.join(cwd, '.claude');
251
+ function tag(relPath) {
252
+ const full = path.join(claudeDir, relPath);
253
+ return fs.existsSync(full) ? ' ' + colors.dim('(preserved)') : '';
254
+ }
255
+
256
+ // Agents
257
+ lines.push(header('Agents') + '\n');
258
+ for (const [file, desc] of Object.entries(AGENTS)) {
259
+ lines.push(' ' + colors.bold(file.padEnd(14)) + desc + tag('agents/' + file));
260
+ }
261
+ lines.push('');
262
+
263
+ // Hooks — Lifecycle
264
+ lines.push(header('Hooks — Lifecycle') + '\n');
265
+ for (const h of HOOKS.lifecycle) {
266
+ lines.push(' ' + colors.bold(h.name.padEnd(28)) + h.desc + tag('hooks/' + h.name));
267
+ }
268
+ lines.push('');
269
+
270
+ // Hooks — Quality Gates
271
+ lines.push(header('Hooks — Quality Gates') + '\n');
272
+ for (const h of HOOKS.gates) {
273
+ lines.push(' ' + colors.bold(h.name.padEnd(36)) + h.desc + tag('hooks/' + h.name));
274
+ }
275
+ lines.push('');
276
+
277
+ // Skills
278
+ lines.push(header('Skills') + '\n');
279
+ for (const [folder, desc] of Object.entries(SKILLS)) {
280
+ lines.push(' ' + colors.bold(folder.padEnd(28)) + desc + tag('skills/' + folder));
281
+ }
282
+ lines.push('');
283
+
284
+ // Config
285
+ lines.push(header('Config') + '\n');
286
+ for (const [file, desc] of Object.entries(CONFIG)) {
287
+ lines.push(' ' + colors.bold(file.padEnd(20)) + desc + tag(file));
288
+ }
289
+ lines.push('');
290
+
291
+ // .gitignore
292
+ lines.push(header('.gitignore entries') + '\n');
293
+ const ignoreEntries = [
294
+ '.claude/raid-last-test-run',
295
+ '.claude/raid-session',
296
+ '.claude/raid-dungeon.md',
297
+ '.claude/raid-dungeon-phase-*',
298
+ '.claude/raid-dungeon-backup.md',
299
+ '.claude/raid-dungeon-phase-*-backup.md',
300
+ '.claude/vault/.draft/',
301
+ '.env.raid',
302
+ ];
303
+ for (const entry of ignoreEntries) {
304
+ lines.push(' ' + colors.dim(entry));
305
+ }
306
+ lines.push('');
130
307
 
131
- Configuration: .claude/raid.json (edit to customize)
132
- Team rules: .claude/raid-rules.md (editable)
308
+ lines.push(' Run without --dry-run to install.');
133
309
 
134
- Run 'claude-raid doctor' for setup guide and environment check.
135
- `);
310
+ return lines.join('\n');
136
311
  }
137
312
 
138
- module.exports = { install, run };
313
+ module.exports = { install, run, dryRun };
@@ -18,6 +18,13 @@ const RAID_HOOKS = {
18
18
  hooks: [
19
19
  { type: 'command', command: `bash .claude/hooks/validate-file-naming.sh ${RAID_HOOK_MARKER}` },
20
20
  { type: 'command', command: `bash .claude/hooks/validate-no-placeholders.sh ${RAID_HOOK_MARKER}` },
21
+ { type: 'command', command: `bash .claude/hooks/validate-dungeon.sh ${RAID_HOOK_MARKER}` },
22
+ ],
23
+ },
24
+ {
25
+ matcher: 'Bash',
26
+ hooks: [
27
+ { type: 'command', command: `bash .claude/hooks/validate-browser-cleanup.sh ${RAID_HOOK_MARKER}` },
21
28
  ],
22
29
  },
23
30
  ],
@@ -25,15 +32,64 @@ const RAID_HOOKS = {
25
32
  {
26
33
  matcher: 'Bash',
27
34
  hooks: [
28
- { type: 'command', command: `bash .claude/hooks/validate-commit-message.sh ${RAID_HOOK_MARKER}` },
29
- { type: 'command', command: `bash .claude/hooks/validate-tests-pass.sh ${RAID_HOOK_MARKER}` },
30
- { type: 'command', command: `bash .claude/hooks/validate-verification.sh ${RAID_HOOK_MARKER}` },
35
+ { type: 'command', command: `bash .claude/hooks/validate-commit.sh ${RAID_HOOK_MARKER}` },
36
+ { type: 'command', command: `bash .claude/hooks/validate-browser-tests-exist.sh ${RAID_HOOK_MARKER}` },
31
37
  ],
32
38
  },
33
39
  {
34
- matcher: 'Write',
40
+ matcher: 'Write|Edit',
41
+ hooks: [
42
+ { type: 'command', command: `bash .claude/hooks/validate-write-gate.sh ${RAID_HOOK_MARKER}` },
43
+ ],
44
+ },
45
+ ],
46
+ SessionStart: [
47
+ {
48
+ hooks: [
49
+ { type: 'command', command: `bash .claude/hooks/raid-session-start.sh ${RAID_HOOK_MARKER}` },
50
+ ],
51
+ },
52
+ ],
53
+ SessionEnd: [
54
+ {
55
+ matcher: 'prompt_input_exit|clear',
56
+ hooks: [
57
+ { type: 'command', command: `bash .claude/hooks/raid-session-end.sh ${RAID_HOOK_MARKER}` },
58
+ ],
59
+ },
60
+ ],
61
+ TeammateIdle: [
62
+ {
63
+ hooks: [
64
+ { type: 'command', command: `bash .claude/hooks/raid-teammate-idle.sh ${RAID_HOOK_MARKER}` },
65
+ ],
66
+ },
67
+ ],
68
+ TaskCreated: [
69
+ {
70
+ hooks: [
71
+ { type: 'command', command: `bash .claude/hooks/raid-task-created.sh ${RAID_HOOK_MARKER}` },
72
+ ],
73
+ },
74
+ ],
75
+ TaskCompleted: [
76
+ {
77
+ hooks: [
78
+ { type: 'command', command: `bash .claude/hooks/raid-task-completed.sh ${RAID_HOOK_MARKER}` },
79
+ ],
80
+ },
81
+ ],
82
+ Stop: [
83
+ {
84
+ hooks: [
85
+ { type: 'command', command: `bash .claude/hooks/raid-stop.sh ${RAID_HOOK_MARKER}` },
86
+ ],
87
+ },
88
+ ],
89
+ PreCompact: [
90
+ {
35
91
  hooks: [
36
- { type: 'command', command: `bash .claude/hooks/validate-phase-gate.sh ${RAID_HOOK_MARKER}` },
92
+ { type: 'command', command: `bash .claude/hooks/raid-pre-compact.sh ${RAID_HOOK_MARKER}` },
37
93
  ],
38
94
  },
39
95
  ],
@@ -70,7 +126,7 @@ function mergeSettings(cwd) {
70
126
  if (!existing.hooks) existing.hooks = {};
71
127
 
72
128
  for (const [event, raidEntries] of Object.entries(RAID_HOOKS)) {
73
- if (!existing.hooks[event]) {
129
+ if (!Array.isArray(existing.hooks[event])) {
74
130
  existing.hooks[event] = [];
75
131
  }
76
132
  existing.hooks[event] = existing.hooks[event].filter(entry => !isRaidHookEntry(entry));
@@ -97,7 +153,7 @@ function removeRaidSettings(cwd) {
97
153
  settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
98
154
  } catch {
99
155
  throw new Error(
100
- 'Your .claude/settings.json contains invalid JSON. Please fix it before running claude-raid remove.'
156
+ 'Your .claude/settings.json contains invalid JSON. Please fix it before running claude-raid dismantle.'
101
157
  );
102
158
  }
103
159
 
package/src/remove.js CHANGED
@@ -3,12 +3,14 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { removeRaidSettings } = require('./merge-settings');
6
+ const { banner, header, colors } = require('./ui');
6
7
 
7
8
  const RAID_AGENTS = ['wizard.md', 'warrior.md', 'archer.md', 'rogue.md'];
8
9
  const RAID_SKILLS = [
9
10
  'raid-protocol', 'raid-design', 'raid-implementation-plan', 'raid-implementation',
10
11
  'raid-review', 'raid-finishing', 'raid-tdd', 'raid-debugging',
11
12
  'raid-verification', 'raid-git-worktrees',
13
+ 'raid-browser', 'raid-browser-playwright', 'raid-browser-chrome',
12
14
  ];
13
15
 
14
16
  function rmSafe(filePath) {
@@ -35,7 +37,9 @@ function performRemove(cwd) {
35
37
 
36
38
  const hooksDir = path.join(claudeDir, 'hooks');
37
39
  if (fs.existsSync(hooksDir)) {
38
- const hooks = fs.readdirSync(hooksDir).filter(f => f.startsWith('validate-') && f.endsWith('.sh'));
40
+ const hooks = fs.readdirSync(hooksDir).filter(f =>
41
+ (f.startsWith('validate-') || f.startsWith('raid-')) && f.endsWith('.sh')
42
+ );
39
43
  for (const hook of hooks) {
40
44
  rmSafe(path.join(hooksDir, hook));
41
45
  }
@@ -64,11 +68,29 @@ function performRemove(cwd) {
64
68
  }
65
69
  }
66
70
 
71
+ // Clean up Dungeon backups
72
+ rmSafe(path.join(claudeDir, 'raid-dungeon-backup.md'));
73
+ if (fs.existsSync(claudeDir)) {
74
+ const backupFiles = fs.readdirSync(claudeDir).filter(f => f.startsWith('raid-dungeon-phase-') && f.endsWith('-backup.md'));
75
+ for (const file of backupFiles) {
76
+ rmSafe(path.join(claudeDir, file));
77
+ }
78
+ }
79
+
80
+ // Clean up Vault draft
81
+ rmDirSafe(path.join(claudeDir, 'vault', '.draft'));
82
+
67
83
  removeRaidSettings(cwd);
68
84
 
69
85
  // Clean .gitignore entries
70
86
  const gitignorePath = path.join(cwd, '.gitignore');
71
- const raidIgnoreEntries = ['.claude/raid-last-test-run', '.claude/raid-session', '.claude/raid-dungeon.md', '.claude/raid-dungeon-phase-*'];
87
+ const raidIgnoreEntries = [
88
+ '.claude/raid-last-test-run', '.claude/raid-session',
89
+ '.claude/raid-dungeon.md', '.claude/raid-dungeon-phase-*',
90
+ '.claude/raid-dungeon-backup.md', '.claude/raid-dungeon-phase-*-backup.md',
91
+ '.claude/vault/.draft/',
92
+ '.env.raid',
93
+ ];
72
94
  if (fs.existsSync(gitignorePath)) {
73
95
  const lines = fs.readFileSync(gitignorePath, 'utf8').split('\n');
74
96
  const filtered = lines.filter(line => !raidIgnoreEntries.includes(line.trim()));
@@ -84,9 +106,11 @@ function performRemove(cwd) {
84
106
 
85
107
  function run() {
86
108
  const cwd = process.cwd();
87
- console.log('\nclaude-raid Removing The Raid\n');
109
+ console.log('\n' + banner());
110
+ console.log(header('Dismantling the Camp...') + '\n');
88
111
  performRemove(cwd);
89
- console.log('The Raid has been removed. Your project settings have been restored.');
112
+ console.log(' ' + colors.green('✔') + ' The camp has been dismantled.');
113
+ console.log(' ' + colors.dim('Your realm has been restored to its former state.') + '\n');
90
114
  }
91
115
 
92
116
  module.exports = { performRemove, run };