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/setup.js ADDED
@@ -0,0 +1,405 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+ const { execSync } = require('child_process');
8
+ const { colors, box, header } = require('./ui');
9
+
10
+ // --- Helpers (private) ---
11
+
12
+ function tryExec(cmd) {
13
+ try {
14
+ return execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function parseVersion(str) {
21
+ const match = str && str.match(/(\d+)\.(\d+)\.(\d+)/);
22
+ if (!match) return null;
23
+ return { major: +match[1], minor: +match[2], patch: +match[3] };
24
+ }
25
+
26
+ function versionGte(v, min) {
27
+ if (v.major !== min.major) return v.major > min.major;
28
+ if (v.minor !== min.minor) return v.minor > min.minor;
29
+ return v.patch >= min.patch;
30
+ }
31
+
32
+ // --- Constants ---
33
+
34
+ const MIN_NODE = { major: 18, minor: 0, patch: 0 };
35
+ const MIN_CLAUDE = { major: 2, minor: 1, patch: 32 };
36
+ const VALID_TEAMMATE_MODES = ['tmux', 'in-process', 'auto'];
37
+ const REQUIRED_IDS = ['node', 'claude', 'jq'];
38
+
39
+ // --- Check functions (private) ---
40
+
41
+ function checkNode(nodeVersion) {
42
+ const v = nodeVersion || process.version;
43
+ const ver = parseVersion(v);
44
+ if (!ver) {
45
+ return {
46
+ id: 'node',
47
+ ok: false,
48
+ label: 'Node.js',
49
+ detail: `unknown version: ${v}`,
50
+ hint: 'Node.js >= 18 is required',
51
+ };
52
+ }
53
+ const ok = versionGte(ver, MIN_NODE);
54
+ const tag = `v${ver.major}.${ver.minor}.${ver.patch}`;
55
+ return {
56
+ id: 'node',
57
+ ok,
58
+ label: 'Node.js',
59
+ detail: ok
60
+ ? `${tag} (>= ${MIN_NODE.major} required)`
61
+ : `${tag} — upgrade required (>= ${MIN_NODE.major})`,
62
+ hint: ok ? undefined : 'Upgrade Node.js to version 18 or later',
63
+ };
64
+ }
65
+
66
+ function checkClaude(exec) {
67
+ const raw = exec('claude --version');
68
+ if (!raw) {
69
+ return {
70
+ id: 'claude',
71
+ ok: false,
72
+ label: 'Claude Code',
73
+ detail: 'not found',
74
+ hint: 'Install: npm install -g @anthropic-ai/claude-code',
75
+ };
76
+ }
77
+ const ver = parseVersion(raw);
78
+ if (!ver) {
79
+ return {
80
+ id: 'claude',
81
+ ok: false,
82
+ label: 'Claude Code',
83
+ detail: `unknown version: ${raw}`,
84
+ hint: 'Expected semver from "claude --version"',
85
+ };
86
+ }
87
+ const ok = versionGte(ver, MIN_CLAUDE);
88
+ const tag = `v${ver.major}.${ver.minor}.${ver.patch}`;
89
+ return {
90
+ id: 'claude',
91
+ ok,
92
+ label: 'Claude Code',
93
+ detail: ok
94
+ ? `${tag} (≥ ${MIN_CLAUDE.major}.${MIN_CLAUDE.minor}.${MIN_CLAUDE.patch} required)`
95
+ : `${tag} — update required (≥ ${MIN_CLAUDE.major}.${MIN_CLAUDE.minor}.${MIN_CLAUDE.patch})`,
96
+ hint: ok ? undefined : 'Update: npm update -g @anthropic-ai/claude-code',
97
+ };
98
+ }
99
+
100
+ function checkTeammateMode(homedir) {
101
+ const configPath = path.join(homedir, '.claude.json');
102
+ if (!fs.existsSync(configPath)) {
103
+ return {
104
+ id: 'teammate-mode',
105
+ ok: false,
106
+ label: 'teammateMode',
107
+ detail: 'not set — ~/.claude.json not found',
108
+ hint: 'Create ~/.claude.json with: { "teammateMode": "tmux" }',
109
+ fixable: true,
110
+ };
111
+ }
112
+ let config;
113
+ try {
114
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
115
+ } catch {
116
+ return {
117
+ id: 'teammate-mode',
118
+ ok: false,
119
+ label: 'teammateMode',
120
+ detail: '~/.claude.json is not valid JSON',
121
+ hint: 'Fix the JSON syntax, then add: "teammateMode": "tmux"',
122
+ fixable: false,
123
+ };
124
+ }
125
+ if (VALID_TEAMMATE_MODES.includes(config.teammateMode)) {
126
+ return {
127
+ id: 'teammate-mode',
128
+ ok: true,
129
+ label: 'teammateMode',
130
+ detail: config.teammateMode,
131
+ };
132
+ }
133
+ return {
134
+ id: 'teammate-mode',
135
+ ok: false,
136
+ label: 'teammateMode',
137
+ detail: config.teammateMode
138
+ ? `set to "${config.teammateMode}" (expected one of: ${VALID_TEAMMATE_MODES.join(', ')})`
139
+ : 'not set in ~/.claude.json',
140
+ hint: `Add "teammateMode": "tmux" to ~/.claude.json`,
141
+ fixable: true,
142
+ };
143
+ }
144
+
145
+ function checkPlaywright(exec, cwd) {
146
+ // Try to read raid.json
147
+ let raidConfig = null;
148
+ if (cwd) {
149
+ const raidJsonPath = path.join(cwd, '.claude', 'raid.json');
150
+ try {
151
+ raidConfig = JSON.parse(fs.readFileSync(raidJsonPath, 'utf8'));
152
+ } catch {
153
+ // No raid.json or invalid — treat as browser not enabled
154
+ }
155
+ }
156
+
157
+ const browserEnabled = raidConfig && raidConfig.browser && raidConfig.browser.enabled;
158
+
159
+ if (!browserEnabled) {
160
+ return {
161
+ id: 'playwright',
162
+ ok: true,
163
+ label: 'Playwright',
164
+ detail: 'not needed (browser testing disabled)',
165
+ };
166
+ }
167
+
168
+ // Determine exec command prefix (e.g. "npx" or "pnpm dlx")
169
+ const execCommand = (raidConfig.project && raidConfig.project.execCommand) || 'npx';
170
+
171
+ // Check if playwright config file exists
172
+ const configFile = (raidConfig.browser && raidConfig.browser.playwrightConfig) || 'playwright.config.ts';
173
+
174
+ // Check playwright version
175
+ const raw = exec(`${execCommand} playwright --version`);
176
+
177
+ if (!raw) {
178
+ return {
179
+ id: 'playwright',
180
+ ok: false,
181
+ label: 'Playwright',
182
+ detail: 'not installed',
183
+ hint: `Install: ${execCommand} playwright install`,
184
+ };
185
+ }
186
+
187
+ const ver = parseVersion(raw);
188
+ const tag = ver ? `v${ver.major}.${ver.minor}.${ver.patch}` : raw.trim();
189
+
190
+ return {
191
+ id: 'playwright',
192
+ ok: true,
193
+ label: 'Playwright',
194
+ detail: `installed (${tag})`,
195
+ hint: `Install: ${execCommand} playwright install`,
196
+ };
197
+ }
198
+
199
+ function checkJq(exec) {
200
+ const found = exec('command -v jq');
201
+ if (found) {
202
+ return {
203
+ id: 'jq',
204
+ ok: true,
205
+ label: 'jq',
206
+ detail: 'installed',
207
+ };
208
+ }
209
+ return {
210
+ id: 'jq',
211
+ ok: false,
212
+ label: 'jq',
213
+ detail: 'not found',
214
+ hint: process.platform === 'darwin'
215
+ ? 'Install: brew install jq'
216
+ : 'Install jq via your package manager (apt, dnf, brew, etc.)',
217
+ };
218
+ }
219
+
220
+ function checkPlatform(platform) {
221
+ if (platform === 'win32') {
222
+ return {
223
+ id: 'platform',
224
+ ok: false,
225
+ label: 'Platform',
226
+ detail: 'Windows is not supported — hooks require POSIX bash',
227
+ hint: 'Use WSL2 (Windows Subsystem for Linux) for full compatibility',
228
+ };
229
+ }
230
+ return {
231
+ id: 'platform',
232
+ ok: true,
233
+ label: 'Platform',
234
+ detail: platform,
235
+ };
236
+ }
237
+
238
+ function checkSplitPane(exec) {
239
+ const tmux = exec('command -v tmux');
240
+ const it2 = exec('command -v it2');
241
+
242
+ if (tmux || it2) {
243
+ const available = [tmux && 'tmux', it2 && 'it2'].filter(Boolean).join(', ');
244
+ return {
245
+ id: 'split-pane',
246
+ ok: true,
247
+ label: 'Split-pane',
248
+ detail: `available: ${available}`,
249
+ };
250
+ }
251
+ return {
252
+ id: 'split-pane',
253
+ ok: false,
254
+ label: 'Split-pane',
255
+ detail: 'no split-pane tool found',
256
+ hint: process.platform === 'darwin'
257
+ ? 'Install tmux: brew install tmux'
258
+ : 'Install tmux via your package manager (apt, dnf, brew, etc.)',
259
+ };
260
+ }
261
+
262
+ // --- Interactive helpers (private) ---
263
+
264
+ const MODE_MENU = [
265
+ { key: '1', value: 'tmux', desc: 'split panes, see all agents at once (requires tmux/iTerm2)' },
266
+ { key: '2', value: 'in-process', desc: 'all in one terminal, cycle with Shift+Down' },
267
+ { key: '3', value: 'auto', desc: 'split panes if available, otherwise in-process' },
268
+ ];
269
+
270
+ function ask(question, stdin, stdout) {
271
+ return new Promise((resolve) => {
272
+ const rl = readline.createInterface({ input: stdin, output: stdout, terminal: false });
273
+ rl.question(question, (answer) => {
274
+ rl.close();
275
+ resolve((answer || '').trim());
276
+ });
277
+ });
278
+ }
279
+
280
+ function writeTeammateMode(homedir, mode) {
281
+ const configPath = path.join(homedir, '.claude.json');
282
+ let config = {};
283
+ if (fs.existsSync(configPath)) {
284
+ try {
285
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
286
+ } catch {
287
+ config = {};
288
+ }
289
+ }
290
+ config.teammateMode = mode;
291
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
292
+ }
293
+
294
+ // --- Formatting helper (private) ---
295
+
296
+ function formatCheckLine(check) {
297
+ const icon = check.ok ? colors.green('✔') : colors.red('✖');
298
+ const lines = [` ${icon} ${check.label} ${check.detail}`];
299
+ if (check.hint) {
300
+ lines.push(` ${colors.dim('→')} ${colors.dim(check.hint)}`);
301
+ }
302
+ return lines;
303
+ }
304
+
305
+ // --- Exports ---
306
+
307
+ function runChecks(opts = {}) {
308
+ const homedir = opts.homedir || os.homedir();
309
+ const exec = opts.exec || tryExec;
310
+ const cwd = opts.cwd || undefined;
311
+
312
+ const nodeVersion = opts.nodeVersion || undefined;
313
+
314
+ const platform = opts.platform || process.platform;
315
+
316
+ const checks = [
317
+ checkPlatform(platform),
318
+ checkNode(nodeVersion),
319
+ checkClaude(exec),
320
+ checkJq(exec),
321
+ checkTeammateMode(homedir),
322
+ checkSplitPane(exec),
323
+ checkPlaywright(exec, cwd),
324
+ ];
325
+
326
+ return {
327
+ checks,
328
+ allOk: checks.filter(c => REQUIRED_IDS.includes(c.id)).every(c => c.ok),
329
+ };
330
+ }
331
+
332
+ async function runSetup(opts = {}) {
333
+ const homedir = opts.homedir || os.homedir();
334
+ const exec = opts.exec || tryExec;
335
+ const stdin = opts.stdin || process.stdin;
336
+ const stdout = opts.stdout || process.stdout;
337
+ const actions = [];
338
+
339
+ let { checks, allOk } = runChecks({ homedir, exec });
340
+
341
+ const isInteractive = !!stdin.isTTY;
342
+
343
+ // Non-interactive: print all checks in a box and return
344
+ if (!isInteractive) {
345
+ const allLines = checks.flatMap(c => formatCheckLine(c));
346
+ stdout.write('\n' + box('Party Status', allLines) + '\n');
347
+ return { checks, allOk, actions: [] };
348
+ }
349
+
350
+ // Interactive: print initial checks (not split-pane yet)
351
+ const initialChecks = checks.filter(c => c.id !== 'split-pane');
352
+ const initialLines = initialChecks.flatMap(c => formatCheckLine(c));
353
+ stdout.write('\n' + box('Party Status', initialLines) + '\n');
354
+
355
+ // Handle teammate-mode fix
356
+ const tmCheck = checks.find(c => c.id === 'teammate-mode');
357
+ let selectedMode = null;
358
+
359
+ if (!tmCheck.ok && tmCheck.fixable) {
360
+ stdout.write('\n Choose your formation:\n\n');
361
+ for (const item of MODE_MENU) {
362
+ stdout.write(` ${colors.amber(item.key + ')')} ${colors.bold(item.value.padEnd(12))} ${colors.dim(item.desc)}\n`);
363
+ }
364
+ const choice = await ask('\n Pick [1/2/3]: ', stdin, stdout);
365
+ const picked = MODE_MENU.find(m => m.key === choice);
366
+ if (picked) {
367
+ const confirm = await ask(` Write teammateMode: "${colors.bold(picked.value)}" to ~/.claude.json? [Y/n] `, stdin, stdout);
368
+ if (confirm.toLowerCase() !== 'n') {
369
+ writeTeammateMode(homedir, picked.value);
370
+ tmCheck.ok = true;
371
+ tmCheck.detail = picked.value;
372
+ delete tmCheck.hint;
373
+ delete tmCheck.fixable;
374
+ actions.push('teammate-mode');
375
+ selectedMode = picked.value;
376
+ stdout.write(' ' + colors.green('✔') + ' Updated ~/.claude.json\n');
377
+ }
378
+ }
379
+ } else if (tmCheck.ok) {
380
+ selectedMode = tmCheck.detail;
381
+ }
382
+
383
+ // Handle split-pane check
384
+ const splitPane = checks.find(c => c.id === 'split-pane');
385
+ if (selectedMode === 'in-process') {
386
+ splitPane.ok = true;
387
+ splitPane.detail = 'not needed (in-process mode)';
388
+ delete splitPane.hint;
389
+ }
390
+
391
+ stdout.write('\n ' + formatCheckLine(splitPane).join('\n ') + '\n');
392
+
393
+ // Recalculate allOk (required checks only: node + claude)
394
+
395
+ allOk = checks.filter(c => REQUIRED_IDS.includes(c.id)).every(c => c.ok);
396
+
397
+ if (checks.every(c => c.ok)) {
398
+ stdout.write('\n ' + colors.green('The party is assembled.') + ' Your quest awaits.\n');
399
+ stdout.write('\n claude --agent wizard\n');
400
+ }
401
+
402
+ return { checks, allOk, actions };
403
+ }
404
+
405
+ module.exports = { runChecks, VALID_TEAMMATE_MODES, runSetup };
package/src/ui.js ADDED
@@ -0,0 +1,168 @@
1
+ 'use strict';
2
+
3
+ // Color support detection (evaluated once at module load)
4
+ const noColor = ('NO_COLOR' in process.env) || !process.stdout.isTTY;
5
+
6
+ function wrap(code) {
7
+ if (noColor) return (str) => str;
8
+ return (str) => `\x1b[${code}m${str}\x1b[0m`;
9
+ }
10
+
11
+ const colors = {
12
+ amber: wrap('33'),
13
+ green: wrap('32'),
14
+ red: wrap('31'),
15
+ dim: wrap('90'),
16
+ bold: wrap('1'),
17
+ boldAmber: wrap('1;33'),
18
+ boldRed: wrap('1;31'),
19
+ dimRed: wrap('2;31'),
20
+ };
21
+
22
+ function stripAnsi(str) {
23
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
24
+ }
25
+
26
+ function banner() {
27
+ const { amber, boldAmber, boldRed, red, dimRed, dim } = colors;
28
+ const rule = amber(' ⚔ ═══════════════════════════════════════════════════════ ⚔');
29
+
30
+ const claudeArt = [
31
+ ' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗',
32
+ ' ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝',
33
+ ' ██║ ██║ ███████║██║ ██║██║ ██║█████╗ ',
34
+ ' ██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ',
35
+ ' ╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗',
36
+ ' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝',
37
+ ];
38
+
39
+ const raidArt = [
40
+ ' ██████╗ █████╗ ██╗██████╗ ',
41
+ ' ██╔══██╗██╔══██╗██║██╔══██╗',
42
+ ' ██████╔╝███████║██║██║ ██║',
43
+ ' ██╔══██╗██╔══██║██║██║ ██║',
44
+ ' ██║ ██║██║ ██║██║██████╔╝',
45
+ ' ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═════╝ ',
46
+ ];
47
+
48
+ // 5-tone vertical gradient: boldAmber -> amber -> boldRed -> red -> dimRed
49
+ // Lines 1-2 (CLAUDE top): boldAmber
50
+ // Lines 3-5 (CLAUDE bottom + transition): amber
51
+ // Line 6 (CLAUDE/RAID boundary): boldRed
52
+ // Lines 7-8 (RAID top): boldRed
53
+ // Lines 9-11 (RAID middle): red
54
+ // Line 12 (RAID bottom): dimRed
55
+ const gradientColors = [
56
+ boldAmber, boldAmber, // claudeArt[0-1]
57
+ amber, amber, amber, // claudeArt[2-4]
58
+ boldRed, // claudeArt[5]
59
+ boldRed, boldRed, // raidArt[0-1]
60
+ red, red, red, // raidArt[2-4]
61
+ dimRed, // raidArt[5]
62
+ ];
63
+
64
+ const allArt = [...claudeArt, ...raidArt];
65
+ const tagline = ' Adversarial multi-agent development for Claude Code';
66
+
67
+ const lines = [
68
+ '',
69
+ rule,
70
+ '',
71
+ ...allArt.map((l, i) => gradientColors[i](l)),
72
+ '',
73
+ dim(tagline),
74
+ '',
75
+ rule,
76
+ '',
77
+ ];
78
+
79
+ return lines.join('\n');
80
+ }
81
+
82
+ function box(title, contentLines) {
83
+ const { amber } = colors;
84
+ const PADDING = 2; // left padding inside box
85
+ const titleStr = ` \u2694 ${title} `;
86
+
87
+ // Calculate width: fit widest content + padding on both sides, or title
88
+ let maxContent = 0;
89
+ for (const line of contentLines) {
90
+ const w = stripAnsi(line).length;
91
+ if (w > maxContent) maxContent = w;
92
+ }
93
+ const innerWidth = Math.max(maxContent + PADDING * 2, titleStr.length + 4);
94
+ // Top border: ┌─── ⚔ Title ───...─┐
95
+ const titleDashesAfter = innerWidth - titleStr.length - 3;
96
+ const topBorder = amber('┌') + amber('───') + titleStr + amber('─'.repeat(Math.max(0, titleDashesAfter))) + amber('┐');
97
+
98
+ // Bottom border
99
+ const botBorder = amber('└') + amber('─'.repeat(innerWidth)) + amber('┘');
100
+
101
+ // Empty line
102
+ const emptyLine = amber('│') + ' '.repeat(innerWidth) + amber('│');
103
+
104
+ const lines = [topBorder, emptyLine];
105
+ for (const content of contentLines) {
106
+ const visLen = stripAnsi(content).length;
107
+ const rightPad = innerWidth - PADDING - visLen;
108
+ lines.push(amber('│') + ' '.repeat(PADDING) + content + ' '.repeat(Math.max(0, rightPad)) + amber('│'));
109
+ }
110
+ lines.push(emptyLine, botBorder);
111
+
112
+ return lines.join('\n');
113
+ }
114
+
115
+ function header(text) {
116
+ const { amber, bold } = colors;
117
+ return ` ${amber(bold(`\u2694 ${text}`))}`;
118
+ }
119
+
120
+ function referenceCard() {
121
+ const howItWorks = box('How It Works', [
122
+ ' You describe a task. The Wizard assesses complexity and',
123
+ ' recommends a mode:',
124
+ '',
125
+ ' ' + colors.bold('Full Raid') + ' 3 agents attack from competing angles',
126
+ ' ' + colors.bold('Skirmish') + ' 2 agents, lighter process',
127
+ ' ' + colors.bold('Scout') + ' 1 agent + Wizard review',
128
+ '',
129
+ ' Every task flows through 4 phases:',
130
+ '',
131
+ ' 1. ' + colors.bold('Design') + ' Agents explore and challenge the approach',
132
+ ' 2. ' + colors.bold('Plan') + ' Agents decompose into testable tasks',
133
+ ' 3. ' + colors.bold('Implement') + ' One builds (TDD), others attack',
134
+ ' 4. ' + colors.bold('Review') + ' Independent reviews, fight over findings',
135
+ '',
136
+ ' Hooks enforce discipline automatically:',
137
+ ' ' + colors.dim('\u2022') + ' No implementation without a design doc',
138
+ ' ' + colors.dim('\u2022') + ' No commits without passing tests',
139
+ ' ' + colors.dim('\u2022') + ' No completion claims without fresh test evidence',
140
+ ' ' + colors.dim('\u2022') + ' Conventional commit messages required',
141
+ '',
142
+ ' ' + colors.dim('Hooks only activate during Raid sessions \u2014 they won\'t'),
143
+ ' ' + colors.dim('interfere with normal coding outside of a Raid.'),
144
+ '',
145
+ ' Config: ' + colors.bold('.claude/raid.json') + ' ' + colors.dim('project settings'),
146
+ ' Rules: ' + colors.bold('.claude/raid-rules.md') + ' ' + colors.dim('editable team rules'),
147
+ ]);
148
+
149
+ const nextStep = box('Next Step', [
150
+ ' ' + colors.bold('claude --agent wizard'),
151
+ '',
152
+ ' Describe your task and the Wizard takes over.',
153
+ ' ' + colors.dim('Tip: start with a small task (bugfix, config change) to'),
154
+ ' ' + colors.dim('see the workflow before tackling something complex.'),
155
+ '',
156
+ ' ' + colors.bold('Controls'),
157
+ ' ' + colors.bold('Shift+Down') + ' Cycle through teammates',
158
+ ' ' + colors.bold('Enter') + ' View a teammate\'s session',
159
+ ' ' + colors.bold('Escape') + ' Interrupt a teammate\'s turn',
160
+ ' ' + colors.bold('Ctrl+T') + ' Toggle the shared task list',
161
+ '',
162
+ ' Review this anytime: ' + colors.bold('claude-raid heal'),
163
+ ]);
164
+
165
+ return howItWorks + '\n' + nextStep;
166
+ }
167
+
168
+ module.exports = { colors, banner, box, header, stripAnsi, referenceCard };
package/src/update.js CHANGED
@@ -3,6 +3,8 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { mergeSettings } = require('./merge-settings');
6
+ const { detectProject } = require('./detect-project');
7
+ const { banner, header, colors } = require('./ui');
6
8
 
7
9
  const TEMPLATE_DIR = path.join(__dirname, '..', 'template', '.claude');
8
10
 
@@ -34,7 +36,7 @@ function performUpdate(cwd) {
34
36
  const skippedAgents = [];
35
37
 
36
38
  if (!fs.existsSync(path.join(claudeDir, 'raid-rules.md'))) {
37
- return { success: false, message: 'The Raid is not installed. Run `claude-raid init` first.', skippedAgents };
39
+ return { success: false, message: 'No party found. Run `claude-raid summon` first.', skippedAgents };
38
40
  }
39
41
 
40
42
  // Update agents — skip if user has customized them
@@ -84,9 +86,53 @@ function performUpdate(cwd) {
84
86
  }
85
87
  }
86
88
 
89
+ // Migrate existing raid.json — add missing browser/packageManager fields
90
+ const raidConfigPath = path.join(claudeDir, 'raid.json');
91
+ const migratedFields = [];
92
+ if (fs.existsSync(raidConfigPath)) {
93
+ let config;
94
+ try {
95
+ config = JSON.parse(fs.readFileSync(raidConfigPath, 'utf8'));
96
+ } catch {
97
+ config = null;
98
+ }
99
+ if (config !== null) {
100
+ const detected = detectProject(cwd);
101
+ // Add packageManager fields if missing
102
+ if (detected.packageManager && config.project && !config.project.packageManager) {
103
+ config.project.packageManager = detected.packageManager;
104
+ if (detected.runCommand) config.project.runCommand = detected.runCommand;
105
+ if (detected.execCommand) config.project.execCommand = detected.execCommand;
106
+ if (detected.installCommand) config.project.installCommand = detected.installCommand;
107
+ migratedFields.push('packageManager');
108
+ }
109
+ // Add browser section if detected and missing
110
+ if (detected.browser && !config.browser) {
111
+ config.browser = {
112
+ enabled: true,
113
+ framework: detected.browser.framework,
114
+ devCommand: detected.browser.devCommand,
115
+ baseUrl: `http://localhost:${detected.browser.defaultPort}`,
116
+ defaultPort: detected.browser.defaultPort,
117
+ portRange: [detected.browser.defaultPort + 1, detected.browser.defaultPort + 5],
118
+ playwrightConfig: 'playwright.config.ts',
119
+ auth: null,
120
+ startup: null,
121
+ };
122
+ migratedFields.push('browser');
123
+ }
124
+ if (migratedFields.length > 0) {
125
+ fs.writeFileSync(raidConfigPath, JSON.stringify(config, null, 2) + '\n');
126
+ }
127
+ }
128
+ }
129
+
87
130
  mergeSettings(cwd);
88
131
 
89
132
  let message = 'The Raid has been updated to the latest version.';
133
+ if (migratedFields.length > 0) {
134
+ message += `\nMigrated raid.json: added ${migratedFields.join(', ')}`;
135
+ }
90
136
  if (skippedAgents.length > 0) {
91
137
  message += `\nSkipped customized agents: ${skippedAgents.join(', ')}`;
92
138
  }
@@ -94,17 +140,28 @@ function performUpdate(cwd) {
94
140
  message += '\nSkipped customized raid-rules.md';
95
141
  }
96
142
  if (skippedAgents.length > 0 || skippedRules) {
97
- message += '\nUse `claude-raid remove` then `claude-raid init` to reset.';
143
+ message += '\nUse `claude-raid dismantle` then `claude-raid summon` to reset.';
98
144
  }
99
145
 
100
- return { success: true, message, skippedAgents };
146
+ return { success: true, message, skippedAgents, migratedFields };
101
147
  }
102
148
 
103
149
  function run() {
104
150
  const cwd = process.cwd();
105
- console.log('\nclaude-raid Updating The Raid\n');
151
+ console.log('\n' + banner());
152
+ console.log(header('Reforging the Arsenal...') + '\n');
153
+
106
154
  const result = performUpdate(cwd);
107
- console.log(result.message);
155
+
156
+ if (!result.success) {
157
+ console.log(' ' + colors.red('✖') + ' No party found. Run ' + colors.bold('claude-raid summon') + ' first.');
158
+ return;
159
+ }
160
+
161
+ console.log(' ' + colors.green('✔') + ' The party\'s arsenal has been reforged.');
162
+ if (result.skippedAgents.length > 0) {
163
+ console.log(' ' + colors.dim('Preserved customized warriors: ' + result.skippedAgents.join(', ')));
164
+ }
108
165
  }
109
166
 
110
167
  module.exports = { performUpdate, run };