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
package/src/doctor.js
CHANGED
|
@@ -1,201 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
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
|
|
9
|
-
|
|
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
|
-
|
|
10
|
+
async function run() {
|
|
11
|
+
const { referenceCard } = require('./ui');
|
|
29
12
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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(
|
|
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 = [
|
|
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
|
|
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
|
-
|
|
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
|
|
116
|
-
console.log('Proceeding with re-
|
|
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
|
-
|
|
180
|
+
// Detection summary
|
|
181
|
+
console.log(' Realm detected: ' + bold(result.detected.language));
|
|
120
182
|
if (result.detected.testCommand) {
|
|
121
|
-
console.log(
|
|
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(
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
Team rules: .claude/raid-rules.md (editable)
|
|
308
|
+
lines.push(' Run without --dry-run to install.');
|
|
133
309
|
|
|
134
|
-
|
|
135
|
-
`);
|
|
310
|
+
return lines.join('\n');
|
|
136
311
|
}
|
|
137
312
|
|
|
138
|
-
module.exports = { install, run };
|
|
313
|
+
module.exports = { install, run, dryRun };
|
package/src/merge-settings.js
CHANGED
|
@@ -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
|
|
29
|
-
{ type: 'command', command: `bash .claude/hooks/validate-tests-
|
|
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/
|
|
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
|
|
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 =>
|
|
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 = [
|
|
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('\
|
|
109
|
+
console.log('\n' + banner());
|
|
110
|
+
console.log(header('Dismantling the Camp...') + '\n');
|
|
88
111
|
performRemove(cwd);
|
|
89
|
-
console.log('
|
|
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 };
|