ghost-qa 0.0.0-stage → 1.3.0
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/LICENSE +21 -0
- package/README.md +273 -3
- package/SKILL.md +105 -0
- package/dist/bin/ghost-qa.d.ts +35 -0
- package/dist/bin/ghost-qa.d.ts.map +1 -0
- package/dist/bin/ghost-qa.js +406 -0
- package/dist/bin/ghost-qa.js.map +1 -0
- package/dist/src/engine/audit-runner.d.ts +10 -0
- package/dist/src/engine/audit-runner.d.ts.map +1 -0
- package/dist/src/engine/audit-runner.js +138 -0
- package/dist/src/engine/audit-runner.js.map +1 -0
- package/dist/src/engine/browser-session.d.ts +37 -0
- package/dist/src/engine/browser-session.d.ts.map +1 -0
- package/dist/src/engine/browser-session.js +114 -0
- package/dist/src/engine/browser-session.js.map +1 -0
- package/dist/src/engine/interaction-runner.d.ts +41 -0
- package/dist/src/engine/interaction-runner.d.ts.map +1 -0
- package/dist/src/engine/interaction-runner.js +274 -0
- package/dist/src/engine/interaction-runner.js.map +1 -0
- package/dist/src/engine/route-crawler.d.ts +31 -0
- package/dist/src/engine/route-crawler.d.ts.map +1 -0
- package/dist/src/engine/route-crawler.js +110 -0
- package/dist/src/engine/route-crawler.js.map +1 -0
- package/dist/src/engine/server-lifecycle.d.ts +41 -0
- package/dist/src/engine/server-lifecycle.d.ts.map +1 -0
- package/dist/src/engine/server-lifecycle.js +154 -0
- package/dist/src/engine/server-lifecycle.js.map +1 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +16 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/server.d.ts +111 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +267 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/reporter/agent-diagnostic.d.ts +17 -0
- package/dist/src/reporter/agent-diagnostic.d.ts.map +1 -0
- package/dist/src/reporter/agent-diagnostic.js +80 -0
- package/dist/src/reporter/agent-diagnostic.js.map +1 -0
- package/dist/src/reporter/html-report.d.ts +9 -0
- package/dist/src/reporter/html-report.d.ts.map +1 -0
- package/dist/src/reporter/html-report.js +142 -0
- package/dist/src/reporter/html-report.js.map +1 -0
- package/dist/src/reporter/terminal-summary.d.ts +5 -0
- package/dist/src/reporter/terminal-summary.d.ts.map +1 -0
- package/dist/src/reporter/terminal-summary.js +71 -0
- package/dist/src/reporter/terminal-summary.js.map +1 -0
- package/dist/src/traps/a11y-trap.d.ts +27 -0
- package/dist/src/traps/a11y-trap.d.ts.map +1 -0
- package/dist/src/traps/a11y-trap.js +173 -0
- package/dist/src/traps/a11y-trap.js.map +1 -0
- package/dist/src/traps/console-trap.d.ts +28 -0
- package/dist/src/traps/console-trap.d.ts.map +1 -0
- package/dist/src/traps/console-trap.js +151 -0
- package/dist/src/traps/console-trap.js.map +1 -0
- package/dist/src/traps/layout-trap.d.ts +31 -0
- package/dist/src/traps/layout-trap.d.ts.map +1 -0
- package/dist/src/traps/layout-trap.js +150 -0
- package/dist/src/traps/layout-trap.js.map +1 -0
- package/dist/src/traps/network-trap.d.ts +24 -0
- package/dist/src/traps/network-trap.d.ts.map +1 -0
- package/dist/src/traps/network-trap.js +132 -0
- package/dist/src/traps/network-trap.js.map +1 -0
- package/dist/src/traps/perf-trap.d.ts +42 -0
- package/dist/src/traps/perf-trap.d.ts.map +1 -0
- package/dist/src/traps/perf-trap.js +196 -0
- package/dist/src/traps/perf-trap.js.map +1 -0
- package/dist/src/types.d.ts +95 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +80 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +91 -6
- package/scripts/prepare-build.mjs +40 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import { runGhostQAAudit, GHOST_QA_VERSION } from '../src/engine/audit-runner.js';
|
|
7
|
+
import { ManagedDevServer } from '../src/engine/server-lifecycle.js';
|
|
8
|
+
import { mcpServerConfig, runDoctorCheck, startMcpServer } from '../src/mcp/server.js';
|
|
9
|
+
import { formatAgentDiagnostic, formatDiagnostic } from '../src/reporter/agent-diagnostic.js';
|
|
10
|
+
import { formatHtmlReport } from '../src/reporter/html-report.js';
|
|
11
|
+
import { formatTerminalSummary } from '../src/reporter/terminal-summary.js';
|
|
12
|
+
import { DEFAULT_OPTIONS } from '../src/types.js';
|
|
13
|
+
export const VERSION = GHOST_QA_VERSION;
|
|
14
|
+
const PROTOCOL_START = '<!-- ghost-qa:start -->';
|
|
15
|
+
const PROTOCOL_END = '<!-- ghost-qa:end -->';
|
|
16
|
+
export const AGENT_PROTOCOL = `${PROTOCOL_START}
|
|
17
|
+
## 👻 ghost-qa — Browser Verification Protocol (MANDATORY)
|
|
18
|
+
|
|
19
|
+
Before reporting any task that touches frontend code as complete, you MUST verify it in a real browser:
|
|
20
|
+
|
|
21
|
+
1. Ensure the app is running locally (e.g. \`npm run dev\`).
|
|
22
|
+
2. Run: \`npx github:avivgerz-hue/ghost-qa test <local-url>\` (e.g. \`npx github:avivgerz-hue/ghost-qa test http://localhost:3000\`) — or call the \`ghost_qa_audit_url\` MCP tool if your host exposes it. Add \`--server-cmd "npm run dev"\` to let ghost-qa manage the dev server itself.
|
|
23
|
+
3. If ghost-qa reports ANY critical or major issue (hydration errors, console errors, 4xx/5xx responses, mobile overflow, accessibility or performance failures, broken interactions), fix the root cause in source and re-run.
|
|
24
|
+
4. Repeat until ghost-qa prints **PASS**. Only then may you tell the user the task is done.
|
|
25
|
+
|
|
26
|
+
Never declare a UI task finished based only on code review, type-checking, or unit tests. The browser is the source of truth.
|
|
27
|
+
${PROTOCOL_END}`;
|
|
28
|
+
const FALLBACK_SKILL = `# ghost-qa — Verify in Browser Before Done
|
|
29
|
+
|
|
30
|
+
You are a coding agent. Never report a frontend task complete without running it in a real browser — the browser is the source of truth, not your mental model of the DOM.
|
|
31
|
+
|
|
32
|
+
## The Self-Healing Loop
|
|
33
|
+
|
|
34
|
+
1. Run the audit against the final code:
|
|
35
|
+
- Server already running: \`npx github:avivgerz-hue/ghost-qa test <local-url>\`
|
|
36
|
+
- Otherwise: \`npx github:avivgerz-hue/ghost-qa test <local-url> --server-cmd "npm run dev"\` (ghost-qa spawns, awaits and tears down the server)
|
|
37
|
+
- Or call the \`ghost_qa_audit_url\` MCP tool if your host exposes it (identical engine).
|
|
38
|
+
It crawls routes on Desktop 1440x900 + Mobile 390x844 and runs the 5-Trap Engine: console/hydration, network, layout, accessibility, performance — plus safe interaction testing (never destructive controls).
|
|
39
|
+
2. Read the Self-Healing Bug Report. Fix every critical/major issue by \`#id\`, at the root cause in source — never silence the checker, delete failing UI, or add suppressHydrationWarning.
|
|
40
|
+
3. Re-run the identical command until it prints \`GHOST-QA PASS\`. If stuck on the same \`#id\` after 5 loops, report that honestly instead of declaring success.
|
|
41
|
+
4. Only then report completion, naming the URL verified, routes crawled, interactions tested, and issues fixed.
|
|
42
|
+
|
|
43
|
+
Useful flags: \`--max-depth\`, \`--max-routes\`, \`--viewport mobile\`, \`--json\`, \`--report <file>\`, \`--html <file>\`, \`--fail-on minor\`.
|
|
44
|
+
If Chromium is missing: \`npx playwright install chromium\`, then \`npx github:avivgerz-hue/ghost-qa doctor\`.
|
|
45
|
+
Full skill: https://github.com/avivgerz-hue/ghost-qa/blob/main/SKILL.md
|
|
46
|
+
`;
|
|
47
|
+
const HELP = `
|
|
48
|
+
👻 ghost-qa v${VERSION} — autonomous headless browser QA for coding agents
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
ghost-qa test <url> [options] Crawl a running app and trap browser failures
|
|
52
|
+
ghost-qa init [--force] Install the agent skill, protocol + MCP config
|
|
53
|
+
ghost-qa mcp Run the native MCP server (JSON-RPC over stdio)
|
|
54
|
+
ghost-qa doctor Check Node, Playwright and Chromium setup
|
|
55
|
+
ghost-qa --help | --version
|
|
56
|
+
|
|
57
|
+
test options:
|
|
58
|
+
--max-depth <n> Link-following depth from the start URL (default ${DEFAULT_OPTIONS.maxDepth})
|
|
59
|
+
--max-routes <n> Maximum routes to visit (default ${DEFAULT_OPTIONS.maxRoutes})
|
|
60
|
+
--timeout <ms> Per-navigation timeout (default ${DEFAULT_OPTIONS.timeoutMs})
|
|
61
|
+
--viewport <name> both | desktop | mobile (default both)
|
|
62
|
+
--no-interactions Only load pages; do not click/fill anything
|
|
63
|
+
--headed Show the browser window (debugging)
|
|
64
|
+
--fail-on <severity> Fail on critical | major | minor (default ${DEFAULT_OPTIONS.failOn})
|
|
65
|
+
--server-cmd <cmd> Spawn this dev-server command first, wait for the URL,
|
|
66
|
+
run the audit, then terminate the server tree
|
|
67
|
+
--server-timeout <ms> How long to wait for --server-cmd to respond (default 30000)
|
|
68
|
+
--json Print the machine-readable JSON report to stdout
|
|
69
|
+
--report <file> Write the diagnostic to a file (.md or .json by extension)
|
|
70
|
+
--html <file> Write the visual HTML evidence dashboard to a file
|
|
71
|
+
--quiet Suppress the terminal summary (report/exit code only)
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
npx github:avivgerz-hue/ghost-qa test http://localhost:3000
|
|
75
|
+
npx github:avivgerz-hue/ghost-qa test http://localhost:3000 --server-cmd "npm run dev"
|
|
76
|
+
npx github:avivgerz-hue/ghost-qa test http://localhost:5173 --max-depth 3 --html ghost-qa-report.html
|
|
77
|
+
npx github:avivgerz-hue/ghost-qa test http://localhost:3000 --json > report.json
|
|
78
|
+
npx github:avivgerz-hue/ghost-qa init
|
|
79
|
+
npx github:avivgerz-hue/ghost-qa mcp
|
|
80
|
+
`;
|
|
81
|
+
function parsePositiveInt(value, flag) {
|
|
82
|
+
const parsed = Number.parseInt(value ?? '', 10);
|
|
83
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
84
|
+
throw new Error(`${flag} expects a positive integer, got "${value ?? ''}"`);
|
|
85
|
+
}
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
function parseNonNegativeInt(value, flag) {
|
|
89
|
+
const parsed = Number.parseInt(value ?? '', 10);
|
|
90
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
91
|
+
throw new Error(`${flag} expects a non-negative integer, got "${value ?? ''}"`);
|
|
92
|
+
}
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
export function parseTestArgs(args) {
|
|
96
|
+
const options = { ...DEFAULT_OPTIONS, viewports: [...DEFAULT_OPTIONS.viewports] };
|
|
97
|
+
let target = '';
|
|
98
|
+
let json = false;
|
|
99
|
+
let quiet = false;
|
|
100
|
+
let reportFile;
|
|
101
|
+
let htmlFile;
|
|
102
|
+
let serverCmd;
|
|
103
|
+
let serverTimeoutMs = 30_000;
|
|
104
|
+
for (let i = 0; i < args.length; i++) {
|
|
105
|
+
const arg = args[i];
|
|
106
|
+
switch (arg) {
|
|
107
|
+
case '--max-depth':
|
|
108
|
+
options.maxDepth = parseNonNegativeInt(args[++i], arg);
|
|
109
|
+
break;
|
|
110
|
+
case '--max-routes':
|
|
111
|
+
options.maxRoutes = parsePositiveInt(args[++i], arg);
|
|
112
|
+
break;
|
|
113
|
+
case '--timeout':
|
|
114
|
+
options.timeoutMs = parsePositiveInt(args[++i], arg);
|
|
115
|
+
break;
|
|
116
|
+
case '--viewport': {
|
|
117
|
+
const value = args[++i];
|
|
118
|
+
if (value === 'desktop' || value === 'mobile')
|
|
119
|
+
options.viewports = [value];
|
|
120
|
+
else if (value === 'both')
|
|
121
|
+
options.viewports = ['desktop', 'mobile'];
|
|
122
|
+
else
|
|
123
|
+
throw new Error(`--viewport expects both|desktop|mobile, got "${value ?? ''}"`);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case '--no-interactions':
|
|
127
|
+
options.interactions = false;
|
|
128
|
+
break;
|
|
129
|
+
case '--headed':
|
|
130
|
+
options.headless = false;
|
|
131
|
+
break;
|
|
132
|
+
case '--fail-on': {
|
|
133
|
+
const value = args[++i];
|
|
134
|
+
if (value === 'critical' || value === 'major' || value === 'minor')
|
|
135
|
+
options.failOn = value;
|
|
136
|
+
else
|
|
137
|
+
throw new Error(`--fail-on expects critical|major|minor, got "${value ?? ''}"`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case '--json':
|
|
141
|
+
json = true;
|
|
142
|
+
break;
|
|
143
|
+
case '--quiet':
|
|
144
|
+
quiet = true;
|
|
145
|
+
break;
|
|
146
|
+
case '--report':
|
|
147
|
+
reportFile = args[++i];
|
|
148
|
+
if (!reportFile)
|
|
149
|
+
throw new Error('--report expects a file path');
|
|
150
|
+
break;
|
|
151
|
+
case '--html':
|
|
152
|
+
htmlFile = args[++i];
|
|
153
|
+
if (!htmlFile)
|
|
154
|
+
throw new Error('--html expects a file path');
|
|
155
|
+
break;
|
|
156
|
+
case '--server-cmd':
|
|
157
|
+
serverCmd = args[++i];
|
|
158
|
+
if (!serverCmd)
|
|
159
|
+
throw new Error('--server-cmd expects a command, e.g. --server-cmd "npm run dev"');
|
|
160
|
+
break;
|
|
161
|
+
case '--server-timeout':
|
|
162
|
+
serverTimeoutMs = parsePositiveInt(args[++i], arg);
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
if (arg.startsWith('--'))
|
|
166
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
167
|
+
if (!target)
|
|
168
|
+
target = arg;
|
|
169
|
+
else
|
|
170
|
+
throw new Error(`Unexpected extra argument: ${arg}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!target)
|
|
174
|
+
throw new Error('Missing target URL. Usage: ghost-qa test <url>');
|
|
175
|
+
return {
|
|
176
|
+
target,
|
|
177
|
+
options,
|
|
178
|
+
json,
|
|
179
|
+
quiet,
|
|
180
|
+
serverTimeoutMs,
|
|
181
|
+
...(reportFile ? { reportFile } : {}),
|
|
182
|
+
...(htmlFile ? { htmlFile } : {}),
|
|
183
|
+
...(serverCmd ? { serverCmd } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function executeTest(parsed) {
|
|
187
|
+
const audit = () => runGhostQAAudit(parsed.target, parsed.options);
|
|
188
|
+
if (!parsed.serverCmd)
|
|
189
|
+
return audit();
|
|
190
|
+
const server = new ManagedDevServer(parsed.serverCmd);
|
|
191
|
+
process.stderr.write(`\n👻 ghost-qa: starting dev server: ${parsed.serverCmd}\n`);
|
|
192
|
+
server.start();
|
|
193
|
+
try {
|
|
194
|
+
await server.waitUntilReady(parsed.target, parsed.serverTimeoutMs);
|
|
195
|
+
process.stderr.write(`👻 ghost-qa: server is responding at ${parsed.target}\n`);
|
|
196
|
+
return await audit();
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
await server.stop();
|
|
200
|
+
process.stderr.write('👻 ghost-qa: dev server terminated.\n');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function loadSkillTemplate() {
|
|
204
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
205
|
+
const candidates = [
|
|
206
|
+
resolve(moduleDir, '../../SKILL.md'), // dist/bin -> package root
|
|
207
|
+
resolve(moduleDir, '../SKILL.md'),
|
|
208
|
+
resolve(process.cwd(), 'SKILL.md'),
|
|
209
|
+
];
|
|
210
|
+
for (const candidate of candidates) {
|
|
211
|
+
try {
|
|
212
|
+
const content = await readFile(candidate, 'utf8');
|
|
213
|
+
if (content.includes('ghost-qa'))
|
|
214
|
+
return content;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
/* try next candidate */
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return FALLBACK_SKILL;
|
|
221
|
+
}
|
|
222
|
+
async function upsertProtocol(filePath) {
|
|
223
|
+
let existing = '';
|
|
224
|
+
try {
|
|
225
|
+
existing = await readFile(filePath, 'utf8');
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
await writeFile(filePath, `# Agent Instructions\n\n${AGENT_PROTOCOL}\n`, 'utf8');
|
|
229
|
+
return 'created';
|
|
230
|
+
}
|
|
231
|
+
const start = existing.indexOf(PROTOCOL_START);
|
|
232
|
+
const end = existing.indexOf(PROTOCOL_END);
|
|
233
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
234
|
+
const next = `${existing.slice(0, start)}${AGENT_PROTOCOL}${existing.slice(end + PROTOCOL_END.length)}`;
|
|
235
|
+
if (next === existing)
|
|
236
|
+
return 'unchanged';
|
|
237
|
+
await writeFile(filePath, next, 'utf8');
|
|
238
|
+
return 'updated';
|
|
239
|
+
}
|
|
240
|
+
await writeFile(filePath, `${existing.replace(/\s*$/, '')}\n\n${AGENT_PROTOCOL}\n`, 'utf8');
|
|
241
|
+
return 'updated';
|
|
242
|
+
}
|
|
243
|
+
export async function installSkill(targetDir, force = false) {
|
|
244
|
+
const results = [];
|
|
245
|
+
const skillPath = join(targetDir, '.claude', 'skills', 'ghost-qa', 'SKILL.md');
|
|
246
|
+
await mkdir(dirname(skillPath), { recursive: true });
|
|
247
|
+
const skillContent = await loadSkillTemplate();
|
|
248
|
+
const skillExisted = existsSync(skillPath);
|
|
249
|
+
if (!skillExisted || force) {
|
|
250
|
+
await writeFile(skillPath, skillContent.endsWith('\n') ? skillContent : `${skillContent}\n`, 'utf8');
|
|
251
|
+
results.push({ file: '.claude/skills/ghost-qa/SKILL.md', action: skillExisted ? 'updated (--force)' : 'installed' });
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
results.push({ file: '.claude/skills/ghost-qa/SKILL.md', action: 'already exists (use --force to overwrite)' });
|
|
255
|
+
}
|
|
256
|
+
for (const name of ['CLAUDE.md', 'AGENTS.md', '.cursorrules']) {
|
|
257
|
+
const action = await upsertProtocol(join(targetDir, name));
|
|
258
|
+
results.push({ file: name, action: `protocol ${action}` });
|
|
259
|
+
}
|
|
260
|
+
const mcpAction = await installMcpConfig(join(targetDir, '.mcp.json'), force);
|
|
261
|
+
results.push({ file: '.mcp.json', action: mcpAction });
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Writes (or merges into) `.mcp.json`, preserving any MCP servers the
|
|
266
|
+
* project already configured. The ghost-qa entry is always refreshed so
|
|
267
|
+
* hosts invoke the published CLI: `npx -y github:avivgerz-hue/ghost-qa mcp`.
|
|
268
|
+
*/
|
|
269
|
+
export async function installMcpConfig(mcpJsonPath, force = false) {
|
|
270
|
+
const desired = mcpServerConfig();
|
|
271
|
+
let existing = {};
|
|
272
|
+
let action = 'installed';
|
|
273
|
+
try {
|
|
274
|
+
const raw = await readFile(mcpJsonPath, 'utf8');
|
|
275
|
+
const parsed = JSON.parse(raw);
|
|
276
|
+
if (parsed && typeof parsed === 'object') {
|
|
277
|
+
existing = parsed;
|
|
278
|
+
action = 'merged';
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
action = 'installed';
|
|
283
|
+
}
|
|
284
|
+
const servers = (existing.mcpServers ?? {});
|
|
285
|
+
const previous = servers['ghost-qa'];
|
|
286
|
+
if (previous && !force && JSON.stringify(previous) === JSON.stringify(desired.mcpServers['ghost-qa'])) {
|
|
287
|
+
return 'already configured';
|
|
288
|
+
}
|
|
289
|
+
servers['ghost-qa'] = desired.mcpServers['ghost-qa'];
|
|
290
|
+
existing.mcpServers = servers;
|
|
291
|
+
await writeFile(mcpJsonPath, `${JSON.stringify(existing, null, 2)}\n`, 'utf8');
|
|
292
|
+
return previous ? 'updated' : action;
|
|
293
|
+
}
|
|
294
|
+
async function runInit(args) {
|
|
295
|
+
const force = args.includes('--force');
|
|
296
|
+
const dirFlagIndex = args.indexOf('--dir');
|
|
297
|
+
const targetDir = dirFlagIndex !== -1 ? resolve(args[dirFlagIndex + 1] ?? '.') : process.cwd();
|
|
298
|
+
const results = await installSkill(targetDir, force);
|
|
299
|
+
process.stdout.write(`\n👻 ghost-qa agent skill installed in ${targetDir}\n`);
|
|
300
|
+
for (const result of results) {
|
|
301
|
+
process.stdout.write(` • ${result.file} — ${result.action}\n`);
|
|
302
|
+
}
|
|
303
|
+
process.stdout.write('\nYour coding agent will now run `npx github:avivgerz-hue/ghost-qa test <local-url>` and fix browser failures before reporting a task complete.\n' +
|
|
304
|
+
'MCP hosts (Claude Code, Claude Desktop, Cursor, Codex) can also call the ghost_qa_audit_url / ghost_qa_doctor tools directly via the .mcp.json entry above.\n');
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
async function runDoctor() {
|
|
308
|
+
process.stdout.write(`\n👻 ghost-qa doctor v${VERSION}\n`);
|
|
309
|
+
const result = await runDoctorCheck();
|
|
310
|
+
process.stdout.write(` ${result.nodeOk ? '✔' : '✖'} Node.js ${result.node} ${result.nodeOk ? '' : '(requires >= 20)'}\n`);
|
|
311
|
+
if (result.chromiumFound) {
|
|
312
|
+
process.stdout.write(` ✔ Chromium binary: ${result.chromiumPath}\n`);
|
|
313
|
+
const smokeOk = result.smokeTest.startsWith('Chromium launched');
|
|
314
|
+
process.stdout.write(` ${smokeOk ? '✔' : '✖'} Browser smoke test: ${result.smokeTest}\n`);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
process.stdout.write(' ✖ Chromium binary not found. Run: npx playwright install chromium\n');
|
|
318
|
+
}
|
|
319
|
+
process.stdout.write(result.ready
|
|
320
|
+
? '\n✔ ghost-qa is ready: npx github:avivgerz-hue/ghost-qa test http://localhost:3000\n'
|
|
321
|
+
: '\n✖ Setup incomplete — fix the items above.\n');
|
|
322
|
+
return result.ready ? 0 : 1;
|
|
323
|
+
}
|
|
324
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
325
|
+
const [command, ...rest] = argv;
|
|
326
|
+
try {
|
|
327
|
+
switch (command) {
|
|
328
|
+
case 'test': {
|
|
329
|
+
const parsed = parseTestArgs(rest);
|
|
330
|
+
const report = await executeTest(parsed);
|
|
331
|
+
if (parsed.reportFile) {
|
|
332
|
+
const isJson = parsed.reportFile.endsWith('.json');
|
|
333
|
+
await writeFile(resolve(parsed.reportFile), formatDiagnostic(report, isJson ? 'json' : 'markdown'), 'utf8');
|
|
334
|
+
}
|
|
335
|
+
if (parsed.htmlFile) {
|
|
336
|
+
await writeFile(resolve(parsed.htmlFile), formatHtmlReport(report), 'utf8');
|
|
337
|
+
}
|
|
338
|
+
if (parsed.json) {
|
|
339
|
+
process.stdout.write(formatDiagnostic(report, 'json'));
|
|
340
|
+
}
|
|
341
|
+
else if (!parsed.quiet) {
|
|
342
|
+
process.stdout.write(`${formatTerminalSummary(report)}\n`);
|
|
343
|
+
if (!report.passed) {
|
|
344
|
+
process.stdout.write(`\n${formatAgentDiagnostic(report)}\n`);
|
|
345
|
+
}
|
|
346
|
+
if (parsed.reportFile)
|
|
347
|
+
process.stdout.write(`Report written to ${parsed.reportFile}\n`);
|
|
348
|
+
if (parsed.htmlFile)
|
|
349
|
+
process.stdout.write(`HTML evidence report written to ${parsed.htmlFile}\n`);
|
|
350
|
+
}
|
|
351
|
+
return report.passed ? 0 : 1;
|
|
352
|
+
}
|
|
353
|
+
case 'mcp':
|
|
354
|
+
await startMcpServer();
|
|
355
|
+
return 0;
|
|
356
|
+
case 'init':
|
|
357
|
+
return await runInit(rest);
|
|
358
|
+
case 'doctor':
|
|
359
|
+
return await runDoctor();
|
|
360
|
+
case '--version':
|
|
361
|
+
case '-v':
|
|
362
|
+
case 'version':
|
|
363
|
+
process.stdout.write(`ghost-qa ${VERSION}\n`);
|
|
364
|
+
return 0;
|
|
365
|
+
case '--help':
|
|
366
|
+
case '-h':
|
|
367
|
+
case 'help':
|
|
368
|
+
case undefined:
|
|
369
|
+
process.stdout.write(`${HELP}\n`);
|
|
370
|
+
return 0;
|
|
371
|
+
default:
|
|
372
|
+
process.stderr.write(`Unknown command: ${command}\n${HELP}\n`);
|
|
373
|
+
return 2;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
process.stderr.write(`\n👻 ghost-qa error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
378
|
+
return 2;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* True when this module is the process entry point. The entry path MUST be
|
|
383
|
+
* realpath-resolved first: package managers invoke bins through a
|
|
384
|
+
* `node_modules/.bin/ghost-qa` symlink, while `import.meta.url` always
|
|
385
|
+
* points at the real file — comparing unresolved paths would make the CLI
|
|
386
|
+
* silently no-op (exit 0, no output) under `npx`.
|
|
387
|
+
*/
|
|
388
|
+
export function isMainEntry(argvEntry, moduleUrl = import.meta.url) {
|
|
389
|
+
if (!argvEntry)
|
|
390
|
+
return false;
|
|
391
|
+
try {
|
|
392
|
+
return moduleUrl === pathToFileURL(realpathSync(resolve(argvEntry))).href;
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (isMainEntry(process.argv[1])) {
|
|
399
|
+
main().then((code) => {
|
|
400
|
+
process.exitCode = code;
|
|
401
|
+
}, (error) => {
|
|
402
|
+
process.stderr.write(`ghost-qa crashed: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
403
|
+
process.exitCode = 2;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=ghost-qa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ghost-qa.js","sourceRoot":"","sources":["../../bin/ghost-qa.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type GhostQAOptions, type GhostQAReport } from '../types.js';
|
|
2
|
+
export declare const GHOST_QA_VERSION = "1.3.0";
|
|
3
|
+
/**
|
|
4
|
+
* The full ghost-qa audit: crawl + all five traps (console, network, layout,
|
|
5
|
+
* accessibility, performance) + safe interactions, on every configured
|
|
6
|
+
* viewport. Shared verbatim by the CLI (`ghost-qa test`), the MCP server
|
|
7
|
+
* (`ghost_qa_audit_url`) and library consumers.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runGhostQAAudit(target: string, options: GhostQAOptions): Promise<GhostQAReport>;
|
|
10
|
+
//# sourceMappingURL=audit-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-runner.d.ts","sourceRoot":"","sources":["../../../src/engine/audit-runner.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,aAAa,EAInB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA8HxB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { BrowserSession } from './browser-session.js';
|
|
2
|
+
import { InteractionRunner } from './interaction-runner.js';
|
|
3
|
+
import { RouteCrawler } from './route-crawler.js';
|
|
4
|
+
import { ConsoleTrap } from '../traps/console-trap.js';
|
|
5
|
+
import { LayoutTrap } from '../traps/layout-trap.js';
|
|
6
|
+
import { NetworkTrap } from '../traps/network-trap.js';
|
|
7
|
+
import { A11yTrap } from '../traps/a11y-trap.js';
|
|
8
|
+
import { PerfTrap } from '../traps/perf-trap.js';
|
|
9
|
+
import { dedupeIssues, makeIssue, summarise, } from '../types.js';
|
|
10
|
+
export const GHOST_QA_VERSION = '1.3.0';
|
|
11
|
+
/**
|
|
12
|
+
* The full ghost-qa audit: crawl + all five traps (console, network, layout,
|
|
13
|
+
* accessibility, performance) + safe interactions, on every configured
|
|
14
|
+
* viewport. Shared verbatim by the CLI (`ghost-qa test`), the MCP server
|
|
15
|
+
* (`ghost_qa_audit_url`) and library consumers.
|
|
16
|
+
*/
|
|
17
|
+
export async function runGhostQAAudit(target, options) {
|
|
18
|
+
const startedAt = new Date();
|
|
19
|
+
const url = new URL(target);
|
|
20
|
+
if (!/^https?:$/.test(url.protocol)) {
|
|
21
|
+
throw new Error(`Target must be an http(s) URL, got: ${target}`);
|
|
22
|
+
}
|
|
23
|
+
const session = new BrowserSession(options.headless);
|
|
24
|
+
const interactions = [];
|
|
25
|
+
const rawIssues = [];
|
|
26
|
+
const records = new Map();
|
|
27
|
+
const crawler = new RouteCrawler(options.maxDepth, options.maxRoutes);
|
|
28
|
+
const runner = new InteractionRunner(options.maxInteractionsPerPage, 250);
|
|
29
|
+
const visit = async (route) => {
|
|
30
|
+
const discovered = new Set();
|
|
31
|
+
const record = {
|
|
32
|
+
url: route.url,
|
|
33
|
+
path: route.path,
|
|
34
|
+
depth: route.depth,
|
|
35
|
+
title: '',
|
|
36
|
+
viewports: [],
|
|
37
|
+
issueCount: 0,
|
|
38
|
+
};
|
|
39
|
+
records.set(route.url, record);
|
|
40
|
+
for (const viewport of options.viewports) {
|
|
41
|
+
record.viewports.push(viewport);
|
|
42
|
+
const page = await session.newPage(viewport);
|
|
43
|
+
const consoleTrap = new ConsoleTrap(viewport);
|
|
44
|
+
const networkTrap = new NetworkTrap(viewport);
|
|
45
|
+
const layoutTrap = new LayoutTrap(viewport);
|
|
46
|
+
const a11yTrap = new A11yTrap(viewport);
|
|
47
|
+
const perfTrap = new PerfTrap(viewport);
|
|
48
|
+
consoleTrap.setRoute(route.path);
|
|
49
|
+
networkTrap.setRoute(route.path);
|
|
50
|
+
consoleTrap.attach(page);
|
|
51
|
+
networkTrap.attach(page);
|
|
52
|
+
try {
|
|
53
|
+
const response = await page.goto(route.url, {
|
|
54
|
+
waitUntil: 'domcontentloaded',
|
|
55
|
+
timeout: options.timeoutMs,
|
|
56
|
+
});
|
|
57
|
+
if (response && viewport === options.viewports[0]) {
|
|
58
|
+
record.httpStatus = response.status();
|
|
59
|
+
}
|
|
60
|
+
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => undefined);
|
|
61
|
+
await page.waitForTimeout(options.settleMs);
|
|
62
|
+
if (!record.title)
|
|
63
|
+
record.title = await page.title().catch(() => '');
|
|
64
|
+
rawIssues.push(...(await layoutTrap.collect(page, route.path)));
|
|
65
|
+
rawIssues.push(...(await a11yTrap.collect(page, route.path)));
|
|
66
|
+
rawIssues.push(...(await perfTrap.collect(page, route.path)));
|
|
67
|
+
for (const href of await RouteCrawler.extractLinks(page))
|
|
68
|
+
discovered.add(href);
|
|
69
|
+
if (options.interactions) {
|
|
70
|
+
const result = await runner.run(page, route.path, viewport);
|
|
71
|
+
interactions.push(...result.actions);
|
|
72
|
+
rawIssues.push(...result.issues);
|
|
73
|
+
for (const href of result.discoveredHrefs)
|
|
74
|
+
discovered.add(href);
|
|
75
|
+
// Interactions may have surfaced fresh console/network events and
|
|
76
|
+
// changed layout/perf characteristics of the page.
|
|
77
|
+
rawIssues.push(...(await layoutTrap.collect(page, route.path)));
|
|
78
|
+
rawIssues.push(...(await perfTrap.collect(page, route.path)));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
rawIssues.push(makeIssue({
|
|
84
|
+
category: 'navigation',
|
|
85
|
+
severity: 'critical',
|
|
86
|
+
type: 'navigation-failed',
|
|
87
|
+
message: `Failed to load ${route.path} on ${viewport}: ${message.replace(/\s+/g, ' ').slice(0, 260)}. Is the dev server running at ${url.origin}?`,
|
|
88
|
+
route: route.path,
|
|
89
|
+
viewport,
|
|
90
|
+
url: route.url,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
rawIssues.push(...consoleTrap.issues, ...networkTrap.issues);
|
|
95
|
+
consoleTrap.detach();
|
|
96
|
+
networkTrap.detach();
|
|
97
|
+
await page.close().catch(() => undefined);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return [...discovered];
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
await crawler.crawl(url.toString(), visit);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await session.close();
|
|
107
|
+
}
|
|
108
|
+
const finishedAt = new Date();
|
|
109
|
+
const issues = dedupeIssues(rawIssues);
|
|
110
|
+
const routes = [...records.values()];
|
|
111
|
+
for (const route of routes) {
|
|
112
|
+
route.issueCount = issues.filter((i) => i.route === route.path).length;
|
|
113
|
+
}
|
|
114
|
+
const summary = summarise(routes, interactions, issues, options.failOn);
|
|
115
|
+
return {
|
|
116
|
+
tool: 'ghost-qa',
|
|
117
|
+
version: GHOST_QA_VERSION,
|
|
118
|
+
targetUrl: url.toString(),
|
|
119
|
+
startedAt: startedAt.toISOString(),
|
|
120
|
+
finishedAt: finishedAt.toISOString(),
|
|
121
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
122
|
+
passed: summary.passed,
|
|
123
|
+
options,
|
|
124
|
+
routes,
|
|
125
|
+
interactions,
|
|
126
|
+
issues,
|
|
127
|
+
summary: {
|
|
128
|
+
routesCrawled: summary.routesCrawled,
|
|
129
|
+
interactionsTested: summary.interactionsTested,
|
|
130
|
+
interactionsSkipped: summary.interactionsSkipped,
|
|
131
|
+
interactionsFailed: summary.interactionsFailed,
|
|
132
|
+
totalIssues: summary.totalIssues,
|
|
133
|
+
bySeverity: summary.bySeverity,
|
|
134
|
+
byCategory: summary.byCategory,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=audit-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-runner.js","sourceRoot":"","sources":["../../../src/engine/audit-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type BrowserContext, type Page } from 'playwright';
|
|
2
|
+
import type { ViewportName } from '../types.js';
|
|
3
|
+
export interface ViewportProfile {
|
|
4
|
+
name: ViewportName;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
isMobile: boolean;
|
|
8
|
+
hasTouch: boolean;
|
|
9
|
+
deviceScaleFactor: number;
|
|
10
|
+
userAgent?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const VIEWPORT_PROFILES: Record<ViewportName, ViewportProfile>;
|
|
13
|
+
export declare class BrowserNotInstalledError extends Error {
|
|
14
|
+
constructor(cause?: unknown);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Owns the headless Chromium lifecycle: one browser, one isolated context per
|
|
18
|
+
* viewport profile, pages created on demand. Always `close()` (or use
|
|
19
|
+
* `await using`-style try/finally) — leaked browsers hang CI.
|
|
20
|
+
*/
|
|
21
|
+
export declare class BrowserSession {
|
|
22
|
+
private browser;
|
|
23
|
+
private readonly contexts;
|
|
24
|
+
private readonly headless;
|
|
25
|
+
constructor(headless?: boolean);
|
|
26
|
+
launch(): Promise<void>;
|
|
27
|
+
get isLaunched(): boolean;
|
|
28
|
+
contextFor(viewport: ViewportName): Promise<BrowserContext>;
|
|
29
|
+
newPage(viewport: ViewportName): Promise<Page>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/** Lightweight launch smoke-test used by `ghost-qa doctor`. */
|
|
33
|
+
export declare function smokeTestBrowser(): Promise<{
|
|
34
|
+
ok: boolean;
|
|
35
|
+
detail: string;
|
|
36
|
+
}>;
|
|
37
|
+
//# sourceMappingURL=browser-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-session.d.ts","sourceRoot":"","sources":["../../../src/engine/browser-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,YAAY,EAAE,eAAe,CAmBnE,CAAC;AAEF,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,KAAK,CAAC,EAAE,OAAO;CAQ5B;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;gBAEvB,QAAQ,UAAO;IAIrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB7B,IAAI,UAAU,IAAI,OAAO,CAExB;IAEK,UAAU,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IAiB3D,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAU7B;AAED,+DAA+D;AAC/D,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBjF"}
|