qaa-agent 1.6.2 → 1.6.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/.claude/commands/create-test.md +164 -164
- package/.claude/commands/qa-audit.md +37 -37
- package/.claude/commands/qa-blueprint.md +54 -54
- package/.claude/commands/qa-fix.md +36 -36
- package/.claude/commands/qa-from-ticket.md +24 -24
- package/.claude/commands/qa-gap.md +20 -20
- package/.claude/commands/qa-map.md +47 -47
- package/.claude/commands/qa-pom.md +36 -36
- package/.claude/commands/qa-pr.md +23 -23
- package/.claude/commands/qa-pyramid.md +37 -37
- package/.claude/commands/qa-report.md +38 -38
- package/.claude/commands/qa-research.md +33 -33
- package/.claude/commands/qa-start.md +22 -22
- package/.claude/commands/qa-testid.md +19 -19
- package/.claude/commands/qa-validate.md +42 -42
- package/.claude/commands/update-test.md +58 -58
- package/.claude/settings.json +20 -20
- package/.claude/skills/qa-bug-detective/SKILL.md +122 -122
- package/.claude/skills/qa-learner/SKILL.md +150 -150
- package/.claude/skills/qa-repo-analyzer/SKILL.md +88 -88
- package/.claude/skills/qa-self-validator/SKILL.md +109 -109
- package/.claude/skills/qa-template-engine/SKILL.md +113 -113
- package/.claude/skills/qa-testid-injector/SKILL.md +93 -93
- package/.claude/skills/qa-workflow-documenter/SKILL.md +87 -87
- package/.mcp.json +8 -8
- package/CHANGELOG.md +71 -71
- package/CLAUDE.md +553 -553
- package/agents/qa-pipeline-orchestrator.md +1378 -1378
- package/agents/qaa-analyzer.md +524 -524
- package/agents/qaa-bug-detective.md +446 -446
- package/agents/qaa-codebase-mapper.md +935 -935
- package/agents/qaa-e2e-runner.md +415 -415
- package/agents/qaa-executor.md +651 -651
- package/agents/qaa-planner.md +390 -390
- package/agents/qaa-project-researcher.md +319 -319
- package/agents/qaa-scanner.md +424 -424
- package/agents/qaa-testid-injector.md +585 -585
- package/agents/qaa-validator.md +452 -452
- package/bin/install.cjs +198 -198
- package/bin/lib/commands.cjs +709 -709
- package/bin/lib/config.cjs +307 -307
- package/bin/lib/core.cjs +497 -497
- package/bin/lib/frontmatter.cjs +299 -299
- package/bin/lib/init.cjs +989 -989
- package/bin/lib/milestone.cjs +241 -241
- package/bin/lib/model-profiles.cjs +60 -60
- package/bin/lib/phase.cjs +911 -911
- package/bin/lib/roadmap.cjs +306 -306
- package/bin/lib/state.cjs +748 -748
- package/bin/lib/template.cjs +222 -222
- package/bin/lib/verify.cjs +842 -842
- package/bin/qaa-tools.cjs +607 -607
- package/docs/COMMANDS.md +341 -341
- package/docs/DEMO.md +182 -182
- package/docs/TESTING.md +156 -156
- package/package.json +41 -41
- package/templates/failure-classification.md +391 -391
- package/templates/gap-analysis.md +409 -409
- package/templates/pr-template.md +48 -48
- package/templates/qa-analysis.md +381 -381
- package/templates/qa-audit-report.md +465 -465
- package/templates/qa-repo-blueprint.md +636 -636
- package/templates/scan-manifest.md +312 -312
- package/templates/test-inventory.md +582 -582
- package/templates/testid-audit-report.md +354 -354
- package/templates/validation-report.md +243 -243
- package/workflows/qa-analyze.md +296 -296
- package/workflows/qa-from-ticket.md +536 -536
- package/workflows/qa-gap.md +303 -303
- package/workflows/qa-pr.md +389 -389
- package/workflows/qa-start.md +1168 -1168
- package/workflows/qa-testid.md +356 -356
- package/workflows/qa-validate.md +295 -295
package/bin/install.cjs
CHANGED
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* QAA - QA Automation Agent Installer
|
|
4
|
-
* Run with: npx qaa-agent
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const readline = require('readline');
|
|
10
|
-
|
|
11
|
-
const VERSION = require('../package.json').version;
|
|
12
|
-
const ROOT = path.resolve(__dirname, '..');
|
|
13
|
-
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
14
|
-
|
|
15
|
-
// Runtime configs
|
|
16
|
-
const RUNTIMES = {
|
|
17
|
-
'1': { name: 'Claude Code', dir: path.join(HOME, '.claude') },
|
|
18
|
-
'2': { name: 'OpenCode', dir: path.join(HOME, '.config', 'opencode') },
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
function ask(question, defaultVal) {
|
|
22
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
-
return new Promise(resolve => {
|
|
24
|
-
rl.question(question, answer => {
|
|
25
|
-
rl.close();
|
|
26
|
-
resolve(answer.trim() || defaultVal);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function copyDir(src, dest) {
|
|
32
|
-
if (!fs.existsSync(src)) return 0;
|
|
33
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
34
|
-
let count = 0;
|
|
35
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
36
|
-
const srcPath = path.join(src, entry.name);
|
|
37
|
-
const destPath = path.join(dest, entry.name);
|
|
38
|
-
if (entry.isDirectory()) {
|
|
39
|
-
count += copyDir(srcPath, destPath);
|
|
40
|
-
} else {
|
|
41
|
-
fs.copyFileSync(srcPath, destPath);
|
|
42
|
-
count++;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return count;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function copyFile(src, dest) {
|
|
49
|
-
if (!fs.existsSync(src)) return false;
|
|
50
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
51
|
-
fs.copyFileSync(src, dest);
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function countEntries(dir, type) {
|
|
56
|
-
if (!fs.existsSync(dir)) return 0;
|
|
57
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
-
if (type === 'dirs') return entries.filter(e => e.isDirectory()).length;
|
|
59
|
-
return entries.filter(e => e.isFile()).length;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function ok(msg) { console.log(` \x1b[32m✓\x1b[0m ${msg}`); }
|
|
63
|
-
function info(msg) { console.log(` ${msg}`); }
|
|
64
|
-
|
|
65
|
-
async function main() {
|
|
66
|
-
console.log('');
|
|
67
|
-
console.log(' \x1b[36m ██████╗ █████╗ █████╗ \x1b[0m');
|
|
68
|
-
console.log(' \x1b[36m██╔═══██╗██╔══██╗██╔══██╗\x1b[0m');
|
|
69
|
-
console.log(' \x1b[36m██║ ██║███████║███████║\x1b[0m');
|
|
70
|
-
console.log(' \x1b[36m██║▄▄ ██║██╔══██║██╔══██║\x1b[0m');
|
|
71
|
-
console.log(' \x1b[36m╚██████╔╝██║ ██║██║ ██║\x1b[0m');
|
|
72
|
-
console.log(' \x1b[36m ╚══▀▀═╝ ╚═╝ ╚═╝╚═╝ ╚═╝\x1b[0m');
|
|
73
|
-
console.log('');
|
|
74
|
-
console.log(` \x1b[1mQA Automation Agent\x1b[0m v${VERSION}`);
|
|
75
|
-
console.log(' Multi-agent QA pipeline for Claude Code.');
|
|
76
|
-
console.log(' Analyzes repos, generates tests, validates, and creates PRs.');
|
|
77
|
-
console.log('');
|
|
78
|
-
|
|
79
|
-
// Ask runtime
|
|
80
|
-
console.log(' Which runtime would you like to install for?');
|
|
81
|
-
console.log('');
|
|
82
|
-
console.log(' 1) Claude Code (~/.claude)');
|
|
83
|
-
console.log(' 2) OpenCode (~/.config/opencode)');
|
|
84
|
-
console.log('');
|
|
85
|
-
const runtimeChoice = await ask(' Choice [1]: ', '1');
|
|
86
|
-
const runtime = RUNTIMES[runtimeChoice] || RUNTIMES['1'];
|
|
87
|
-
|
|
88
|
-
// Ask scope
|
|
89
|
-
console.log('');
|
|
90
|
-
console.log(' Where would you like to install?');
|
|
91
|
-
console.log('');
|
|
92
|
-
console.log(` 1) Global (~/${path.relative(HOME, runtime.dir)}) - available in all projects`);
|
|
93
|
-
console.log(' 2) Local (./.claude) - this project only');
|
|
94
|
-
console.log('');
|
|
95
|
-
const scopeChoice = await ask(' Choice [1]: ', '1');
|
|
96
|
-
const isGlobal = scopeChoice !== '2';
|
|
97
|
-
const baseDir = isGlobal ? runtime.dir : path.join(process.cwd(), '.claude');
|
|
98
|
-
const qaaDir = isGlobal ? path.join(runtime.dir, 'qaa') : path.join(process.cwd(), '.claude', 'qaa');
|
|
99
|
-
|
|
100
|
-
console.log('');
|
|
101
|
-
console.log(` Installing for ${runtime.name} to ${isGlobal ? '~/' + path.relative(HOME, runtime.dir) : './.claude'}`);
|
|
102
|
-
console.log('');
|
|
103
|
-
|
|
104
|
-
// Install commands
|
|
105
|
-
const commandsSrc = path.join(ROOT, '.claude', 'commands');
|
|
106
|
-
const commandsDest = path.join(baseDir, 'commands');
|
|
107
|
-
const cmdCount = copyDir(commandsSrc, commandsDest);
|
|
108
|
-
ok(`Installed ${cmdCount} slash commands`);
|
|
109
|
-
|
|
110
|
-
// Install skills (only to baseDir -- Claude Code reads from ~/.claude/skills/)
|
|
111
|
-
const skillsSrc = path.join(ROOT, '.claude', 'skills');
|
|
112
|
-
const skillsDest = path.join(baseDir, 'skills');
|
|
113
|
-
const skillCount = copyDir(skillsSrc, skillsDest);
|
|
114
|
-
const skillDirCount = countEntries(skillsSrc, 'dirs');
|
|
115
|
-
ok(`Installed ${skillDirCount} skills (${skillCount} files)`);
|
|
116
|
-
|
|
117
|
-
// Install workflows
|
|
118
|
-
const workflowsSrc = path.join(ROOT, 'workflows');
|
|
119
|
-
const workflowsDest = path.join(qaaDir, 'workflows');
|
|
120
|
-
const wfCount = copyDir(workflowsSrc, workflowsDest);
|
|
121
|
-
ok(`Installed ${wfCount} workflows`);
|
|
122
|
-
|
|
123
|
-
// Install agents
|
|
124
|
-
const agentsSrc = path.join(ROOT, 'agents');
|
|
125
|
-
const agentsDest = path.join(qaaDir, 'agents');
|
|
126
|
-
const agentCount = copyDir(agentsSrc, agentsDest);
|
|
127
|
-
ok(`Installed ${agentCount} agent definitions`);
|
|
128
|
-
|
|
129
|
-
// Install templates
|
|
130
|
-
const templatesSrc = path.join(ROOT, 'templates');
|
|
131
|
-
const templatesDest = path.join(qaaDir, 'templates');
|
|
132
|
-
const templateCount = copyDir(templatesSrc, templatesDest);
|
|
133
|
-
ok(`Installed ${templateCount} templates`);
|
|
134
|
-
|
|
135
|
-
// Install bin
|
|
136
|
-
const binSrc = path.join(ROOT, 'bin');
|
|
137
|
-
const binDest = path.join(qaaDir, 'bin');
|
|
138
|
-
const binCount = copyDir(binSrc, binDest);
|
|
139
|
-
try { fs.unlinkSync(path.join(binDest, 'install.cjs')); } catch {}
|
|
140
|
-
ok(`Installed CLI tooling`);
|
|
141
|
-
|
|
142
|
-
// Install CLAUDE.md
|
|
143
|
-
copyFile(path.join(ROOT, 'CLAUDE.md'), path.join(qaaDir, 'CLAUDE.md'));
|
|
144
|
-
ok('Installed QA standards (CLAUDE.md)');
|
|
145
|
-
|
|
146
|
-
// Install .mcp.json (Playwright MCP server config)
|
|
147
|
-
const mcpSrc = path.join(ROOT, '.mcp.json');
|
|
148
|
-
if (fs.existsSync(mcpSrc)) {
|
|
149
|
-
const mcpDest = path.join(qaaDir, '.mcp.json');
|
|
150
|
-
copyFile(mcpSrc, mcpDest);
|
|
151
|
-
ok('Installed Playwright MCP server config (.mcp.json)');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Write version
|
|
155
|
-
fs.writeFileSync(path.join(qaaDir, 'VERSION'), VERSION);
|
|
156
|
-
ok(`Wrote VERSION (${VERSION})`);
|
|
157
|
-
|
|
158
|
-
// Merge settings
|
|
159
|
-
const settingsSrc = path.join(ROOT, '.claude', 'settings.json');
|
|
160
|
-
const settingsDest = path.join(baseDir, 'settings.json');
|
|
161
|
-
if (fs.existsSync(settingsSrc)) {
|
|
162
|
-
let existing = {};
|
|
163
|
-
if (fs.existsSync(settingsDest)) {
|
|
164
|
-
try { existing = JSON.parse(fs.readFileSync(settingsDest, 'utf8')); } catch {}
|
|
165
|
-
}
|
|
166
|
-
const qaaSettings = JSON.parse(fs.readFileSync(settingsSrc, 'utf8'));
|
|
167
|
-
if (qaaSettings.permissions) {
|
|
168
|
-
existing.permissions = existing.permissions || {};
|
|
169
|
-
existing.permissions.allow = [...new Set([
|
|
170
|
-
...(existing.permissions.allow || []),
|
|
171
|
-
...(qaaSettings.permissions.allow || [])
|
|
172
|
-
])];
|
|
173
|
-
}
|
|
174
|
-
fs.writeFileSync(settingsDest, JSON.stringify(existing, null, 2));
|
|
175
|
-
ok('Merged permissions into settings.json');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Done
|
|
179
|
-
const total = cmdCount + skillCount + agentCount + templateCount + wfCount + binCount;
|
|
180
|
-
console.log('');
|
|
181
|
-
console.log(` \x1b[32m✓ Done!\x1b[0m Installed ${total} files.`);
|
|
182
|
-
console.log('');
|
|
183
|
-
console.log(' Open Claude Code in any project and run:');
|
|
184
|
-
console.log('');
|
|
185
|
-
console.log(' \x1b[1m/qa-start\x1b[0m Full QA pipeline (multi-agent)');
|
|
186
|
-
console.log(' \x1b[1m/qa-map\x1b[0m Codebase map + analysis');
|
|
187
|
-
console.log(' \x1b[1m/create-test\x1b[0m Tests for a feature');
|
|
188
|
-
console.log(' \x1b[1m/qa-from-ticket\x1b[0m Tests from a Jira/Linear ticket');
|
|
189
|
-
console.log(' \x1b[1m/qa-validate\x1b[0m Validate existing tests');
|
|
190
|
-
console.log('');
|
|
191
|
-
console.log(` ${cmdCount} commands + ${skillDirCount} skills + ${agentCount} agents ready.`);
|
|
192
|
-
console.log('');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
main().catch(err => {
|
|
196
|
-
console.error('Installation failed:', err.message);
|
|
197
|
-
process.exit(1);
|
|
198
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* QAA - QA Automation Agent Installer
|
|
4
|
+
* Run with: npx qaa-agent
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
|
|
11
|
+
const VERSION = require('../package.json').version;
|
|
12
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
13
|
+
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
14
|
+
|
|
15
|
+
// Runtime configs
|
|
16
|
+
const RUNTIMES = {
|
|
17
|
+
'1': { name: 'Claude Code', dir: path.join(HOME, '.claude') },
|
|
18
|
+
'2': { name: 'OpenCode', dir: path.join(HOME, '.config', 'opencode') },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function ask(question, defaultVal) {
|
|
22
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
return new Promise(resolve => {
|
|
24
|
+
rl.question(question, answer => {
|
|
25
|
+
rl.close();
|
|
26
|
+
resolve(answer.trim() || defaultVal);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function copyDir(src, dest) {
|
|
32
|
+
if (!fs.existsSync(src)) return 0;
|
|
33
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
36
|
+
const srcPath = path.join(src, entry.name);
|
|
37
|
+
const destPath = path.join(dest, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
count += copyDir(srcPath, destPath);
|
|
40
|
+
} else {
|
|
41
|
+
fs.copyFileSync(srcPath, destPath);
|
|
42
|
+
count++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return count;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function copyFile(src, dest) {
|
|
49
|
+
if (!fs.existsSync(src)) return false;
|
|
50
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
51
|
+
fs.copyFileSync(src, dest);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function countEntries(dir, type) {
|
|
56
|
+
if (!fs.existsSync(dir)) return 0;
|
|
57
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
if (type === 'dirs') return entries.filter(e => e.isDirectory()).length;
|
|
59
|
+
return entries.filter(e => e.isFile()).length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ok(msg) { console.log(` \x1b[32m✓\x1b[0m ${msg}`); }
|
|
63
|
+
function info(msg) { console.log(` ${msg}`); }
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(' \x1b[36m ██████╗ █████╗ █████╗ \x1b[0m');
|
|
68
|
+
console.log(' \x1b[36m██╔═══██╗██╔══██╗██╔══██╗\x1b[0m');
|
|
69
|
+
console.log(' \x1b[36m██║ ██║███████║███████║\x1b[0m');
|
|
70
|
+
console.log(' \x1b[36m██║▄▄ ██║██╔══██║██╔══██║\x1b[0m');
|
|
71
|
+
console.log(' \x1b[36m╚██████╔╝██║ ██║██║ ██║\x1b[0m');
|
|
72
|
+
console.log(' \x1b[36m ╚══▀▀═╝ ╚═╝ ╚═╝╚═╝ ╚═╝\x1b[0m');
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(` \x1b[1mQA Automation Agent\x1b[0m v${VERSION}`);
|
|
75
|
+
console.log(' Multi-agent QA pipeline for Claude Code.');
|
|
76
|
+
console.log(' Analyzes repos, generates tests, validates, and creates PRs.');
|
|
77
|
+
console.log('');
|
|
78
|
+
|
|
79
|
+
// Ask runtime
|
|
80
|
+
console.log(' Which runtime would you like to install for?');
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(' 1) Claude Code (~/.claude)');
|
|
83
|
+
console.log(' 2) OpenCode (~/.config/opencode)');
|
|
84
|
+
console.log('');
|
|
85
|
+
const runtimeChoice = await ask(' Choice [1]: ', '1');
|
|
86
|
+
const runtime = RUNTIMES[runtimeChoice] || RUNTIMES['1'];
|
|
87
|
+
|
|
88
|
+
// Ask scope
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(' Where would you like to install?');
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(` 1) Global (~/${path.relative(HOME, runtime.dir)}) - available in all projects`);
|
|
93
|
+
console.log(' 2) Local (./.claude) - this project only');
|
|
94
|
+
console.log('');
|
|
95
|
+
const scopeChoice = await ask(' Choice [1]: ', '1');
|
|
96
|
+
const isGlobal = scopeChoice !== '2';
|
|
97
|
+
const baseDir = isGlobal ? runtime.dir : path.join(process.cwd(), '.claude');
|
|
98
|
+
const qaaDir = isGlobal ? path.join(runtime.dir, 'qaa') : path.join(process.cwd(), '.claude', 'qaa');
|
|
99
|
+
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(` Installing for ${runtime.name} to ${isGlobal ? '~/' + path.relative(HOME, runtime.dir) : './.claude'}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
|
|
104
|
+
// Install commands
|
|
105
|
+
const commandsSrc = path.join(ROOT, '.claude', 'commands');
|
|
106
|
+
const commandsDest = path.join(baseDir, 'commands');
|
|
107
|
+
const cmdCount = copyDir(commandsSrc, commandsDest);
|
|
108
|
+
ok(`Installed ${cmdCount} slash commands`);
|
|
109
|
+
|
|
110
|
+
// Install skills (only to baseDir -- Claude Code reads from ~/.claude/skills/)
|
|
111
|
+
const skillsSrc = path.join(ROOT, '.claude', 'skills');
|
|
112
|
+
const skillsDest = path.join(baseDir, 'skills');
|
|
113
|
+
const skillCount = copyDir(skillsSrc, skillsDest);
|
|
114
|
+
const skillDirCount = countEntries(skillsSrc, 'dirs');
|
|
115
|
+
ok(`Installed ${skillDirCount} skills (${skillCount} files)`);
|
|
116
|
+
|
|
117
|
+
// Install workflows
|
|
118
|
+
const workflowsSrc = path.join(ROOT, 'workflows');
|
|
119
|
+
const workflowsDest = path.join(qaaDir, 'workflows');
|
|
120
|
+
const wfCount = copyDir(workflowsSrc, workflowsDest);
|
|
121
|
+
ok(`Installed ${wfCount} workflows`);
|
|
122
|
+
|
|
123
|
+
// Install agents
|
|
124
|
+
const agentsSrc = path.join(ROOT, 'agents');
|
|
125
|
+
const agentsDest = path.join(qaaDir, 'agents');
|
|
126
|
+
const agentCount = copyDir(agentsSrc, agentsDest);
|
|
127
|
+
ok(`Installed ${agentCount} agent definitions`);
|
|
128
|
+
|
|
129
|
+
// Install templates
|
|
130
|
+
const templatesSrc = path.join(ROOT, 'templates');
|
|
131
|
+
const templatesDest = path.join(qaaDir, 'templates');
|
|
132
|
+
const templateCount = copyDir(templatesSrc, templatesDest);
|
|
133
|
+
ok(`Installed ${templateCount} templates`);
|
|
134
|
+
|
|
135
|
+
// Install bin
|
|
136
|
+
const binSrc = path.join(ROOT, 'bin');
|
|
137
|
+
const binDest = path.join(qaaDir, 'bin');
|
|
138
|
+
const binCount = copyDir(binSrc, binDest);
|
|
139
|
+
try { fs.unlinkSync(path.join(binDest, 'install.cjs')); } catch {}
|
|
140
|
+
ok(`Installed CLI tooling`);
|
|
141
|
+
|
|
142
|
+
// Install CLAUDE.md
|
|
143
|
+
copyFile(path.join(ROOT, 'CLAUDE.md'), path.join(qaaDir, 'CLAUDE.md'));
|
|
144
|
+
ok('Installed QA standards (CLAUDE.md)');
|
|
145
|
+
|
|
146
|
+
// Install .mcp.json (Playwright MCP server config)
|
|
147
|
+
const mcpSrc = path.join(ROOT, '.mcp.json');
|
|
148
|
+
if (fs.existsSync(mcpSrc)) {
|
|
149
|
+
const mcpDest = path.join(qaaDir, '.mcp.json');
|
|
150
|
+
copyFile(mcpSrc, mcpDest);
|
|
151
|
+
ok('Installed Playwright MCP server config (.mcp.json)');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Write version
|
|
155
|
+
fs.writeFileSync(path.join(qaaDir, 'VERSION'), VERSION);
|
|
156
|
+
ok(`Wrote VERSION (${VERSION})`);
|
|
157
|
+
|
|
158
|
+
// Merge settings
|
|
159
|
+
const settingsSrc = path.join(ROOT, '.claude', 'settings.json');
|
|
160
|
+
const settingsDest = path.join(baseDir, 'settings.json');
|
|
161
|
+
if (fs.existsSync(settingsSrc)) {
|
|
162
|
+
let existing = {};
|
|
163
|
+
if (fs.existsSync(settingsDest)) {
|
|
164
|
+
try { existing = JSON.parse(fs.readFileSync(settingsDest, 'utf8')); } catch {}
|
|
165
|
+
}
|
|
166
|
+
const qaaSettings = JSON.parse(fs.readFileSync(settingsSrc, 'utf8'));
|
|
167
|
+
if (qaaSettings.permissions) {
|
|
168
|
+
existing.permissions = existing.permissions || {};
|
|
169
|
+
existing.permissions.allow = [...new Set([
|
|
170
|
+
...(existing.permissions.allow || []),
|
|
171
|
+
...(qaaSettings.permissions.allow || [])
|
|
172
|
+
])];
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(settingsDest, JSON.stringify(existing, null, 2));
|
|
175
|
+
ok('Merged permissions into settings.json');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Done
|
|
179
|
+
const total = cmdCount + skillCount + agentCount + templateCount + wfCount + binCount;
|
|
180
|
+
console.log('');
|
|
181
|
+
console.log(` \x1b[32m✓ Done!\x1b[0m Installed ${total} files.`);
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(' Open Claude Code in any project and run:');
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log(' \x1b[1m/qa-start\x1b[0m Full QA pipeline (multi-agent)');
|
|
186
|
+
console.log(' \x1b[1m/qa-map\x1b[0m Codebase map + analysis');
|
|
187
|
+
console.log(' \x1b[1m/create-test\x1b[0m Tests for a feature');
|
|
188
|
+
console.log(' \x1b[1m/qa-from-ticket\x1b[0m Tests from a Jira/Linear ticket');
|
|
189
|
+
console.log(' \x1b[1m/qa-validate\x1b[0m Validate existing tests');
|
|
190
|
+
console.log('');
|
|
191
|
+
console.log(` ${cmdCount} commands + ${skillDirCount} skills + ${agentCount} agents ready.`);
|
|
192
|
+
console.log('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
main().catch(err => {
|
|
196
|
+
console.error('Installation failed:', err.message);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|