ghost-qa 1.3.2 → 2.0.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/README.md +77 -24
- package/SKILL.md +78 -77
- package/dist/bin/ghost-qa.d.ts +10 -2
- package/dist/bin/ghost-qa.d.ts.map +1 -1
- package/dist/bin/ghost-qa.js +106 -16
- package/dist/bin/ghost-qa.js.map +1 -1
- package/dist/src/engine/audit-runner.d.ts +6 -5
- package/dist/src/engine/audit-runner.d.ts.map +1 -1
- package/dist/src/engine/audit-runner.js +117 -60
- package/dist/src/engine/audit-runner.js.map +1 -1
- package/dist/src/engine/browser-session.d.ts +3 -3
- package/dist/src/engine/browser-session.d.ts.map +1 -1
- package/dist/src/engine/browser-session.js +18 -7
- package/dist/src/engine/browser-session.js.map +1 -1
- package/dist/src/engine/compare.d.ts +37 -0
- package/dist/src/engine/compare.d.ts.map +1 -0
- package/dist/src/engine/compare.js +63 -0
- package/dist/src/engine/compare.js.map +1 -0
- package/dist/src/engine/health-score.d.ts +14 -0
- package/dist/src/engine/health-score.d.ts.map +1 -0
- package/dist/src/engine/health-score.js +74 -0
- package/dist/src/engine/health-score.js.map +1 -0
- package/dist/src/engine/version.d.ts +14 -0
- package/dist/src/engine/version.d.ts.map +1 -0
- package/dist/src/engine/version.js +43 -0
- package/dist/src/engine/version.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/server.d.ts +24 -4
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +43 -5
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/reporter/agent-diagnostic.d.ts +6 -0
- package/dist/src/reporter/agent-diagnostic.d.ts.map +1 -1
- package/dist/src/reporter/agent-diagnostic.js +52 -2
- package/dist/src/reporter/agent-diagnostic.js.map +1 -1
- package/dist/src/reporter/html-report.d.ts.map +1 -1
- package/dist/src/reporter/html-report.js +79 -4
- package/dist/src/reporter/html-report.js.map +1 -1
- package/dist/src/reporter/terminal-summary.d.ts.map +1 -1
- package/dist/src/reporter/terminal-summary.js +6 -0
- package/dist/src/reporter/terminal-summary.js.map +1 -1
- package/dist/src/traps/a11y-trap.d.ts +23 -0
- package/dist/src/traps/a11y-trap.d.ts.map +1 -1
- package/dist/src/traps/a11y-trap.js +185 -0
- package/dist/src/traps/a11y-trap.js.map +1 -1
- package/dist/src/traps/console-trap.d.ts +3 -1
- package/dist/src/traps/console-trap.d.ts.map +1 -1
- package/dist/src/traps/console-trap.js +41 -0
- package/dist/src/traps/console-trap.js.map +1 -1
- package/dist/src/traps/layout-trap.d.ts +31 -0
- package/dist/src/traps/layout-trap.d.ts.map +1 -1
- package/dist/src/traps/layout-trap.js +173 -0
- package/dist/src/traps/layout-trap.js.map +1 -1
- package/dist/src/traps/links-trap.d.ts +34 -0
- package/dist/src/traps/links-trap.d.ts.map +1 -0
- package/dist/src/traps/links-trap.js +136 -0
- package/dist/src/traps/links-trap.js.map +1 -0
- package/dist/src/traps/network-trap.d.ts +4 -1
- package/dist/src/traps/network-trap.d.ts.map +1 -1
- package/dist/src/traps/network-trap.js +29 -2
- package/dist/src/traps/network-trap.js.map +1 -1
- package/dist/src/traps/perf-trap.d.ts +18 -0
- package/dist/src/traps/perf-trap.d.ts.map +1 -1
- package/dist/src/traps/perf-trap.js +148 -8
- package/dist/src/traps/perf-trap.js.map +1 -1
- package/dist/src/traps/seo-trap.d.ts +30 -0
- package/dist/src/traps/seo-trap.d.ts.map +1 -0
- package/dist/src/traps/seo-trap.js +98 -0
- package/dist/src/traps/seo-trap.js.map +1 -0
- package/dist/src/traps/theme-trap.d.ts +32 -0
- package/dist/src/traps/theme-trap.d.ts.map +1 -0
- package/dist/src/traps/theme-trap.js +109 -0
- package/dist/src/traps/theme-trap.js.map +1 -0
- package/dist/src/types.d.ts +55 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +30 -2
- package/dist/src/types.js.map +1 -1
- package/package.json +11 -3
|
@@ -8,10 +8,19 @@ export const VIEWPORT_PROFILES = {
|
|
|
8
8
|
hasTouch: false,
|
|
9
9
|
deviceScaleFactor: 1,
|
|
10
10
|
},
|
|
11
|
+
tablet: {
|
|
12
|
+
name: 'tablet',
|
|
13
|
+
width: 768,
|
|
14
|
+
height: 1024,
|
|
15
|
+
isMobile: false,
|
|
16
|
+
hasTouch: true,
|
|
17
|
+
deviceScaleFactor: 2,
|
|
18
|
+
userAgent: 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
|
|
19
|
+
},
|
|
11
20
|
mobile: {
|
|
12
21
|
name: 'mobile',
|
|
13
|
-
width:
|
|
14
|
-
height:
|
|
22
|
+
width: 375,
|
|
23
|
+
height: 812,
|
|
15
24
|
isMobile: true,
|
|
16
25
|
hasTouch: true,
|
|
17
26
|
deviceScaleFactor: 3,
|
|
@@ -58,10 +67,11 @@ export class BrowserSession {
|
|
|
58
67
|
get isLaunched() {
|
|
59
68
|
return this.browser !== null;
|
|
60
69
|
}
|
|
61
|
-
async contextFor(viewport) {
|
|
70
|
+
async contextFor(viewport, colorScheme = 'light') {
|
|
62
71
|
if (!this.browser)
|
|
63
72
|
await this.launch();
|
|
64
|
-
const
|
|
73
|
+
const key = `${viewport}:${colorScheme}`;
|
|
74
|
+
const existing = this.contexts.get(key);
|
|
65
75
|
if (existing)
|
|
66
76
|
return existing;
|
|
67
77
|
const profile = VIEWPORT_PROFILES[viewport];
|
|
@@ -70,13 +80,14 @@ export class BrowserSession {
|
|
|
70
80
|
isMobile: profile.isMobile,
|
|
71
81
|
hasTouch: profile.hasTouch,
|
|
72
82
|
deviceScaleFactor: profile.deviceScaleFactor,
|
|
83
|
+
colorScheme,
|
|
73
84
|
...(profile.userAgent ? { userAgent: profile.userAgent } : {}),
|
|
74
85
|
});
|
|
75
|
-
this.contexts.set(
|
|
86
|
+
this.contexts.set(key, context);
|
|
76
87
|
return context;
|
|
77
88
|
}
|
|
78
|
-
async newPage(viewport) {
|
|
79
|
-
const context = await this.contextFor(viewport);
|
|
89
|
+
async newPage(viewport, colorScheme = 'light') {
|
|
90
|
+
const context = await this.contextFor(viewport, colorScheme);
|
|
80
91
|
return context.newPage();
|
|
81
92
|
}
|
|
82
93
|
async close() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-session.js","sourceRoot":"","sources":["../../../src/engine/browser-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAgD,MAAM,YAAY,CAAC;AAapF,MAAM,CAAC,MAAM,iBAAiB,GAA0C;IACtE,OAAO,EAAE;QACP,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,GAAG;QACX,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,KAAK;QACf,iBAAiB,EAAE,CAAC;KACrB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,GAAG;QACX,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,iBAAiB,EAAE,CAAC;QACpB,SAAS,EACP,yIAAyI;KAC5I;CACF,CAAC;AAEF,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,KAAe;QACzB,KAAK,CACH,kFAAkF;YAChF,oDAAoD;YACpD,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,qBAAqB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACvE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACjB,OAAO,GAAmB,IAAI,CAAC;IACtB,QAAQ,GAAG,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"browser-session.js","sourceRoot":"","sources":["../../../src/engine/browser-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GhostQAReport, QAIssue } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Regression & Verification Delta engine (`ghost-qa compare` /
|
|
4
|
+
* `ghost_qa_compare`).
|
|
5
|
+
*
|
|
6
|
+
* Findings are matched by their stable fingerprint id, so the same bug in
|
|
7
|
+
* the same route/viewport/scheme lines up across runs even though issue
|
|
8
|
+
* ordering and timestamps change. A finding present afterwards but absent
|
|
9
|
+
* before is a NEW REGRESSION; present before but gone after is FIXED.
|
|
10
|
+
*/
|
|
11
|
+
export interface CompareDelta {
|
|
12
|
+
tool: 'ghost-qa-compare';
|
|
13
|
+
beforeScore: number;
|
|
14
|
+
afterScore: number;
|
|
15
|
+
/** afterScore − beforeScore (positive means the app got healthier). */
|
|
16
|
+
scoreDelta: number;
|
|
17
|
+
beforeGrade: string;
|
|
18
|
+
afterGrade: string;
|
|
19
|
+
fixedFindings: QAIssue[];
|
|
20
|
+
newRegressions: QAIssue[];
|
|
21
|
+
remainingFindings: QAIssue[];
|
|
22
|
+
counts: {
|
|
23
|
+
before: number;
|
|
24
|
+
after: number;
|
|
25
|
+
fixed: number;
|
|
26
|
+
newRegressions: number;
|
|
27
|
+
remaining: number;
|
|
28
|
+
};
|
|
29
|
+
/** True when the fix loop may stop: no regressions, nothing blocking left. */
|
|
30
|
+
verified: boolean;
|
|
31
|
+
}
|
|
32
|
+
type ReportLike = Pick<GhostQAReport, 'issues'> & Partial<Pick<GhostQAReport, 'health' | 'targetUrl' | 'version'>>;
|
|
33
|
+
export declare function compareReports(before: ReportLike, after: ReportLike): CompareDelta;
|
|
34
|
+
/** Human/agent-readable Regression & Verification Delta Report. */
|
|
35
|
+
export declare function formatCompareReport(delta: CompareDelta): string;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../src/engine/compare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG1D;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,kBAAkB,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,EAAE,CAAC;IACzB,cAAc,EAAE,OAAO,EAAE,CAAC;IAC1B,iBAAiB,EAAE,OAAO,EAAE,CAAC;IAC7B,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,GAC7C,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC;AAQnE,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,YAAY,CAmClF;AAKD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CA0B/D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { computeHealthScore } from './health-score.js';
|
|
2
|
+
function scoreOf(report) {
|
|
3
|
+
if (report.health)
|
|
4
|
+
return { score: report.health.score, grade: report.health.grade };
|
|
5
|
+
const computed = computeHealthScore(report.issues ?? []);
|
|
6
|
+
return { score: computed.score, grade: computed.grade };
|
|
7
|
+
}
|
|
8
|
+
export function compareReports(before, after) {
|
|
9
|
+
const beforeIssues = before.issues ?? [];
|
|
10
|
+
const afterIssues = after.issues ?? [];
|
|
11
|
+
const beforeById = new Map(beforeIssues.map((i) => [i.id, i]));
|
|
12
|
+
const afterById = new Map(afterIssues.map((i) => [i.id, i]));
|
|
13
|
+
const fixedFindings = beforeIssues.filter((i) => !afterById.has(i.id));
|
|
14
|
+
const newRegressions = afterIssues.filter((i) => !beforeById.has(i.id));
|
|
15
|
+
const remainingFindings = afterIssues.filter((i) => beforeById.has(i.id));
|
|
16
|
+
const b = scoreOf(before);
|
|
17
|
+
const a = scoreOf(after);
|
|
18
|
+
const blockingRemaining = remainingFindings.some((i) => i.severity === 'critical' || i.severity === 'major');
|
|
19
|
+
return {
|
|
20
|
+
tool: 'ghost-qa-compare',
|
|
21
|
+
beforeScore: b.score,
|
|
22
|
+
afterScore: a.score,
|
|
23
|
+
scoreDelta: a.score - b.score,
|
|
24
|
+
beforeGrade: b.grade,
|
|
25
|
+
afterGrade: a.grade,
|
|
26
|
+
fixedFindings,
|
|
27
|
+
newRegressions,
|
|
28
|
+
remainingFindings,
|
|
29
|
+
counts: {
|
|
30
|
+
before: beforeIssues.length,
|
|
31
|
+
after: afterIssues.length,
|
|
32
|
+
fixed: fixedFindings.length,
|
|
33
|
+
newRegressions: newRegressions.length,
|
|
34
|
+
remaining: remainingFindings.length,
|
|
35
|
+
},
|
|
36
|
+
verified: newRegressions.length === 0 && !blockingRemaining && a.score >= 95,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const fmtIssue = (issue) => `- [#${issue.id}] (${issue.severity}/${issue.category} · ${issue.route} · ${issue.viewport}${issue.colorScheme ? ` · ${issue.colorScheme}` : ''}) ${issue.message}`;
|
|
40
|
+
/** Human/agent-readable Regression & Verification Delta Report. */
|
|
41
|
+
export function formatCompareReport(delta) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
lines.push('# 👻 ghost-qa — Regression & Verification Delta Report');
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push(`**QA Health Score:** ${delta.beforeScore} (${delta.beforeGrade}) → ${delta.afterScore} (${delta.afterGrade}) ` +
|
|
46
|
+
`(${delta.scoreDelta >= 0 ? '+' : ''}${delta.scoreDelta})`);
|
|
47
|
+
lines.push(`**Findings:** ${delta.counts.before} before · ${delta.counts.after} after · ` +
|
|
48
|
+
`${delta.counts.fixed} fixed · ${delta.counts.newRegressions} new regressions · ${delta.counts.remaining} remaining`);
|
|
49
|
+
lines.push(`**Verdict:** ${delta.verified ? '✅ VERIFIED — score ≥ 95 (A+ band), zero regressions, no blocking findings remain' : '❌ NOT YET VERIFIED — keep fixing (goal: score 95–100, zero regressions, zero critical/major remaining)'}`);
|
|
50
|
+
const section = (title, issues) => {
|
|
51
|
+
lines.push('');
|
|
52
|
+
lines.push(`## ${title} (${issues.length})`);
|
|
53
|
+
if (issues.length === 0)
|
|
54
|
+
lines.push('- none');
|
|
55
|
+
for (const issue of issues)
|
|
56
|
+
lines.push(fmtIssue(issue));
|
|
57
|
+
};
|
|
58
|
+
section('Fixed findings', delta.fixedFindings);
|
|
59
|
+
section('New regressions', delta.newRegressions);
|
|
60
|
+
section('Remaining findings', delta.remainingFindings);
|
|
61
|
+
return `${lines.join('\n')}\n`;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/engine/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type HealthScore, type IssueSeverity, type QAIssue, type TrapName } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic QA Health Score.
|
|
4
|
+
*
|
|
5
|
+
* Each of the eight traps starts at 100 and loses points per finding —
|
|
6
|
+
* critical −25, major −10, minor −3 — floored at 0. The overall score is
|
|
7
|
+
* the rounded mean of the eight trap scores, so the same DOM always earns
|
|
8
|
+
* the same grade (no clocks, no randomness, no machine-dependent input).
|
|
9
|
+
*/
|
|
10
|
+
export declare const SCORE_DEDUCTIONS: Record<IssueSeverity, number>;
|
|
11
|
+
export declare const ALL_TRAPS: TrapName[];
|
|
12
|
+
export declare function gradeForScore(score: number): string;
|
|
13
|
+
export declare function computeHealthScore(issues: QAIssue[]): HealthScore;
|
|
14
|
+
//# sourceMappingURL=health-score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-score.d.ts","sourceRoot":"","sources":["../../../src/engine/health-score.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,QAAQ,EAEd,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAI1D,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,QAAQ,EAS/B,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAqBjE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CATEGORY_TO_TRAP, TRAP_LABELS, } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic QA Health Score.
|
|
4
|
+
*
|
|
5
|
+
* Each of the eight traps starts at 100 and loses points per finding —
|
|
6
|
+
* critical −25, major −10, minor −3 — floored at 0. The overall score is
|
|
7
|
+
* the rounded mean of the eight trap scores, so the same DOM always earns
|
|
8
|
+
* the same grade (no clocks, no randomness, no machine-dependent input).
|
|
9
|
+
*/
|
|
10
|
+
export const SCORE_DEDUCTIONS = {
|
|
11
|
+
critical: 25,
|
|
12
|
+
major: 10,
|
|
13
|
+
minor: 3,
|
|
14
|
+
};
|
|
15
|
+
export const ALL_TRAPS = [
|
|
16
|
+
'console',
|
|
17
|
+
'network',
|
|
18
|
+
'layout',
|
|
19
|
+
'accessibility',
|
|
20
|
+
'performance',
|
|
21
|
+
'links',
|
|
22
|
+
'seo',
|
|
23
|
+
'theme',
|
|
24
|
+
];
|
|
25
|
+
export function gradeForScore(score) {
|
|
26
|
+
if (score >= 97)
|
|
27
|
+
return 'A+';
|
|
28
|
+
if (score >= 93)
|
|
29
|
+
return 'A';
|
|
30
|
+
if (score >= 90)
|
|
31
|
+
return 'A-';
|
|
32
|
+
if (score >= 87)
|
|
33
|
+
return 'B+';
|
|
34
|
+
if (score >= 83)
|
|
35
|
+
return 'B';
|
|
36
|
+
if (score >= 80)
|
|
37
|
+
return 'B-';
|
|
38
|
+
if (score >= 77)
|
|
39
|
+
return 'C+';
|
|
40
|
+
if (score >= 73)
|
|
41
|
+
return 'C';
|
|
42
|
+
if (score >= 70)
|
|
43
|
+
return 'C-';
|
|
44
|
+
if (score >= 67)
|
|
45
|
+
return 'D+';
|
|
46
|
+
if (score >= 63)
|
|
47
|
+
return 'D';
|
|
48
|
+
if (score >= 60)
|
|
49
|
+
return 'D-';
|
|
50
|
+
return 'F';
|
|
51
|
+
}
|
|
52
|
+
export function computeHealthScore(issues) {
|
|
53
|
+
const byTrap = ALL_TRAPS.map((trap) => {
|
|
54
|
+
const trapIssues = issues.filter((issue) => CATEGORY_TO_TRAP[issue.category] === trap);
|
|
55
|
+
const bySeverity = { critical: 0, major: 0, minor: 0 };
|
|
56
|
+
let deductions = 0;
|
|
57
|
+
for (const issue of trapIssues) {
|
|
58
|
+
bySeverity[issue.severity] += 1;
|
|
59
|
+
deductions += SCORE_DEDUCTIONS[issue.severity];
|
|
60
|
+
}
|
|
61
|
+
const score = Math.max(0, 100 - deductions);
|
|
62
|
+
return {
|
|
63
|
+
trap,
|
|
64
|
+
label: TRAP_LABELS[trap],
|
|
65
|
+
score,
|
|
66
|
+
grade: gradeForScore(score),
|
|
67
|
+
issues: trapIssues.length,
|
|
68
|
+
bySeverity,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const score = Math.round(byTrap.reduce((sum, t) => sum + t.score, 0) / byTrap.length);
|
|
72
|
+
return { score, grade: gradeForScore(score), byTrap };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=health-score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-score.js","sourceRoot":"","sources":["../../../src/engine/health-score.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,WAAW,GAMZ,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAkC;IAC7D,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE;IACT,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAe;IACnC,SAAS;IACT,SAAS;IACT,QAAQ;IACR,eAAe;IACf,aAAa;IACb,OAAO;IACP,KAAK;IACL,OAAO;CACR,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,MAAM,MAAM,GAAgB,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACjD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;QACvF,MAAM,UAAU,GAAkC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACtF,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAChC,UAAU,IAAI,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC;QAC5C,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC;YACxB,KAAK;YACL,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC;YAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,UAAU;SACX,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACtF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for the ghost-qa version is `package.json`.
|
|
3
|
+
*
|
|
4
|
+
* It is read dynamically at runtime (never hard-coded) so `ghost-qa
|
|
5
|
+
* --version`, MCP `serverInfo`, report metadata and the npm package can
|
|
6
|
+
* never drift apart again — the v1.3.1 npm tarball famously reported
|
|
7
|
+
* "1.3.0" because its committed dist/ had been built before the bump.
|
|
8
|
+
* Falls back to the current release line when package.json is unreachable
|
|
9
|
+
* (exotic bundling), which keeps `GHOST_QA_VERSION` total and side-effect
|
|
10
|
+
* safe.
|
|
11
|
+
*/
|
|
12
|
+
export declare const FALLBACK_VERSION = "2.0.0";
|
|
13
|
+
export declare const GHOST_QA_VERSION: string;
|
|
14
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/engine/version.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAC;AA4BxC,eAAO,MAAM,gBAAgB,EAAE,MAA6B,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* The single source of truth for the ghost-qa version is `package.json`.
|
|
6
|
+
*
|
|
7
|
+
* It is read dynamically at runtime (never hard-coded) so `ghost-qa
|
|
8
|
+
* --version`, MCP `serverInfo`, report metadata and the npm package can
|
|
9
|
+
* never drift apart again — the v1.3.1 npm tarball famously reported
|
|
10
|
+
* "1.3.0" because its committed dist/ had been built before the bump.
|
|
11
|
+
* Falls back to the current release line when package.json is unreachable
|
|
12
|
+
* (exotic bundling), which keeps `GHOST_QA_VERSION` total and side-effect
|
|
13
|
+
* safe.
|
|
14
|
+
*/
|
|
15
|
+
export const FALLBACK_VERSION = '2.0.0';
|
|
16
|
+
function readPackageVersion() {
|
|
17
|
+
const candidates = [];
|
|
18
|
+
try {
|
|
19
|
+
// dist/src/engine/version.js -> <pkg>/package.json (../../..)
|
|
20
|
+
// src/engine/version.ts (vitest/ts) -> <pkg>/package.json (../..)
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
candidates.push(join(here, '..', '..', '..', 'package.json'));
|
|
23
|
+
candidates.push(join(here, '..', '..', 'package.json'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* import.meta unavailable — cwd candidate below still applies */
|
|
27
|
+
}
|
|
28
|
+
candidates.push(join(process.cwd(), 'package.json'));
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
32
|
+
if (parsed.name === 'ghost-qa' && typeof parsed.version === 'string' && parsed.version) {
|
|
33
|
+
return parsed.version;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* try next candidate */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return FALLBACK_VERSION;
|
|
41
|
+
}
|
|
42
|
+
export const GHOST_QA_VERSION = readPackageVersion();
|
|
43
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/engine/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAExC,SAAS,kBAAkB;IACzB,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,8DAA8D;QAC9D,kEAAkE;QAClE,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;QAC9D,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;IACnE,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IAErD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAA0C,CAAC;YACpG,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACvF,OAAO,MAAM,CAAC,OAAO,CAAC;YACxB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAW,kBAAkB,EAAE,CAAC"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,10 +4,16 @@ export * from './traps/network-trap.js';
|
|
|
4
4
|
export * from './traps/layout-trap.js';
|
|
5
5
|
export * from './traps/a11y-trap.js';
|
|
6
6
|
export * from './traps/perf-trap.js';
|
|
7
|
+
export * from './traps/links-trap.js';
|
|
8
|
+
export * from './traps/seo-trap.js';
|
|
9
|
+
export * from './traps/theme-trap.js';
|
|
10
|
+
export * from './engine/version.js';
|
|
7
11
|
export * from './engine/browser-session.js';
|
|
8
12
|
export * from './engine/route-crawler.js';
|
|
9
13
|
export * from './engine/interaction-runner.js';
|
|
10
14
|
export * from './engine/audit-runner.js';
|
|
15
|
+
export * from './engine/health-score.js';
|
|
16
|
+
export * from './engine/compare.js';
|
|
11
17
|
export * from './engine/server-lifecycle.js';
|
|
12
18
|
export * from './reporter/terminal-summary.js';
|
|
13
19
|
export * from './reporter/agent-diagnostic.js';
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -4,10 +4,16 @@ export * from './traps/network-trap.js';
|
|
|
4
4
|
export * from './traps/layout-trap.js';
|
|
5
5
|
export * from './traps/a11y-trap.js';
|
|
6
6
|
export * from './traps/perf-trap.js';
|
|
7
|
+
export * from './traps/links-trap.js';
|
|
8
|
+
export * from './traps/seo-trap.js';
|
|
9
|
+
export * from './traps/theme-trap.js';
|
|
10
|
+
export * from './engine/version.js';
|
|
7
11
|
export * from './engine/browser-session.js';
|
|
8
12
|
export * from './engine/route-crawler.js';
|
|
9
13
|
export * from './engine/interaction-runner.js';
|
|
10
14
|
export * from './engine/audit-runner.js';
|
|
15
|
+
export * from './engine/health-score.js';
|
|
16
|
+
export * from './engine/compare.js';
|
|
11
17
|
export * from './engine/server-lifecycle.js';
|
|
12
18
|
export * from './reporter/terminal-summary.js';
|
|
13
19
|
export * from './reporter/agent-diagnostic.js';
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
|
package/dist/src/mcp/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CompareDelta } from '../engine/compare.js';
|
|
1
2
|
import { type GhostQAOptions, type GhostQAReport } from '../types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Native MCP (Model Context Protocol) server for ghost-qa.
|
|
@@ -12,7 +13,7 @@ import { type GhostQAOptions, type GhostQAReport } from '../types.js';
|
|
|
12
13
|
export declare const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
13
14
|
export declare const MCP_SERVER_INFO: {
|
|
14
15
|
readonly name: "ghost-qa";
|
|
15
|
-
readonly version:
|
|
16
|
+
readonly version: string;
|
|
16
17
|
};
|
|
17
18
|
export interface JsonRpcRequest {
|
|
18
19
|
jsonrpc: '2.0';
|
|
@@ -48,7 +49,7 @@ export interface DoctorResult {
|
|
|
48
49
|
export declare function runDoctorCheck(): Promise<DoctorResult>;
|
|
49
50
|
export declare const MCP_TOOLS: readonly [{
|
|
50
51
|
readonly name: "ghost_qa_audit_url";
|
|
51
|
-
readonly description: "Run the full ghost-qa
|
|
52
|
+
readonly description: "Run the full ghost-qa Deep Browser Inspection against a running localhost app: crawls internal routes on Desktop 1440x900, Tablet 768x1024 and Mobile 375x812 under light + dark colour schemes, safely exercises interactive controls, and runs the 8-Trap Engine (console/framework runtime, network/asset integrity, layout/geometry/collision, WCAG accessibility + keyboard focus, Core Web Vitals performance, link integrity, SEO/metadata, theme parity). Returns the Self-Healing Bug Report plus the QA Health Score (0-100, grade A+ to F) — fix every critical/major issue and re-audit until the score is 95+ before declaring the task done. Save the returned report JSON as before.json for ghost_qa_compare.";
|
|
52
53
|
readonly inputSchema: {
|
|
53
54
|
readonly type: "object";
|
|
54
55
|
readonly properties: {
|
|
@@ -66,13 +67,31 @@ export declare const MCP_TOOLS: readonly [{
|
|
|
66
67
|
};
|
|
67
68
|
readonly viewport: {
|
|
68
69
|
readonly type: "string";
|
|
69
|
-
readonly enum: readonly ["
|
|
70
|
-
readonly description: "Viewports to test (default
|
|
70
|
+
readonly enum: readonly ["all", "desktop", "tablet", "mobile"];
|
|
71
|
+
readonly description: "Viewports to test (default all: desktop + tablet + mobile)";
|
|
71
72
|
};
|
|
72
73
|
};
|
|
73
74
|
readonly required: readonly ["url"];
|
|
74
75
|
readonly additionalProperties: false;
|
|
75
76
|
};
|
|
77
|
+
}, {
|
|
78
|
+
readonly name: "ghost_qa_compare";
|
|
79
|
+
readonly description: "Compare two ghost-qa JSON reports (the objects returned by ghost_qa_audit_url, or report.json files written by the CLI) and return the Regression & Verification Delta Report: scoreDelta, fixedFindings, newRegressions and remainingFindings. Use it after fixing bugs to prove the fix worked without introducing regressions; verified=true only at score >= 95 with zero regressions and no blocking findings left.";
|
|
80
|
+
readonly inputSchema: {
|
|
81
|
+
readonly type: "object";
|
|
82
|
+
readonly properties: {
|
|
83
|
+
readonly before: {
|
|
84
|
+
readonly type: "object";
|
|
85
|
+
readonly description: "Baseline ghost-qa report JSON (before fixes), e.g. the saved before.json object";
|
|
86
|
+
};
|
|
87
|
+
readonly after: {
|
|
88
|
+
readonly type: "object";
|
|
89
|
+
readonly description: "New ghost-qa report JSON (after fixes), e.g. the saved after.json object";
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
readonly required: readonly ["before", "after"];
|
|
93
|
+
readonly additionalProperties: false;
|
|
94
|
+
};
|
|
76
95
|
}, {
|
|
77
96
|
readonly name: "ghost_qa_doctor";
|
|
78
97
|
readonly description: "Verify ghost-qa environment readiness: Node.js version, Chromium binary availability, and a live headless-browser smoke test. Run this first when an audit fails to start.";
|
|
@@ -86,6 +105,7 @@ export type McpToolName = (typeof MCP_TOOLS)[number]['name'];
|
|
|
86
105
|
export interface McpHandlers {
|
|
87
106
|
audit: (target: string, options: GhostQAOptions) => Promise<GhostQAReport>;
|
|
88
107
|
doctor: () => Promise<DoctorResult>;
|
|
108
|
+
compare?: (before: GhostQAReport, after: GhostQAReport) => CompareDelta;
|
|
89
109
|
}
|
|
90
110
|
export declare const defaultHandlers: McpHandlers;
|
|
91
111
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuC,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG9F,OAAO,EAAmB,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEvF;;;;;;;;;GASG;AAEH,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,eAAe;;;CAA2D,CAAC;AAExF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D;AAED,eAAO,MAAM,eAAe;;;;;;CAMlB,CAAC;AAEX,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC,CAmB5D;AAUD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkDZ,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3E,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,KAAK,YAAY,CAAC;CACzE;AAED,eAAO,MAAM,eAAe,EAAE,WAI7B,CAAC;AA0GF;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,QAAQ,GAAE,WAA6B,GACtC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmDjC;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,WAA6B,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBxB;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,CAMrG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,GAAE,MAAM,CAAC,cAA8B,EAC5C,MAAM,GAAE,MAAM,CAAC,cAA+B,EAC9C,QAAQ,GAAE,WAA6B,GACtC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createInterface } from 'node:readline';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { chromium } from 'playwright';
|
|
4
4
|
import { runGhostQAAudit, GHOST_QA_VERSION } from '../engine/audit-runner.js';
|
|
5
|
+
import { compareReports, formatCompareReport } from '../engine/compare.js';
|
|
5
6
|
import { smokeTestBrowser } from '../engine/browser-session.js';
|
|
6
7
|
import { formatAgentDiagnostic } from '../reporter/agent-diagnostic.js';
|
|
7
8
|
import { DEFAULT_OPTIONS } from '../types.js';
|
|
@@ -55,7 +56,7 @@ function safeExecutablePath() {
|
|
|
55
56
|
export const MCP_TOOLS = [
|
|
56
57
|
{
|
|
57
58
|
name: 'ghost_qa_audit_url',
|
|
58
|
-
description: 'Run the full ghost-qa
|
|
59
|
+
description: 'Run the full ghost-qa Deep Browser Inspection against a running localhost app: crawls internal routes on Desktop 1440x900, Tablet 768x1024 and Mobile 375x812 under light + dark colour schemes, safely exercises interactive controls, and runs the 8-Trap Engine (console/framework runtime, network/asset integrity, layout/geometry/collision, WCAG accessibility + keyboard focus, Core Web Vitals performance, link integrity, SEO/metadata, theme parity). Returns the Self-Healing Bug Report plus the QA Health Score (0-100, grade A+ to F) — fix every critical/major issue and re-audit until the score is 95+ before declaring the task done. Save the returned report JSON as before.json for ghost_qa_compare.',
|
|
59
60
|
inputSchema: {
|
|
60
61
|
type: 'object',
|
|
61
62
|
properties: {
|
|
@@ -67,14 +68,33 @@ export const MCP_TOOLS = [
|
|
|
67
68
|
maxRoutes: { type: 'number', description: 'Maximum routes to visit (default 25)' },
|
|
68
69
|
viewport: {
|
|
69
70
|
type: 'string',
|
|
70
|
-
enum: ['
|
|
71
|
-
description: 'Viewports to test (default
|
|
71
|
+
enum: ['all', 'desktop', 'tablet', 'mobile'],
|
|
72
|
+
description: 'Viewports to test (default all: desktop + tablet + mobile)',
|
|
72
73
|
},
|
|
73
74
|
},
|
|
74
75
|
required: ['url'],
|
|
75
76
|
additionalProperties: false,
|
|
76
77
|
},
|
|
77
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: 'ghost_qa_compare',
|
|
81
|
+
description: 'Compare two ghost-qa JSON reports (the objects returned by ghost_qa_audit_url, or report.json files written by the CLI) and return the Regression & Verification Delta Report: scoreDelta, fixedFindings, newRegressions and remainingFindings. Use it after fixing bugs to prove the fix worked without introducing regressions; verified=true only at score >= 95 with zero regressions and no blocking findings left.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
before: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
description: 'Baseline ghost-qa report JSON (before fixes), e.g. the saved before.json object',
|
|
88
|
+
},
|
|
89
|
+
after: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
description: 'New ghost-qa report JSON (after fixes), e.g. the saved after.json object',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['before', 'after'],
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
78
98
|
{
|
|
79
99
|
name: 'ghost_qa_doctor',
|
|
80
100
|
description: 'Verify ghost-qa environment readiness: Node.js version, Chromium binary availability, and a live headless-browser smoke test. Run this first when an audit fails to start.',
|
|
@@ -84,6 +104,7 @@ export const MCP_TOOLS = [
|
|
|
84
104
|
export const defaultHandlers = {
|
|
85
105
|
audit: (target, options) => runGhostQAAudit(target, options),
|
|
86
106
|
doctor: () => runDoctorCheck(),
|
|
107
|
+
compare: (before, after) => compareReports(before, after),
|
|
87
108
|
};
|
|
88
109
|
function toolResult(text, structured, isError = false) {
|
|
89
110
|
return {
|
|
@@ -100,12 +121,15 @@ function optionsFromArgs(args) {
|
|
|
100
121
|
if (typeof args.maxRoutes === 'number' && Number.isFinite(args.maxRoutes) && args.maxRoutes > 0) {
|
|
101
122
|
options.maxRoutes = Math.floor(args.maxRoutes);
|
|
102
123
|
}
|
|
103
|
-
if (args.viewport === 'desktop' || args.viewport === 'mobile') {
|
|
124
|
+
if (args.viewport === 'desktop' || args.viewport === 'tablet' || args.viewport === 'mobile') {
|
|
104
125
|
options.viewports = [args.viewport];
|
|
105
126
|
}
|
|
106
127
|
else if (args.viewport === 'both') {
|
|
107
128
|
options.viewports = ['desktop', 'mobile'];
|
|
108
129
|
}
|
|
130
|
+
else if (args.viewport === 'all') {
|
|
131
|
+
options.viewports = ['desktop', 'tablet', 'mobile'];
|
|
132
|
+
}
|
|
109
133
|
return options;
|
|
110
134
|
}
|
|
111
135
|
async function callTool(name, args, handlers) {
|
|
@@ -125,6 +149,10 @@ async function callTool(name, args, handlers) {
|
|
|
125
149
|
totalIssues: report.summary.totalIssues,
|
|
126
150
|
bySeverity: report.summary.bySeverity,
|
|
127
151
|
byCategory: report.summary.byCategory,
|
|
152
|
+
healthScore: report.health?.score,
|
|
153
|
+
grade: report.health?.grade,
|
|
154
|
+
byTrap: report.health?.byTrap,
|
|
155
|
+
report,
|
|
128
156
|
issues: report.issues.map((issue) => ({
|
|
129
157
|
id: issue.id,
|
|
130
158
|
severity: issue.severity,
|
|
@@ -132,6 +160,8 @@ async function callTool(name, args, handlers) {
|
|
|
132
160
|
type: issue.type,
|
|
133
161
|
route: issue.route,
|
|
134
162
|
viewport: issue.viewport,
|
|
163
|
+
...(issue.colorScheme ? { colorScheme: issue.colorScheme } : {}),
|
|
164
|
+
...(issue.selector ? { selector: issue.selector } : {}),
|
|
135
165
|
message: issue.message,
|
|
136
166
|
})),
|
|
137
167
|
});
|
|
@@ -141,6 +171,14 @@ async function callTool(name, args, handlers) {
|
|
|
141
171
|
'Check that the dev server is running and Chromium is installed (npx playwright install chromium), or call ghost_qa_doctor.', undefined, true);
|
|
142
172
|
}
|
|
143
173
|
}
|
|
174
|
+
case 'ghost_qa_compare': {
|
|
175
|
+
const isReport = (value) => typeof value === 'object' && value !== null && Array.isArray(value.issues);
|
|
176
|
+
if (!isReport(args.before) || !isReport(args.after)) {
|
|
177
|
+
return toolResult('Invalid arguments: "before" and "after" must be ghost-qa report JSON objects (with an issues array), as returned by ghost_qa_audit_url or written to report.json by the CLI.', undefined, true);
|
|
178
|
+
}
|
|
179
|
+
const delta = (handlers.compare ?? defaultHandlers.compare)(args.before, args.after);
|
|
180
|
+
return toolResult(formatCompareReport(delta), { ...delta });
|
|
181
|
+
}
|
|
144
182
|
case 'ghost_qa_doctor': {
|
|
145
183
|
const result = await handlers.doctor();
|
|
146
184
|
const lines = [
|
|
@@ -175,7 +213,7 @@ export async function handleJsonRpcRequest(request, handlers = defaultHandlers)
|
|
|
175
213
|
protocolVersion: typeof requested === 'string' && requested ? requested : MCP_PROTOCOL_VERSION,
|
|
176
214
|
capabilities: { tools: { listChanged: false } },
|
|
177
215
|
serverInfo: { ...MCP_SERVER_INFO },
|
|
178
|
-
instructions: '
|
|
216
|
+
instructions: 'Self-healing verification loop: (1) ghost_qa_doctor to confirm readiness, (2) ghost_qa_audit_url across desktop/tablet/mobile + light/dark and save the report as the baseline, (3) map selectors/stacks back to source components and fix root causes, (4) re-audit and call ghost_qa_compare(before, after). Report done only when the delta shows zero new regressions, zero blocking findings, and QA Health Score >= 95 (grade A+ band).',
|
|
179
217
|
});
|
|
180
218
|
}
|
|
181
219
|
case 'ping':
|