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/bin/cli.js CHANGED
@@ -3,32 +3,59 @@
3
3
  'use strict';
4
4
 
5
5
  const command = process.argv[2];
6
+ const { banner, colors, header } = require('../src/ui');
7
+ const versionCheck = require('../src/version-check');
8
+
9
+ // Start non-blocking version check immediately
10
+ const showUpdateNotice = versionCheck.start();
6
11
 
7
12
  const COMMANDS = {
8
- init: () => require('../src/init').run(),
13
+ // Primary commands
14
+ summon: () => {
15
+ if (process.argv.includes('--dry-run')) {
16
+ console.log('\n' + banner());
17
+ console.log(require('../src/init').dryRun(process.cwd()));
18
+ return;
19
+ }
20
+ return require('../src/init').run();
21
+ },
9
22
  update: () => require('../src/update').run(),
23
+ dismantle: () => require('../src/remove').run(),
24
+ heal: () => require('../src/doctor').run(),
25
+ // Aliases (backward compat)
26
+ init: () => {
27
+ if (process.argv.includes('--dry-run')) {
28
+ console.log('\n' + banner());
29
+ console.log(require('../src/init').dryRun(process.cwd()));
30
+ return;
31
+ }
32
+ return require('../src/init').run();
33
+ },
10
34
  remove: () => require('../src/remove').run(),
11
35
  doctor: () => require('../src/doctor').run(),
12
36
  };
13
37
 
14
38
  if (!command || !COMMANDS[command]) {
15
- console.log(`
16
- claude-raid Adversarial multi-agent development for Claude Code
17
-
18
- Usage:
19
- claude-raid init Install The Raid into the current project
20
- claude-raid update Update to the latest version
21
- claude-raid remove Uninstall The Raid
22
- claude-raid doctor Check prerequisites and show quick start guide
23
-
24
- Learn more: https://github.com/pedropicardi/claude-raid
25
- `);
39
+ console.log('\n' + banner());
40
+ console.log(header('Commands') + '\n');
41
+ const cmds = [
42
+ ['summon', 'Summon the party into this realm'],
43
+ ['update', 'Reforge the party\'s arsenal'],
44
+ ['dismantle', 'Dismantle the camp and retreat'],
45
+ ['heal', 'Diagnose wounds and prepare for battle'],
46
+ ];
47
+ for (const [name, desc] of cmds) {
48
+ console.log(' ' + colors.bold(name.padEnd(12)) + desc);
49
+ }
50
+ console.log(header('Begin the Raid') + '\n');
51
+ console.log(' claude --agent wizard\n');
52
+ console.log(colors.dim(' github.com/pedropicardi/claude-raid') + '\n');
26
53
  process.exit(command ? 1 : 0);
27
54
  }
28
55
 
29
- try {
30
- COMMANDS[command]();
31
- } catch (err) {
32
- console.error(`\nclaude-raid: ${err.message}\n`);
33
- process.exit(1);
34
- }
56
+ Promise.resolve(COMMANDS[command]())
57
+ .then(() => showUpdateNotice(colors))
58
+ .catch((err) => {
59
+ console.error(`\nclaude-raid: ${err.message}\n`);
60
+ process.exit(1);
61
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-raid",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "commonjs",
5
5
  "description": "Adversarial multi-agent development system for Claude Code",
6
6
  "author": "Pedro Picardi",
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ // Single source of truth for file descriptions used by dry-run and install output.
4
+ // Keep descriptions under 80 chars — they appear in terminal columns.
5
+
6
+ const AGENTS = {
7
+ 'wizard.md': 'Dungeon master — opens phases, dispatches team, closes with rulings',
8
+ 'warrior.md': 'Stress-tester — breaks things under load, edge cases, pressure',
9
+ 'archer.md': 'Pattern-seeker — traces ripple effects, naming drift, contract violations',
10
+ 'rogue.md': 'Assumption-destroyer — thinks like a failing system, malicious input',
11
+ };
12
+
13
+ const HOOKS = {
14
+ lifecycle: [
15
+ { name: 'raid-lib.sh', desc: 'Shared config — reads raid.json, exports session state' },
16
+ { name: 'raid-session-start.sh', desc: 'Activates Raid workflow when session begins' },
17
+ { name: 'raid-session-end.sh', desc: 'Archives Dungeon, cleans up when session ends' },
18
+ { name: 'raid-stop.sh', desc: 'Backs up Dungeon on phase transitions' },
19
+ { name: 'raid-pre-compact.sh', desc: 'Backs up Dungeon before message compaction' },
20
+ { name: 'raid-task-created.sh', desc: 'Validates task subjects are meaningful' },
21
+ { name: 'raid-task-completed.sh', desc: 'Blocks task completion without test evidence' },
22
+ { name: 'raid-teammate-idle.sh', desc: 'Nudges idle agents to participate' },
23
+ ],
24
+ gates: [
25
+ { name: 'validate-commit.sh', desc: 'Enforces conventional commits + test gate' },
26
+ { name: 'validate-write-gate.sh', desc: 'Blocks implementation before design doc exists' },
27
+ { name: 'validate-file-naming.sh', desc: 'Enforces naming convention (kebab-case, etc.)' },
28
+ { name: 'validate-no-placeholders.sh', desc: 'Blocks TBD/TODO in specs and plans' },
29
+ { name: 'validate-dungeon.sh', desc: 'Requires multi-agent verification on pins' },
30
+ { name: 'validate-browser-tests-exist.sh', desc: 'Checks Playwright tests exist before commits' },
31
+ { name: 'validate-browser-cleanup.sh', desc: 'Verifies browser processes cleaned up properly' },
32
+ ],
33
+ };
34
+
35
+ const SKILLS = {
36
+ 'raid-protocol': 'Session lifecycle and team rules',
37
+ 'raid-design': 'Phase 1: adversarial exploration',
38
+ 'raid-implementation-plan': 'Phase 2: task decomposition',
39
+ 'raid-implementation': 'Phase 3: TDD with direct challenge',
40
+ 'raid-review': 'Phase 4: independent review + fighting',
41
+ 'raid-finishing': 'Completeness debate + merge options',
42
+ 'raid-tdd': 'RED-GREEN-REFACTOR enforcement',
43
+ 'raid-debugging': 'Root-cause investigation',
44
+ 'raid-verification': 'Evidence-before-claims gate',
45
+ 'raid-git-worktrees': 'Isolated workspace creation',
46
+ 'raid-browser': 'Browser startup discovery',
47
+ 'raid-browser-playwright': 'Playwright test authoring',
48
+ 'raid-browser-chrome': 'Live browser inspection',
49
+ };
50
+
51
+ const CONFIG = {
52
+ 'raid.json': 'Project settings (editable)',
53
+ 'raid-rules.md': '17 team rules (editable)',
54
+ 'settings.json': 'Hooks merged into existing (backup created)',
55
+ };
56
+
57
+ module.exports = { AGENTS, HOOKS, SKILLS, CONFIG };
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Single-file detectors: first matching variant wins
7
+ const SINGLE_FILE_DETECTORS = [
8
+ {
9
+ variants: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
10
+ framework: 'next',
11
+ devCommand: (run) => `${run} dev`,
12
+ defaultPort: 3000,
13
+ },
14
+ {
15
+ variants: ['nuxt.config.ts', 'nuxt.config.js'],
16
+ framework: 'nuxt',
17
+ devCommand: (run) => `${run} dev`,
18
+ defaultPort: 3000,
19
+ },
20
+ {
21
+ variants: ['remix.config.js', 'remix.config.ts'],
22
+ framework: 'remix',
23
+ devCommand: (run) => `${run} dev`,
24
+ defaultPort: 3000,
25
+ },
26
+ {
27
+ variants: ['svelte.config.js', 'svelte.config.ts'],
28
+ framework: 'svelte',
29
+ devCommand: (run) => `${run} dev`,
30
+ defaultPort: 5173,
31
+ },
32
+ {
33
+ variants: ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'],
34
+ framework: 'vite',
35
+ devCommand: (run) => `${run} dev`,
36
+ defaultPort: 5173,
37
+ },
38
+ {
39
+ variants: ['angular.json'],
40
+ framework: 'angular',
41
+ devCommand: () => 'ng serve',
42
+ defaultPort: 4200,
43
+ },
44
+ {
45
+ variants: ['astro.config.mjs', 'astro.config.js', 'astro.config.ts'],
46
+ framework: 'astro',
47
+ devCommand: (run) => `${run} dev`,
48
+ defaultPort: 4321,
49
+ },
50
+ {
51
+ variants: ['gatsby-config.js', 'gatsby-config.ts'],
52
+ framework: 'gatsby',
53
+ devCommand: (run) => `${run} develop`,
54
+ defaultPort: 8000,
55
+ },
56
+ {
57
+ variants: ['manage.py'],
58
+ framework: 'django',
59
+ devCommand: () => 'python manage.py runserver',
60
+ defaultPort: 8000,
61
+ },
62
+ {
63
+ variants: ['trunk.toml'],
64
+ framework: 'trunk',
65
+ devCommand: () => 'trunk serve',
66
+ defaultPort: 8080,
67
+ },
68
+ ];
69
+
70
+ // Multi-file detectors: ALL listed files must exist
71
+ const MULTI_FILE_DETECTORS = [
72
+ {
73
+ files: ['webpack.config.js', 'index.html'],
74
+ framework: 'webpack',
75
+ devCommand: (run) => `${run} dev`,
76
+ defaultPort: 8080,
77
+ },
78
+ ];
79
+
80
+ // Content-checked detectors: file must exist AND contain a marker string
81
+ const CONTENT_DETECTORS = [
82
+ {
83
+ file: 'app.py',
84
+ markers: ['flask', 'Flask'],
85
+ framework: 'flask',
86
+ devCommand: () => 'flask run',
87
+ defaultPort: 5000,
88
+ },
89
+ ];
90
+
91
+ // Directory+file detectors: the nested path must exist
92
+ const DIR_FILE_DETECTORS = [
93
+ {
94
+ filePath: path.join('app', 'root.tsx'),
95
+ framework: 'remix',
96
+ devCommand: (run) => `${run} dev`,
97
+ defaultPort: 3000,
98
+ },
99
+ ];
100
+
101
+ function detectBrowser(cwd, runCommand) {
102
+ // Check single-file detectors (first match wins)
103
+ for (const detector of SINGLE_FILE_DETECTORS) {
104
+ for (const variant of detector.variants) {
105
+ if (fs.existsSync(path.join(cwd, variant))) {
106
+ return {
107
+ detected: true,
108
+ framework: detector.framework,
109
+ devCommand: detector.devCommand(runCommand),
110
+ defaultPort: detector.defaultPort,
111
+ };
112
+ }
113
+ }
114
+ }
115
+
116
+ // Check content-based detectors (file must exist and contain marker)
117
+ for (const detector of CONTENT_DETECTORS) {
118
+ const filePath = path.join(cwd, detector.file);
119
+ if (fs.existsSync(filePath)) {
120
+ try {
121
+ const content = fs.readFileSync(filePath, 'utf8');
122
+ if (detector.markers.some(m => content.includes(m))) {
123
+ return {
124
+ detected: true,
125
+ framework: detector.framework,
126
+ devCommand: detector.devCommand(runCommand),
127
+ defaultPort: detector.defaultPort,
128
+ };
129
+ }
130
+ } catch {
131
+ // Unreadable file, skip
132
+ }
133
+ }
134
+ }
135
+
136
+ // Check multi-file detectors (all files must exist)
137
+ for (const detector of MULTI_FILE_DETECTORS) {
138
+ const allExist = detector.files.every((f) => fs.existsSync(path.join(cwd, f)));
139
+ if (allExist) {
140
+ return {
141
+ detected: true,
142
+ framework: detector.framework,
143
+ devCommand: detector.devCommand(runCommand),
144
+ defaultPort: detector.defaultPort,
145
+ };
146
+ }
147
+ }
148
+
149
+ // Check directory+file detectors
150
+ for (const detector of DIR_FILE_DETECTORS) {
151
+ if (fs.existsSync(path.join(cwd, detector.filePath))) {
152
+ return {
153
+ detected: true,
154
+ framework: detector.framework,
155
+ devCommand: detector.devCommand(runCommand),
156
+ defaultPort: detector.defaultPort,
157
+ };
158
+ }
159
+ }
160
+
161
+ return null;
162
+ }
163
+
164
+ module.exports = { detectBrowser };
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const JS_LOCKFILES = [
7
+ {
8
+ file: 'pnpm-lock.yaml',
9
+ packageManager: 'pnpm',
10
+ runCommand: 'pnpm',
11
+ execCommand: 'pnpm dlx',
12
+ installCommand: 'pnpm add',
13
+ },
14
+ {
15
+ file: 'yarn.lock',
16
+ packageManager: 'yarn',
17
+ runCommand: 'yarn',
18
+ execCommand: 'yarn dlx',
19
+ installCommand: 'yarn add',
20
+ },
21
+ {
22
+ file: 'bun.lockb',
23
+ packageManager: 'bun',
24
+ runCommand: 'bun',
25
+ execCommand: 'bunx',
26
+ installCommand: 'bun add',
27
+ },
28
+ {
29
+ file: 'bun.lock',
30
+ packageManager: 'bun',
31
+ runCommand: 'bun',
32
+ execCommand: 'bunx',
33
+ installCommand: 'bun add',
34
+ },
35
+ {
36
+ file: 'package-lock.json',
37
+ packageManager: 'npm',
38
+ runCommand: 'npm run',
39
+ execCommand: 'npx',
40
+ installCommand: 'npm install',
41
+ },
42
+ ];
43
+
44
+ const PYTHON_LOCKFILES = [
45
+ {
46
+ file: 'uv.lock',
47
+ packageManager: 'uv',
48
+ runCommand: 'uv run',
49
+ execCommand: 'uvx',
50
+ installCommand: 'uv add',
51
+ },
52
+ {
53
+ file: 'poetry.lock',
54
+ packageManager: 'poetry',
55
+ runCommand: 'poetry run',
56
+ execCommand: 'poetry run',
57
+ installCommand: 'poetry add',
58
+ },
59
+ ];
60
+
61
+ const JS_FALLBACK = {
62
+ packageManager: 'npm',
63
+ runCommand: 'npm run',
64
+ execCommand: 'npx',
65
+ installCommand: 'npm install',
66
+ };
67
+
68
+ const PYTHON_FALLBACK = {
69
+ packageManager: 'pip',
70
+ runCommand: 'python -m',
71
+ execCommand: 'python -m',
72
+ installCommand: 'pip install',
73
+ };
74
+
75
+ function detectPackageManager(cwd, language) {
76
+ if (language === 'javascript') {
77
+ for (const entry of JS_LOCKFILES) {
78
+ if (fs.existsSync(path.join(cwd, entry.file))) {
79
+ return {
80
+ packageManager: entry.packageManager,
81
+ runCommand: entry.runCommand,
82
+ execCommand: entry.execCommand,
83
+ installCommand: entry.installCommand,
84
+ };
85
+ }
86
+ }
87
+ return { ...JS_FALLBACK };
88
+ }
89
+
90
+ if (language === 'python') {
91
+ for (const entry of PYTHON_LOCKFILES) {
92
+ if (fs.existsSync(path.join(cwd, entry.file))) {
93
+ return {
94
+ packageManager: entry.packageManager,
95
+ runCommand: entry.runCommand,
96
+ execCommand: entry.execCommand,
97
+ installCommand: entry.installCommand,
98
+ };
99
+ }
100
+ }
101
+ return { ...PYTHON_FALLBACK };
102
+ }
103
+
104
+ return null;
105
+ }
106
+
107
+ module.exports = { detectPackageManager };
@@ -2,21 +2,28 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const { detectPackageManager } = require('./detect-package-manager');
6
+ const { detectBrowser } = require('./detect-browser');
5
7
 
6
8
  const DETECTORS = [
7
9
  {
8
10
  file: 'package.json',
9
11
  language: 'javascript',
10
- detect(cwd) {
12
+ detect(cwd, pm) {
11
13
  const pkgPath = path.join(cwd, 'package.json');
14
+ const run = pm ? pm.runCommand : 'npm run';
15
+ // For pnpm/yarn/bun the runCommand is just the pm name (e.g. 'pnpm'),
16
+ // so 'pnpm test' is correct. For npm the runCommand is 'npm run',
17
+ // so we special-case 'npm run test' -> 'npm test'.
18
+ const testCmd = (run === 'npm run') ? 'npm test' : `${run} test`;
12
19
  try {
13
20
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
14
21
  const scripts = pkg.scripts || {};
15
22
  return {
16
23
  language: 'javascript',
17
- testCommand: scripts.test ? 'npm test' : '',
18
- lintCommand: scripts.lint ? 'npm run lint' : '',
19
- buildCommand: scripts.build ? 'npm run build' : '',
24
+ testCommand: scripts.test ? testCmd : '',
25
+ lintCommand: scripts.lint ? `${run} lint` : '',
26
+ buildCommand: scripts.build ? `${run} build` : '',
20
27
  name: pkg.name || path.basename(cwd),
21
28
  };
22
29
  } catch {
@@ -40,10 +47,29 @@ const DETECTORS = [
40
47
  {
41
48
  file: 'pyproject.toml',
42
49
  language: 'python',
43
- detect(cwd) {
50
+ detect(cwd, pm) {
44
51
  try {
45
52
  const content = fs.readFileSync(path.join(cwd, 'pyproject.toml'), 'utf8');
46
53
  const usesPoetry = content.includes('[tool.poetry]');
54
+ // If a pm is provided (uv or poetry), use its runCommand
55
+ if (pm && pm.packageManager === 'uv') {
56
+ return {
57
+ language: 'python',
58
+ testCommand: 'uv run pytest',
59
+ lintCommand: 'uv run ruff check .',
60
+ buildCommand: 'uv run python -m build',
61
+ name: path.basename(cwd),
62
+ };
63
+ }
64
+ if (pm && pm.packageManager === 'poetry') {
65
+ return {
66
+ language: 'python',
67
+ testCommand: 'poetry run pytest',
68
+ lintCommand: 'poetry run ruff check .',
69
+ buildCommand: 'poetry build',
70
+ name: path.basename(cwd),
71
+ };
72
+ }
47
73
  return {
48
74
  language: 'python',
49
75
  testCommand: usesPoetry ? 'poetry run pytest' : 'pytest',
@@ -89,7 +115,15 @@ function detectProject(cwd) {
89
115
 
90
116
  for (const detector of DETECTORS) {
91
117
  if (fs.existsSync(path.join(cwd, detector.file))) {
92
- detected.push(detector.detect(cwd));
118
+ const pm = detectPackageManager(cwd, detector.language);
119
+ const entry = detector.detect(cwd, pm);
120
+ if (pm) {
121
+ entry.packageManager = pm.packageManager;
122
+ entry.runCommand = pm.runCommand;
123
+ entry.execCommand = pm.execCommand;
124
+ entry.installCommand = pm.installCommand;
125
+ }
126
+ detected.push(entry);
93
127
  }
94
128
  }
95
129
 
@@ -100,11 +134,15 @@ function detectProject(cwd) {
100
134
  lintCommand: '',
101
135
  buildCommand: '',
102
136
  name: path.basename(cwd),
137
+ browser: null,
103
138
  detected: [],
104
139
  };
105
140
  }
106
141
 
107
142
  const primary = detected[0];
143
+ // Use the primary entry's runCommand for browser detection, fall back to 'npm run'
144
+ const primaryRunCommand = primary.runCommand || 'npm run';
145
+ primary.browser = detectBrowser(cwd, primaryRunCommand);
108
146
  primary.detected = detected;
109
147
  return primary;
110
148
  }