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.
Files changed (81) hide show
  1. package/README.md +77 -24
  2. package/SKILL.md +78 -77
  3. package/dist/bin/ghost-qa.d.ts +10 -2
  4. package/dist/bin/ghost-qa.d.ts.map +1 -1
  5. package/dist/bin/ghost-qa.js +106 -16
  6. package/dist/bin/ghost-qa.js.map +1 -1
  7. package/dist/src/engine/audit-runner.d.ts +6 -5
  8. package/dist/src/engine/audit-runner.d.ts.map +1 -1
  9. package/dist/src/engine/audit-runner.js +117 -60
  10. package/dist/src/engine/audit-runner.js.map +1 -1
  11. package/dist/src/engine/browser-session.d.ts +3 -3
  12. package/dist/src/engine/browser-session.d.ts.map +1 -1
  13. package/dist/src/engine/browser-session.js +18 -7
  14. package/dist/src/engine/browser-session.js.map +1 -1
  15. package/dist/src/engine/compare.d.ts +37 -0
  16. package/dist/src/engine/compare.d.ts.map +1 -0
  17. package/dist/src/engine/compare.js +63 -0
  18. package/dist/src/engine/compare.js.map +1 -0
  19. package/dist/src/engine/health-score.d.ts +14 -0
  20. package/dist/src/engine/health-score.d.ts.map +1 -0
  21. package/dist/src/engine/health-score.js +74 -0
  22. package/dist/src/engine/health-score.js.map +1 -0
  23. package/dist/src/engine/version.d.ts +14 -0
  24. package/dist/src/engine/version.d.ts.map +1 -0
  25. package/dist/src/engine/version.js +43 -0
  26. package/dist/src/engine/version.js.map +1 -0
  27. package/dist/src/index.d.ts +6 -0
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +6 -0
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/mcp/server.d.ts +24 -4
  32. package/dist/src/mcp/server.d.ts.map +1 -1
  33. package/dist/src/mcp/server.js +43 -5
  34. package/dist/src/mcp/server.js.map +1 -1
  35. package/dist/src/reporter/agent-diagnostic.d.ts +6 -0
  36. package/dist/src/reporter/agent-diagnostic.d.ts.map +1 -1
  37. package/dist/src/reporter/agent-diagnostic.js +52 -2
  38. package/dist/src/reporter/agent-diagnostic.js.map +1 -1
  39. package/dist/src/reporter/html-report.d.ts.map +1 -1
  40. package/dist/src/reporter/html-report.js +79 -4
  41. package/dist/src/reporter/html-report.js.map +1 -1
  42. package/dist/src/reporter/terminal-summary.d.ts.map +1 -1
  43. package/dist/src/reporter/terminal-summary.js +6 -0
  44. package/dist/src/reporter/terminal-summary.js.map +1 -1
  45. package/dist/src/traps/a11y-trap.d.ts +23 -0
  46. package/dist/src/traps/a11y-trap.d.ts.map +1 -1
  47. package/dist/src/traps/a11y-trap.js +185 -0
  48. package/dist/src/traps/a11y-trap.js.map +1 -1
  49. package/dist/src/traps/console-trap.d.ts +3 -1
  50. package/dist/src/traps/console-trap.d.ts.map +1 -1
  51. package/dist/src/traps/console-trap.js +41 -0
  52. package/dist/src/traps/console-trap.js.map +1 -1
  53. package/dist/src/traps/layout-trap.d.ts +31 -0
  54. package/dist/src/traps/layout-trap.d.ts.map +1 -1
  55. package/dist/src/traps/layout-trap.js +173 -0
  56. package/dist/src/traps/layout-trap.js.map +1 -1
  57. package/dist/src/traps/links-trap.d.ts +34 -0
  58. package/dist/src/traps/links-trap.d.ts.map +1 -0
  59. package/dist/src/traps/links-trap.js +136 -0
  60. package/dist/src/traps/links-trap.js.map +1 -0
  61. package/dist/src/traps/network-trap.d.ts +4 -1
  62. package/dist/src/traps/network-trap.d.ts.map +1 -1
  63. package/dist/src/traps/network-trap.js +29 -2
  64. package/dist/src/traps/network-trap.js.map +1 -1
  65. package/dist/src/traps/perf-trap.d.ts +18 -0
  66. package/dist/src/traps/perf-trap.d.ts.map +1 -1
  67. package/dist/src/traps/perf-trap.js +148 -8
  68. package/dist/src/traps/perf-trap.js.map +1 -1
  69. package/dist/src/traps/seo-trap.d.ts +30 -0
  70. package/dist/src/traps/seo-trap.d.ts.map +1 -0
  71. package/dist/src/traps/seo-trap.js +98 -0
  72. package/dist/src/traps/seo-trap.js.map +1 -0
  73. package/dist/src/traps/theme-trap.d.ts +32 -0
  74. package/dist/src/traps/theme-trap.d.ts.map +1 -0
  75. package/dist/src/traps/theme-trap.js +109 -0
  76. package/dist/src/traps/theme-trap.js.map +1 -0
  77. package/dist/src/types.d.ts +55 -3
  78. package/dist/src/types.d.ts.map +1 -1
  79. package/dist/src/types.js +30 -2
  80. package/dist/src/types.js.map +1 -1
  81. 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: 390,
14
- height: 844,
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 existing = this.contexts.get(viewport);
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(viewport, context);
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":"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"}
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"}
@@ -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';
@@ -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';
@@ -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"}
@@ -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: "1.3.1";
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 headless browser audit against a running localhost app: crawls internal routes on Desktop 1440x900 and Mobile 390x844, safely exercises interactive controls, and traps console/hydration errors, failed network requests, layout overflow, accessibility bugs and performance problems. Returns the Self-Healing Bug Report — fix every critical/major issue it reports before declaring the task done.";
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 ["both", "desktop", "mobile"];
70
- readonly description: "Viewports to test (default both)";
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":"AAMA,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BZ,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;CACrC;AAED,eAAO,MAAM,eAAe,EAAE,WAG7B,CAAC;AAqFF;;;;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"}
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"}
@@ -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 headless browser audit against a running localhost app: crawls internal routes on Desktop 1440x900 and Mobile 390x844, safely exercises interactive controls, and traps console/hydration errors, failed network requests, layout overflow, accessibility bugs and performance problems. Returns the Self-Healing Bug Report — fix every critical/major issue it reports before declaring the task done.',
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: ['both', 'desktop', 'mobile'],
71
- description: 'Viewports to test (default both)',
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: 'Use ghost_qa_audit_url to verify any frontend change in a real browser before reporting a task complete. Fix every critical/major issue it returns and re-run until it passes.',
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':