ghost-qa 0.0.0-stage → 1.3.1

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -3
  3. package/SKILL.md +105 -0
  4. package/dist/bin/ghost-qa.d.ts +35 -0
  5. package/dist/bin/ghost-qa.d.ts.map +1 -0
  6. package/dist/bin/ghost-qa.js +406 -0
  7. package/dist/bin/ghost-qa.js.map +1 -0
  8. package/dist/src/engine/audit-runner.d.ts +10 -0
  9. package/dist/src/engine/audit-runner.d.ts.map +1 -0
  10. package/dist/src/engine/audit-runner.js +138 -0
  11. package/dist/src/engine/audit-runner.js.map +1 -0
  12. package/dist/src/engine/browser-session.d.ts +37 -0
  13. package/dist/src/engine/browser-session.d.ts.map +1 -0
  14. package/dist/src/engine/browser-session.js +114 -0
  15. package/dist/src/engine/browser-session.js.map +1 -0
  16. package/dist/src/engine/interaction-runner.d.ts +41 -0
  17. package/dist/src/engine/interaction-runner.d.ts.map +1 -0
  18. package/dist/src/engine/interaction-runner.js +274 -0
  19. package/dist/src/engine/interaction-runner.js.map +1 -0
  20. package/dist/src/engine/route-crawler.d.ts +31 -0
  21. package/dist/src/engine/route-crawler.d.ts.map +1 -0
  22. package/dist/src/engine/route-crawler.js +110 -0
  23. package/dist/src/engine/route-crawler.js.map +1 -0
  24. package/dist/src/engine/server-lifecycle.d.ts +41 -0
  25. package/dist/src/engine/server-lifecycle.d.ts.map +1 -0
  26. package/dist/src/engine/server-lifecycle.js +154 -0
  27. package/dist/src/engine/server-lifecycle.js.map +1 -0
  28. package/dist/src/index.d.ts +16 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/index.js +16 -0
  31. package/dist/src/index.js.map +1 -0
  32. package/dist/src/mcp/server.d.ts +111 -0
  33. package/dist/src/mcp/server.d.ts.map +1 -0
  34. package/dist/src/mcp/server.js +267 -0
  35. package/dist/src/mcp/server.js.map +1 -0
  36. package/dist/src/reporter/agent-diagnostic.d.ts +17 -0
  37. package/dist/src/reporter/agent-diagnostic.d.ts.map +1 -0
  38. package/dist/src/reporter/agent-diagnostic.js +80 -0
  39. package/dist/src/reporter/agent-diagnostic.js.map +1 -0
  40. package/dist/src/reporter/html-report.d.ts +9 -0
  41. package/dist/src/reporter/html-report.d.ts.map +1 -0
  42. package/dist/src/reporter/html-report.js +142 -0
  43. package/dist/src/reporter/html-report.js.map +1 -0
  44. package/dist/src/reporter/terminal-summary.d.ts +5 -0
  45. package/dist/src/reporter/terminal-summary.d.ts.map +1 -0
  46. package/dist/src/reporter/terminal-summary.js +71 -0
  47. package/dist/src/reporter/terminal-summary.js.map +1 -0
  48. package/dist/src/traps/a11y-trap.d.ts +27 -0
  49. package/dist/src/traps/a11y-trap.d.ts.map +1 -0
  50. package/dist/src/traps/a11y-trap.js +173 -0
  51. package/dist/src/traps/a11y-trap.js.map +1 -0
  52. package/dist/src/traps/console-trap.d.ts +28 -0
  53. package/dist/src/traps/console-trap.d.ts.map +1 -0
  54. package/dist/src/traps/console-trap.js +151 -0
  55. package/dist/src/traps/console-trap.js.map +1 -0
  56. package/dist/src/traps/layout-trap.d.ts +31 -0
  57. package/dist/src/traps/layout-trap.d.ts.map +1 -0
  58. package/dist/src/traps/layout-trap.js +150 -0
  59. package/dist/src/traps/layout-trap.js.map +1 -0
  60. package/dist/src/traps/network-trap.d.ts +24 -0
  61. package/dist/src/traps/network-trap.d.ts.map +1 -0
  62. package/dist/src/traps/network-trap.js +132 -0
  63. package/dist/src/traps/network-trap.js.map +1 -0
  64. package/dist/src/traps/perf-trap.d.ts +42 -0
  65. package/dist/src/traps/perf-trap.d.ts.map +1 -0
  66. package/dist/src/traps/perf-trap.js +196 -0
  67. package/dist/src/traps/perf-trap.js.map +1 -0
  68. package/dist/src/types.d.ts +95 -0
  69. package/dist/src/types.d.ts.map +1 -0
  70. package/dist/src/types.js +80 -0
  71. package/dist/src/types.js.map +1 -0
  72. package/package.json +89 -4
  73. package/scripts/prepare-build.mjs +40 -0
@@ -0,0 +1,196 @@
1
+ import { makeIssue } from '../types.js';
2
+ export const EMPTY_PERF_METRICS = {
3
+ domContentLoadedMs: 0,
4
+ loadMs: 0,
5
+ resourceCount: 0,
6
+ totalTransferBytes: 0,
7
+ oversizedResources: [],
8
+ renderBlockingCount: 0,
9
+ domNodeCount: 0,
10
+ longTaskCount: 0,
11
+ longTaskTotalMs: 0,
12
+ };
13
+ export const PERF_THRESHOLDS = {
14
+ domContentLoadedMs: 3_000,
15
+ loadMs: 5_000,
16
+ singleResourceBytes: 1_000_000,
17
+ totalTransferBytes: 3_000_000,
18
+ renderBlockingCount: 5,
19
+ domNodeCount: 1_500,
20
+ longTaskCount: 5,
21
+ longTaskTotalMs: 500,
22
+ };
23
+ const kb = (bytes) => `${Math.round(bytes / 1024)}KB`;
24
+ const ms = (value) => `${Math.round(value)}ms`;
25
+ /**
26
+ * Pure evaluator: Navigation/Resource Timing facts in, perf issues out.
27
+ * Fully unit-testable without a browser.
28
+ */
29
+ export function evaluatePerfMetrics(metrics, route, viewport) {
30
+ const issues = [];
31
+ if (metrics.domContentLoadedMs > PERF_THRESHOLDS.domContentLoadedMs ||
32
+ metrics.loadMs > PERF_THRESHOLDS.loadMs) {
33
+ const critical = metrics.domContentLoadedMs > PERF_THRESHOLDS.domContentLoadedMs * 2 ||
34
+ metrics.loadMs > PERF_THRESHOLDS.loadMs * 2;
35
+ issues.push(makeIssue({
36
+ category: 'performance',
37
+ severity: critical ? 'critical' : 'major',
38
+ type: 'slow-page-load',
39
+ message: `Slow page load on ${viewport}: DOMContentLoaded ${ms(metrics.domContentLoadedMs)}, full load ${ms(metrics.loadMs)} ` +
40
+ `(budgets: ${ms(PERF_THRESHOLDS.domContentLoadedMs)} / ${ms(PERF_THRESHOLDS.loadMs)}). ` +
41
+ 'Profile render-blocking scripts, server response time and hydration cost before adding more UI.',
42
+ route,
43
+ viewport,
44
+ details: {
45
+ domContentLoadedMs: Math.round(metrics.domContentLoadedMs),
46
+ loadMs: Math.round(metrics.loadMs),
47
+ },
48
+ }));
49
+ }
50
+ for (const resource of metrics.oversizedResources.slice(0, 5)) {
51
+ if (resource.transferBytes <= PERF_THRESHOLDS.singleResourceBytes)
52
+ continue;
53
+ issues.push(makeIssue({
54
+ category: 'performance',
55
+ severity: 'major',
56
+ type: 'oversized-resource',
57
+ message: `Oversized resource (${kb(resource.transferBytes)}): ${resource.url}. Compress, resize, lazy-load or code-split it — a single asset should stay under ${kb(PERF_THRESHOLDS.singleResourceBytes)}.`,
58
+ route,
59
+ viewport,
60
+ url: resource.url,
61
+ details: { transferBytes: resource.transferBytes },
62
+ }));
63
+ }
64
+ if (metrics.totalTransferBytes > PERF_THRESHOLDS.totalTransferBytes) {
65
+ issues.push(makeIssue({
66
+ category: 'performance',
67
+ severity: 'major',
68
+ type: 'excessive-payload',
69
+ message: `Excessive page payload: ${kb(metrics.totalTransferBytes)} transferred across ${metrics.resourceCount} resources (budget ${kb(PERF_THRESHOLDS.totalTransferBytes)}). Audit bundles, images and fonts; defer anything below the fold.`,
70
+ route,
71
+ viewport,
72
+ details: {
73
+ totalTransferBytes: metrics.totalTransferBytes,
74
+ resourceCount: metrics.resourceCount,
75
+ },
76
+ }));
77
+ }
78
+ if (metrics.renderBlockingCount > PERF_THRESHOLDS.renderBlockingCount) {
79
+ issues.push(makeIssue({
80
+ category: 'performance',
81
+ severity: 'minor',
82
+ type: 'render-blocking-resources',
83
+ message: `${metrics.renderBlockingCount} render-blocking scripts/stylesheets in <head> delay first paint. Add defer/async, inline critical CSS, or move non-critical tags to the end of <body>.`,
84
+ route,
85
+ viewport,
86
+ details: { renderBlockingCount: metrics.renderBlockingCount },
87
+ }));
88
+ }
89
+ if (metrics.longTaskCount > PERF_THRESHOLDS.longTaskCount || metrics.longTaskTotalMs > PERF_THRESHOLDS.longTaskTotalMs) {
90
+ issues.push(makeIssue({
91
+ category: 'performance',
92
+ severity: 'major',
93
+ type: 'layout-thrashing',
94
+ message: `Main-thread jank: ${metrics.longTaskCount} long tasks totalling ${ms(metrics.longTaskTotalMs)} (>50ms each) — a layout-thrashing / heavy-JS indicator. ` +
95
+ 'Break up synchronous work, memoise renders, and avoid forced synchronous layouts (reading offsetWidth in loops after DOM writes).',
96
+ route,
97
+ viewport,
98
+ details: {
99
+ longTaskCount: metrics.longTaskCount,
100
+ longTaskTotalMs: Math.round(metrics.longTaskTotalMs),
101
+ },
102
+ }));
103
+ }
104
+ if (metrics.domNodeCount > PERF_THRESHOLDS.domNodeCount) {
105
+ issues.push(makeIssue({
106
+ category: 'performance',
107
+ severity: 'minor',
108
+ type: 'excessive-dom-size',
109
+ message: `Excessive DOM size: ${metrics.domNodeCount} nodes (budget ${PERF_THRESHOLDS.domNodeCount}). Deep/wide DOMs slow style recalculation, layout and hydration — virtualise long lists and prune wrapper divs.`,
110
+ route,
111
+ viewport,
112
+ details: { domNodeCount: metrics.domNodeCount },
113
+ }));
114
+ }
115
+ return issues;
116
+ }
117
+ /** In-page probe: reads Navigation Timing + Resource Timing from the live page. */
118
+ async function probePerf() {
119
+ const nav = performance.getEntriesByType('navigation')[0];
120
+ const resources = performance.getEntriesByType('resource');
121
+ let totalTransferBytes = 0;
122
+ const oversizedResources = [];
123
+ for (const resource of resources) {
124
+ const bytes = resource.transferSize || resource.encodedBodySize || 0;
125
+ totalTransferBytes += bytes;
126
+ if (bytes > 250_000) {
127
+ oversizedResources.push({ url: resource.name.slice(0, 200), transferBytes: bytes });
128
+ }
129
+ }
130
+ oversizedResources.sort((a, b) => b.transferBytes - a.transferBytes);
131
+ let renderBlockingCount = 0;
132
+ document
133
+ .querySelectorAll('head script[src]:not([async]):not([defer]), head link[rel="stylesheet"]')
134
+ .forEach(() => {
135
+ renderBlockingCount += 1;
136
+ });
137
+ let longTaskCount = 0;
138
+ let longTaskTotalMs = 0;
139
+ try {
140
+ // PerformanceObserver with buffered entries — getEntriesByType('longtask')
141
+ // logs a "Deprecated API" console warning in Chromium, which ghost-qa's
142
+ // own Console Trap would then (correctly) flag on every page.
143
+ const longTasks = await new Promise((resolvePromise) => {
144
+ const collected = [];
145
+ try {
146
+ const observer = new PerformanceObserver((list) => {
147
+ collected.push(...list.getEntries());
148
+ });
149
+ observer.observe({ type: 'longtask', buffered: true });
150
+ setTimeout(() => {
151
+ observer.disconnect();
152
+ resolvePromise(collected);
153
+ }, 30);
154
+ }
155
+ catch {
156
+ resolvePromise(collected);
157
+ }
158
+ });
159
+ for (const entry of longTasks) {
160
+ longTaskCount += 1;
161
+ longTaskTotalMs += entry.duration;
162
+ }
163
+ }
164
+ catch {
165
+ /* Long Tasks API unsupported in this browser */
166
+ }
167
+ return {
168
+ domContentLoadedMs: nav ? nav.domContentLoadedEventEnd : 0,
169
+ loadMs: nav ? nav.loadEventEnd || nav.responseEnd : 0,
170
+ resourceCount: resources.length,
171
+ totalTransferBytes,
172
+ oversizedResources: oversizedResources.slice(0, 10),
173
+ renderBlockingCount,
174
+ domNodeCount: document.querySelectorAll('*').length,
175
+ longTaskCount,
176
+ longTaskTotalMs,
177
+ };
178
+ }
179
+ /** Runs the in-page perf probe against a live page and evaluates it. */
180
+ export class PerfTrap {
181
+ viewport;
182
+ constructor(viewport) {
183
+ this.viewport = viewport;
184
+ }
185
+ async collect(page, route) {
186
+ let metrics;
187
+ try {
188
+ metrics = await page.evaluate(probePerf);
189
+ }
190
+ catch {
191
+ return [];
192
+ }
193
+ return evaluatePerfMetrics(metrics, route, this.viewport);
194
+ }
195
+ }
196
+ //# sourceMappingURL=perf-trap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perf-trap.js","sourceRoot":"","sources":["../../../src/traps/perf-trap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared contracts for ghost-qa.
3
+ *
4
+ * Every trap, crawler and reporter speaks in these types so a run can be
5
+ * rendered for a human (terminal), serialised for CI (JSON), or fed back to a
6
+ * coding agent (Markdown diagnostic) without any translation layer.
7
+ */
8
+ export type ViewportName = 'desktop' | 'mobile';
9
+ export type IssueCategory = 'console' | 'network' | 'layout' | 'accessibility' | 'performance' | 'interaction' | 'navigation';
10
+ export type IssueSeverity = 'critical' | 'major' | 'minor';
11
+ export type IssueType = 'page-error' | 'unhandled-rejection' | 'hydration-mismatch' | 'console-error' | 'console-warning' | 'http-error' | 'request-failed' | 'cors-error' | 'broken-asset' | 'horizontal-overflow' | 'zero-size-interactive' | 'broken-image' | 'missing-viewport-meta' | 'interaction-failed' | 'navigation-failed' | 'missing-accessible-name' | 'img-missing-alt' | 'input-missing-label' | 'missing-document-title' | 'missing-html-lang' | 'slow-page-load' | 'oversized-resource' | 'excessive-payload' | 'render-blocking-resources' | 'layout-thrashing' | 'excessive-dom-size';
12
+ export interface QAIssue {
13
+ /** Stable, de-duplication-friendly identifier (hash of the fingerprint). */
14
+ id: string;
15
+ category: IssueCategory;
16
+ severity: IssueSeverity;
17
+ type: IssueType;
18
+ /** Human / agent readable one-line description. */
19
+ message: string;
20
+ /** Path of the route the issue was observed on, e.g. `/pricing`. */
21
+ route: string;
22
+ viewport: ViewportName;
23
+ /** Optional machine-readable context (status code, selector, geometry…). */
24
+ details?: Record<string, string | number | boolean>;
25
+ stack?: string;
26
+ /** Fully-qualified URL related to the issue, when one exists. */
27
+ url?: string;
28
+ timestamp: string;
29
+ }
30
+ export interface InteractionAction {
31
+ kind: 'click' | 'fill' | 'tab' | 'modal-open' | 'modal-close' | 'toggle';
32
+ /** CSS-ish descriptor or visible label of the element exercised. */
33
+ target: string;
34
+ route: string;
35
+ viewport: ViewportName;
36
+ outcome: 'passed' | 'failed' | 'skipped';
37
+ reason?: string;
38
+ }
39
+ export interface RouteRecord {
40
+ url: string;
41
+ path: string;
42
+ depth: number;
43
+ title: string;
44
+ httpStatus?: number;
45
+ viewports: ViewportName[];
46
+ issueCount: number;
47
+ }
48
+ export interface GhostQAOptions {
49
+ maxDepth: number;
50
+ maxRoutes: number;
51
+ maxInteractionsPerPage: number;
52
+ timeoutMs: number;
53
+ settleMs: number;
54
+ headless: boolean;
55
+ viewports: ViewportName[];
56
+ interactions: boolean;
57
+ /** Fail (exit 1) when an issue at or above this severity is found. */
58
+ failOn: IssueSeverity;
59
+ }
60
+ export declare const DEFAULT_OPTIONS: GhostQAOptions;
61
+ export interface GhostQAReport {
62
+ tool: 'ghost-qa';
63
+ version: string;
64
+ targetUrl: string;
65
+ startedAt: string;
66
+ finishedAt: string;
67
+ durationMs: number;
68
+ passed: boolean;
69
+ options: GhostQAOptions;
70
+ routes: RouteRecord[];
71
+ interactions: InteractionAction[];
72
+ issues: QAIssue[];
73
+ summary: {
74
+ routesCrawled: number;
75
+ interactionsTested: number;
76
+ interactionsSkipped: number;
77
+ interactionsFailed: number;
78
+ totalIssues: number;
79
+ bySeverity: Record<IssueSeverity, number>;
80
+ byCategory: Record<IssueCategory, number>;
81
+ };
82
+ }
83
+ export declare const SEVERITY_ORDER: Record<IssueSeverity, number>;
84
+ export declare function severityAtOrAbove(severity: IssueSeverity, threshold: IssueSeverity): boolean;
85
+ /** Small deterministic hash used for stable issue ids. */
86
+ export declare function fingerprint(input: string): string;
87
+ export declare function makeIssue(input: Omit<QAIssue, 'id' | 'timestamp'> & {
88
+ timestamp?: string;
89
+ }): QAIssue;
90
+ /** De-duplicate identical findings (the same error often fires per viewport listener). */
91
+ export declare function dedupeIssues(issues: QAIssue[]): QAIssue[];
92
+ export declare function summarise(routes: RouteRecord[], interactions: InteractionAction[], issues: QAIssue[], failOn: IssueSeverity): GhostQAReport['summary'] & {
93
+ passed: boolean;
94
+ };
95
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEhD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,eAAe,GACf,aAAa,GACb,aAAa,GACb,YAAY,CAAC;AAEjB,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAE3D,MAAM,MAAM,SAAS,GACjB,YAAY,GACZ,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,iBAAiB,GACjB,YAAY,GACZ,gBAAgB,GAChB,YAAY,GACZ,cAAc,GACd,qBAAqB,GACrB,uBAAuB,GACvB,cAAc,GACd,uBAAuB,GACvB,oBAAoB,GACpB,mBAAmB,GACnB,yBAAyB,GACzB,iBAAiB,GACjB,qBAAqB,GACrB,wBAAwB,GACxB,mBAAmB,GACnB,gBAAgB,GAChB,oBAAoB,GACpB,mBAAmB,GACnB,2BAA2B,GAC3B,kBAAkB,GAClB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,OAAO;IACtB,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,GAAG,aAAa,GAAG,QAAQ,CAAC;IACzE,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,cAU7B,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAC;QACtB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAC1C,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;KAC3C,CAAC;CACH;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAIxD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,GAAG,OAAO,CAE5F;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,OAAO,CAUT;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAMzD;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,WAAW,EAAE,EACrB,YAAY,EAAE,iBAAiB,EAAE,EACjC,MAAM,EAAE,OAAO,EAAE,EACjB,MAAM,EAAE,aAAa,GACpB,aAAa,CAAC,SAAS,CAAC,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAyBhD"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared contracts for ghost-qa.
3
+ *
4
+ * Every trap, crawler and reporter speaks in these types so a run can be
5
+ * rendered for a human (terminal), serialised for CI (JSON), or fed back to a
6
+ * coding agent (Markdown diagnostic) without any translation layer.
7
+ */
8
+ export const DEFAULT_OPTIONS = {
9
+ maxDepth: 2,
10
+ maxRoutes: 25,
11
+ maxInteractionsPerPage: 12,
12
+ timeoutMs: 15_000,
13
+ settleMs: 600,
14
+ headless: true,
15
+ viewports: ['desktop', 'mobile'],
16
+ interactions: true,
17
+ failOn: 'major',
18
+ };
19
+ export const SEVERITY_ORDER = {
20
+ critical: 3,
21
+ major: 2,
22
+ minor: 1,
23
+ };
24
+ export function severityAtOrAbove(severity, threshold) {
25
+ return SEVERITY_ORDER[severity] >= SEVERITY_ORDER[threshold];
26
+ }
27
+ /** Small deterministic hash used for stable issue ids. */
28
+ export function fingerprint(input) {
29
+ let hash = 0x811c9dc5;
30
+ for (let i = 0; i < input.length; i++) {
31
+ hash ^= input.charCodeAt(i);
32
+ hash = Math.imul(hash, 0x01000193);
33
+ }
34
+ return (hash >>> 0).toString(16).padStart(8, '0');
35
+ }
36
+ export function makeIssue(input) {
37
+ const detailString = input.details ? JSON.stringify(input.details) : '';
38
+ const id = fingerprint([input.category, input.type, input.route, input.viewport, input.message, detailString].join('|'));
39
+ return {
40
+ ...input,
41
+ id,
42
+ timestamp: input.timestamp ?? new Date().toISOString(),
43
+ };
44
+ }
45
+ /** De-duplicate identical findings (the same error often fires per viewport listener). */
46
+ export function dedupeIssues(issues) {
47
+ const seen = new Map();
48
+ for (const issue of issues) {
49
+ if (!seen.has(issue.id))
50
+ seen.set(issue.id, issue);
51
+ }
52
+ return [...seen.values()];
53
+ }
54
+ export function summarise(routes, interactions, issues, failOn) {
55
+ const bySeverity = { critical: 0, major: 0, minor: 0 };
56
+ const byCategory = {
57
+ console: 0,
58
+ network: 0,
59
+ layout: 0,
60
+ accessibility: 0,
61
+ performance: 0,
62
+ interaction: 0,
63
+ navigation: 0,
64
+ };
65
+ for (const issue of issues) {
66
+ bySeverity[issue.severity] += 1;
67
+ byCategory[issue.category] += 1;
68
+ }
69
+ return {
70
+ routesCrawled: routes.length,
71
+ interactionsTested: interactions.filter((a) => a.outcome === 'passed').length,
72
+ interactionsSkipped: interactions.filter((a) => a.outcome === 'skipped').length,
73
+ interactionsFailed: interactions.filter((a) => a.outcome === 'failed').length,
74
+ totalIssues: issues.length,
75
+ bySeverity,
76
+ byCategory,
77
+ passed: !issues.some((i) => severityAtOrAbove(i.severity, failOn)),
78
+ };
79
+ }
80
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+FH,MAAM,CAAC,MAAM,eAAe,GAAmB;IAC7C,QAAQ,EAAE,CAAC;IACX,SAAS,EAAE,EAAE;IACb,sBAAsB,EAAE,EAAE;IAC1B,SAAS,EAAE,MAAM;IACjB,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;IAChC,YAAY,EAAE,IAAI;IAClB,MAAM,EAAE,OAAO;CAChB,CAAC;AAyBF,MAAM,CAAC,MAAM,cAAc,GAAkC;IAC3D,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,QAAuB,EAAE,SAAwB;IACjF,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;AAC/D,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,KAAiE;IAEjE,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,MAAM,EAAE,GAAG,WAAW,CACpB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CACjG,CAAC;IACF,OAAO;QACL,GAAG,KAAK;QACR,EAAE;QACF,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvD,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAmB,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,MAAqB,EACrB,YAAiC,EACjC,MAAiB,EACjB,MAAqB;IAErB,MAAM,UAAU,GAAkC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtF,MAAM,UAAU,GAAkC;QAChD,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,CAAC;QACT,aAAa,EAAE,CAAC;QAChB,WAAW,EAAE,CAAC;QACd,WAAW,EAAE,CAAC;QACd,UAAU,EAAE,CAAC;KACd,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IACD,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,MAAM;QAC5B,kBAAkB,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM;QAC7E,mBAAmB,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,MAAM;QAC/E,kBAAkB,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM;QAC7E,WAAW,EAAE,MAAM,CAAC,MAAM;QAC1B,UAAU;QACV,UAAU;QACV,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;KACnE,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,91 @@
1
1
  {
2
2
  "name": "ghost-qa",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.3.1",
4
+ "description": "Autonomous headless browser QA & self-healing verification loop for coding agents. Crawls localhost, traps console/network/layout failures, and feeds structured diagnostics back to the agent.",
5
+ "type": "module",
6
+ "bin": {
7
+ "ghost-qa": "dist/bin/ghost-qa.js"
8
+ },
9
+ "main": "./dist/src/index.js",
10
+ "types": "./dist/src/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/src/index.d.ts",
14
+ "import": "./dist/src/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "scripts/prepare-build.mjs",
20
+ "SKILL.md",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "scripts": {
25
+ "prepare": "node scripts/prepare-build.mjs",
26
+ "build": "tsc -p tsconfig.json",
27
+ "dev": "tsc -p tsconfig.json --watch",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "qa": "npm run build && npm test",
32
+ "prepublishOnly": "npm run qa"
33
+ },
34
+ "keywords": [
35
+ "mcp",
36
+ "mcp-server",
37
+ "model-context-protocol",
38
+ "claude-code",
39
+ "claude-skills",
40
+ "agent-skills",
41
+ "ai-agents",
42
+ "coding-agents",
43
+ "playwright",
44
+ "headless-browser",
45
+ "qa-automation",
46
+ "e2e-testing",
47
+ "self-healing",
48
+ "codex",
49
+ "cursor",
50
+ "cursorrules",
51
+ "windsurf",
52
+ "accessibility-testing",
53
+ "a11y",
54
+ "web-vitals",
55
+ "performance-testing",
56
+ "browser-testing",
57
+ "test-automation",
58
+ "automated-testing",
59
+ "typescript",
60
+ "cli",
61
+ "devtools",
62
+ "hydration",
63
+ "ci-cd",
64
+ "browser-verification",
65
+ "agent-tools",
66
+ "llm-agents",
67
+ "frontend-qa",
68
+ "mobile-testing"
69
+ ],
70
+ "author": "VibeFoundry",
71
+ "license": "MIT",
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "git+https://github.com/avivgerz-hue/ghost-qa.git"
75
+ },
76
+ "homepage": "https://github.com/avivgerz-hue/ghost-qa#readme",
77
+ "bugs": {
78
+ "url": "https://github.com/avivgerz-hue/ghost-qa/issues"
79
+ },
80
+ "engines": {
81
+ "node": ">=20"
82
+ },
83
+ "dependencies": {
84
+ "playwright": "^1.49.1"
85
+ },
86
+ "devDependencies": {
87
+ "@types/node": "^22.10.2",
88
+ "typescript": "^5.7.2",
89
+ "vitest": "^2.1.8"
90
+ }
91
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `prepare` lifecycle script — must be safe in EVERY install context:
4
+ *
5
+ * 1. dist/ already built (it is committed to the repo, so
6
+ * `npx github:avivgerz-hue/ghost-qa` works instantly) -> no-op.
7
+ * 2. TypeScript available (normal clone / npm install) -> build.
8
+ * 3. TypeScript absent (production install, git dep with
9
+ * devDependencies pruned) -> no-op, never fail.
10
+ *
11
+ * Failing here would break installs for end users, so every path exits 0
12
+ * except a genuine build failure when TypeScript IS present and dist/ is
13
+ * missing.
14
+ */
15
+ import { existsSync } from 'node:fs';
16
+ import { spawnSync } from 'node:child_process';
17
+ import { dirname, join } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..');
21
+ const distEntry = join(root, 'dist', 'bin', 'ghost-qa.js');
22
+ const tscBin = join(root, 'node_modules', 'typescript', 'bin', 'tsc');
23
+
24
+ if (existsSync(distEntry)) {
25
+ process.exit(0);
26
+ }
27
+
28
+ if (!existsSync(tscBin)) {
29
+ console.warn(
30
+ 'ghost-qa: TypeScript not installed and dist/ missing — skipping build ' +
31
+ '(install from a full clone if you need to compile from source).',
32
+ );
33
+ process.exit(0);
34
+ }
35
+
36
+ const result = spawnSync(process.execPath, [tscBin, '-p', 'tsconfig.json'], {
37
+ cwd: root,
38
+ stdio: 'inherit',
39
+ });
40
+ process.exit(result.status ?? 0);