@ia-qa/self-healing 0.1.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 (54) hide show
  1. package/README.md +91 -0
  2. package/TUTORIAL.md +399 -0
  3. package/dist/aom.d.ts +22 -0
  4. package/dist/aom.js +58 -0
  5. package/dist/aom.js.map +1 -0
  6. package/dist/browser/contract.d.ts +1 -0
  7. package/dist/browser/contract.js +107 -0
  8. package/dist/browser/contract.js.map +1 -0
  9. package/dist/browser/extract.d.ts +25 -0
  10. package/dist/browser/extract.js +194 -0
  11. package/dist/browser/extract.js.map +1 -0
  12. package/dist/browser/match.d.ts +88 -0
  13. package/dist/browser/match.js +179 -0
  14. package/dist/browser/match.js.map +1 -0
  15. package/dist/cli/diff.d.ts +13 -0
  16. package/dist/cli/diff.js +143 -0
  17. package/dist/cli/diff.js.map +1 -0
  18. package/dist/cli/fix.d.ts +17 -0
  19. package/dist/cli/fix.js +144 -0
  20. package/dist/cli/fix.js.map +1 -0
  21. package/dist/cli/index.d.ts +2 -0
  22. package/dist/cli/index.js +50 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cli/init.d.ts +8 -0
  25. package/dist/cli/init.js +185 -0
  26. package/dist/cli/init.js.map +1 -0
  27. package/dist/cli/map.d.ts +9 -0
  28. package/dist/cli/map.js +118 -0
  29. package/dist/cli/map.js.map +1 -0
  30. package/dist/config.d.ts +55 -0
  31. package/dist/config.js +111 -0
  32. package/dist/config.js.map +1 -0
  33. package/dist/fixEngine.d.ts +49 -0
  34. package/dist/fixEngine.js +154 -0
  35. package/dist/fixEngine.js.map +1 -0
  36. package/dist/index.d.ts +12 -0
  37. package/dist/index.js +29 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/launcher.d.ts +38 -0
  40. package/dist/launcher.js +38 -0
  41. package/dist/launcher.js.map +1 -0
  42. package/dist/mapUrl.d.ts +34 -0
  43. package/dist/mapUrl.js +90 -0
  44. package/dist/mapUrl.js.map +1 -0
  45. package/dist/markdown.d.ts +10 -0
  46. package/dist/markdown.js +54 -0
  47. package/dist/markdown.js.map +1 -0
  48. package/dist/mcp/server.d.ts +2 -0
  49. package/dist/mcp/server.js +251 -0
  50. package/dist/mcp/server.js.map +1 -0
  51. package/dist/playwright/healer.d.ts +39 -0
  52. package/dist/playwright/healer.js +84 -0
  53. package/dist/playwright/healer.js.map +1 -0
  54. package/package.json +54 -0
@@ -0,0 +1,34 @@
1
+ import { PageMapping } from './aom';
2
+ import { BrowserChoice } from './launcher';
3
+ /**
4
+ * Config-free single-URL mapping — the primitive behind the MCP `map_app` tool.
5
+ *
6
+ * Unlike `ia-qa-heal map` (which reads `.ia-qa/config.json` for auth + a route
7
+ * list), this takes one URL, launches its own headless Chromium, extracts the
8
+ * interactive elements, and returns the mapping. It runs on the caller's machine,
9
+ * so it sees whatever that machine sees — localhost, staging, an app behind a
10
+ * VPN. Nothing leaves the machine.
11
+ */
12
+ export interface MapUrlOptions extends BrowserChoice {
13
+ /** Logical page name → mapping filename. Defaults to a slug of the URL path. */
14
+ pageName?: string;
15
+ /** Directory to write `<page>.json` + `<page>.md` into. Omit to skip writing. */
16
+ outDir?: string;
17
+ /** Playwright load state to await before extracting. Default 'networkidle'. */
18
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
19
+ /** Navigation timeout in ms. Default 30000. */
20
+ timeout?: number;
21
+ /** Optional selector to wait for before extracting (helps SPAs settle). */
22
+ waitForSelector?: string;
23
+ }
24
+ export interface MapUrlResult {
25
+ mapping: PageMapping;
26
+ jsonPath?: string;
27
+ mdPath?: string;
28
+ /** Which browser was actually used (bundled, system channel, explicit path). */
29
+ browser: string;
30
+ }
31
+ export declare function pageNameFromUrl(url: string): string;
32
+ export declare function mapUrl(url: string, options?: MapUrlOptions): Promise<MapUrlResult>;
33
+ /** Convenience for logs: a relative path when possible. */
34
+ export declare function rel(p: string): string;
package/dist/mapUrl.js ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.pageNameFromUrl = pageNameFromUrl;
37
+ exports.mapUrl = mapUrl;
38
+ exports.rel = rel;
39
+ const path = __importStar(require("path"));
40
+ const aom_1 = require("./aom");
41
+ const markdown_1 = require("./markdown");
42
+ const config_1 = require("./config");
43
+ const launcher_1 = require("./launcher");
44
+ function pageNameFromUrl(url) {
45
+ let pathname = '/';
46
+ try {
47
+ pathname = new URL(url).pathname;
48
+ }
49
+ catch {
50
+ /* fall through to default */
51
+ }
52
+ const slug = pathname.replace(/^\/+|\/+$/g, '').replace(/\//g, '-');
53
+ return slug || 'home';
54
+ }
55
+ async function mapUrl(url, options = {}) {
56
+ const pageName = options.pageName || pageNameFromUrl(url);
57
+ const waitUntil = options.waitUntil || 'networkidle';
58
+ const timeout = options.timeout ?? 30000;
59
+ const { browser, using } = await (0, launcher_1.launchBrowser)(options);
60
+ try {
61
+ const context = await browser.newContext();
62
+ const page = await context.newPage();
63
+ await page.goto(url, { waitUntil, timeout });
64
+ if (options.waitForSelector) {
65
+ await page.waitForSelector(options.waitForSelector, { timeout });
66
+ }
67
+ const elements = await (0, aom_1.extractInteractiveElements)(page);
68
+ const mapping = {
69
+ page: pageName,
70
+ url,
71
+ capturedAt: new Date().toISOString(),
72
+ elements,
73
+ };
74
+ const result = { mapping, browser: using };
75
+ if (options.outDir !== undefined) {
76
+ const dir = options.outDir || (0, config_1.mappingDir)();
77
+ result.jsonPath = (0, aom_1.saveMapping)(mapping, dir);
78
+ result.mdPath = (0, markdown_1.saveMarkdown)(mapping, dir);
79
+ }
80
+ return result;
81
+ }
82
+ finally {
83
+ await browser.close();
84
+ }
85
+ }
86
+ /** Convenience for logs: a relative path when possible. */
87
+ function rel(p) {
88
+ return path.relative(process.cwd(), p) || p;
89
+ }
90
+ //# sourceMappingURL=mapUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapUrl.js","sourceRoot":"","sources":["../src/mapUrl.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,0CASC;AAED,wBAgCC;AAGD,kBAEC;AArFD,2CAA6B;AAC7B,+BAA4F;AAC5F,yCAA0C;AAC1C,qCAAsC;AACtC,yCAA0D;AAiC1D,SAAgB,eAAe,CAAC,GAAW;IACzC,IAAI,QAAQ,GAAG,GAAG,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpE,OAAO,IAAI,IAAI,MAAM,CAAC;AACxB,CAAC;AAEM,KAAK,UAAU,MAAM,CAAC,GAAW,EAAE,UAAyB,EAAE;IACnE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;IAEzC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,IAAA,wBAAa,EAAC,OAAO,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,QAAQ,GAAoB,MAAM,IAAA,gCAA0B,EAAC,IAAI,CAAC,CAAC;QACzE,MAAM,OAAO,GAAgB;YAC3B,IAAI,EAAE,QAAQ;YACd,GAAG;YACH,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,QAAQ;SACT,CAAC;QAEF,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACzD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,IAAA,mBAAU,GAAE,CAAC;YAC3C,MAAM,CAAC,QAAQ,GAAG,IAAA,iBAAW,EAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,GAAG,IAAA,uBAAY,EAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,SAAgB,GAAG,CAAC,CAAS;IAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { PageMapping } from './aom';
2
+ import { renderPageContract } from './browser/contract';
3
+ /**
4
+ * Node-side file I/O for the page contract. The rendering itself lives in
5
+ * `browser/contract.js` (pure JS) so the CLI and ia-qa.com produce identical
6
+ * `.md` output from the same mapping.
7
+ */
8
+ export { renderPageContract };
9
+ export declare function markdownPath(pageName: string, dir?: string): string;
10
+ export declare function saveMarkdown(mapping: PageMapping, dir?: string): string;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.renderPageContract = void 0;
37
+ exports.markdownPath = markdownPath;
38
+ exports.saveMarkdown = saveMarkdown;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const config_1 = require("./config");
42
+ const contract_1 = require("./browser/contract");
43
+ Object.defineProperty(exports, "renderPageContract", { enumerable: true, get: function () { return contract_1.renderPageContract; } });
44
+ function markdownPath(pageName, dir = (0, config_1.mappingDir)()) {
45
+ const safe = pageName.replace(/[^a-zA-Z0-9._-]/g, '-');
46
+ return path.join(dir, `${safe}.md`);
47
+ }
48
+ function saveMarkdown(mapping, dir = (0, config_1.mappingDir)()) {
49
+ fs.mkdirSync(dir, { recursive: true });
50
+ const file = markdownPath(mapping.page, dir);
51
+ fs.writeFileSync(file, (0, contract_1.renderPageContract)(mapping), 'utf8');
52
+ return file;
53
+ }
54
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,oCAGC;AAED,oCAKC;AAxBD,uCAAyB;AACzB,2CAA6B;AAE7B,qCAAsC;AACtC,iDAAwD;AAQ/C,mGARA,6BAAkB,OAQA;AAE3B,SAAgB,YAAY,CAAC,QAAgB,EAAE,MAAc,IAAA,mBAAU,GAAE;IACvE,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,SAAgB,YAAY,CAAC,OAAoB,EAAE,MAAc,IAAA,mBAAU,GAAE;IAC3E,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC7C,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAA,6BAAkB,EAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const fs = __importStar(require("fs"));
38
+ const mapUrl_1 = require("../mapUrl");
39
+ const match_1 = require("../browser/match");
40
+ const markdown_1 = require("../markdown");
41
+ const fixEngine_1 = require("../fixEngine");
42
+ /**
43
+ * @ia-qa/self-healing — local stdio MCP server.
44
+ *
45
+ * A dependency-free JSON-RPC 2.0 server over stdio (newline-delimited messages,
46
+ * the MCP stdio transport). It runs on the END USER's machine, launches its own
47
+ * headless Chromium via Playwright, and exposes three tools to an agent:
48
+ * - map_app — scan one URL → page contract (json + md)
49
+ * - diff_mappings — two contracts → PASS/FIX/BLOCK verdict + drift detail
50
+ * - fix_tests — apply the deterministic healable rewrites to test files
51
+ *
52
+ * Because it runs locally, it sees localhost / staging / VPN apps and nothing
53
+ * leaves the machine. stdout carries the protocol; all logs go to stderr.
54
+ */
55
+ const PROTOCOL_VERSION = '2024-11-05';
56
+ const SERVER_INFO = { name: 'ia-qa-self-healing', version: '0.1.0' };
57
+ const TOOLS = [
58
+ {
59
+ name: 'map_app',
60
+ description: "Scan ONE page of a web app and produce its 'page contract': every visible interactive element as {role, accessible name, context, stable selector}. Runs a local headless browser, so it works on localhost, staging, or an app behind a VPN — nothing leaves the machine. Call once per route (navigate + log in first if needed). Returns the contract as Markdown and, if out_dir is set, writes <page>.json + <page>.md.",
61
+ inputSchema: {
62
+ type: 'object',
63
+ properties: {
64
+ url: { type: 'string', description: 'The page URL to map (e.g. http://localhost:3000/checkout).' },
65
+ page_name: { type: 'string', description: 'Logical name for the mapping file. Defaults to a slug of the URL path.' },
66
+ out_dir: { type: 'string', description: 'Directory to write <page>.json + <page>.md into. Omit to return the contract without writing files.' },
67
+ wait_for_selector: { type: 'string', description: 'Optional CSS selector to wait for before extracting — helps a lazy SPA settle.' },
68
+ wait_until: { type: 'string', enum: ['load', 'domcontentloaded', 'networkidle', 'commit'], description: "Load state to await. Default 'networkidle'." },
69
+ browser_channel: { type: 'string', description: "Reuse a browser already installed on the machine instead of the bundled one: 'chrome', 'msedge'. Playwright's browser cache is machine-wide, so the bundled Chromium is usually already present and free to use — set this only to avoid a download on a machine that has Chrome but no Playwright browsers." },
70
+ browser_path: { type: 'string', description: 'Absolute path to a browser executable to use. Wins over browser_channel.' },
71
+ },
72
+ required: ['url'],
73
+ },
74
+ },
75
+ {
76
+ name: 'diff_mappings',
77
+ description: 'Diff a baseline page contract against a current one and return a CI-style verdict: PASS / FIX / BLOCK, plus per-element drift (ok, renamed, healable, ambiguous, lost, added). Pure and deterministic — no browser. Provide each mapping either as a file path or inline JSON.',
78
+ inputSchema: {
79
+ type: 'object',
80
+ properties: {
81
+ before_path: { type: 'string', description: 'Path to the baseline mapping JSON.' },
82
+ after_path: { type: 'string', description: 'Path to the current mapping JSON.' },
83
+ before_json: { type: 'object', description: 'Baseline mapping inline (alternative to before_path).' },
84
+ after_json: { type: 'object', description: 'Current mapping inline (alternative to after_path).' },
85
+ },
86
+ },
87
+ },
88
+ {
89
+ name: 'fix_tests',
90
+ description: "Apply ONLY the deterministic 'healable' selector rewrites (old→new) from a diff to your test files. Framework/language-agnostic: replaces the selector only where it appears as a quoted string literal, quote-safe for attribute selectors. LOST / AMBIGUOUS / RENAMED are reported but never touched — they need human judgment. Edits the working tree and NEVER commits; review with git diff. Use dry_run to preview.",
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ before_path: { type: 'string', description: 'Path to the baseline mapping JSON.' },
95
+ after_path: { type: 'string', description: 'Path to the current mapping JSON.' },
96
+ before_json: { type: 'object', description: 'Baseline mapping inline (alternative to before_path).' },
97
+ after_json: { type: 'object', description: 'Current mapping inline (alternative to after_path).' },
98
+ test_paths: { type: 'array', items: { type: 'string' }, description: 'Test files or directories to rewrite.' },
99
+ dry_run: { type: 'boolean', description: 'Preview the plan without writing. Default false.' },
100
+ },
101
+ required: ['test_paths'],
102
+ },
103
+ },
104
+ ];
105
+ function loadMappingArg(inlineJson, filePath, label) {
106
+ let mapping = inlineJson;
107
+ if (!mapping && filePath) {
108
+ mapping = JSON.parse(fs.readFileSync(filePath, 'utf8'));
109
+ }
110
+ if (!mapping)
111
+ throw new Error(`Provide ${label}_path or ${label}_json.`);
112
+ if (!Array.isArray(mapping.elements))
113
+ throw new Error(`${label} has no "elements" array — not a mapping.`);
114
+ return mapping;
115
+ }
116
+ async function callTool(name, args) {
117
+ if (name === 'map_app') {
118
+ if (!args?.url)
119
+ throw new Error('map_app requires "url".');
120
+ const res = await (0, mapUrl_1.mapUrl)(args.url, {
121
+ pageName: args.page_name,
122
+ outDir: args.out_dir,
123
+ waitForSelector: args.wait_for_selector,
124
+ waitUntil: args.wait_until,
125
+ browserChannel: args.browser_channel,
126
+ browserPath: args.browser_path,
127
+ });
128
+ const md = (0, markdown_1.renderPageContract)(res.mapping);
129
+ const header = `Mapped ${res.mapping.elements.length} interactive elements on "${res.mapping.page}" (${args.url}).` +
130
+ `\nBrowser: ${res.browser}` +
131
+ (res.jsonPath ? `\nWrote ${(0, mapUrl_1.rel)(res.jsonPath)} + ${(0, mapUrl_1.rel)(res.mdPath)}` : '\n(No out_dir given — contract returned inline, not written.)');
132
+ return `${header}\n\n${md}`;
133
+ }
134
+ if (name === 'diff_mappings') {
135
+ const before = loadMappingArg(args?.before_json, args?.before_path, 'before');
136
+ const after = loadMappingArg(args?.after_json, args?.after_path, 'after');
137
+ const report = (0, match_1.diffMappings)(before, after);
138
+ return JSON.stringify(report, null, 2);
139
+ }
140
+ if (name === 'fix_tests') {
141
+ const before = loadMappingArg(args?.before_json, args?.before_path, 'before');
142
+ const after = loadMappingArg(args?.after_json, args?.after_path, 'after');
143
+ if (!Array.isArray(args?.test_paths) || args.test_paths.length === 0) {
144
+ throw new Error('fix_tests requires a non-empty "test_paths" array.');
145
+ }
146
+ const report = (0, match_1.diffMappings)(before, after);
147
+ const rewrites = (0, fixEngine_1.computeRewrites)(report.rows);
148
+ const skipped = [];
149
+ const files = (0, fixEngine_1.collectFiles)(args.test_paths, (p) => skipped.push(p));
150
+ const result = (0, fixEngine_1.applyRewrites)(files, rewrites, !!args.dry_run);
151
+ const flagged = report.rows
152
+ .filter((r) => r.status === 'lost' || r.status === 'ambiguous' || r.status === 'renamed')
153
+ .map((r) => ({ status: r.status, role: r.role, name: r.name, selector: r.selector, newName: r.newName, candidateCount: r.candidateCount }));
154
+ return JSON.stringify({
155
+ verdict: report.verdict,
156
+ counts: report.counts,
157
+ applied: result.dryRun ? 0 : result.totalReplacements,
158
+ would_apply: result.totalReplacements,
159
+ edits: result.edits.map((e) => ({ file: (0, mapUrl_1.rel)(e.file), replacements: e.replacements })),
160
+ needs_human: flagged,
161
+ skipped_paths: skipped,
162
+ note: result.dryRun
163
+ ? 'Dry run — nothing written.'
164
+ : 'Working tree edited. NOT committed — review with git diff, then a human decides to push or discard.',
165
+ }, null, 2);
166
+ }
167
+ throw new Error(`Unknown tool "${name}".`);
168
+ }
169
+ // ── JSON-RPC over stdio (newline-delimited) ─────────────────────────────────
170
+ function send(msg) {
171
+ process.stdout.write(JSON.stringify(msg) + '\n');
172
+ }
173
+ function reply(id, result) {
174
+ send({ jsonrpc: '2.0', id, result });
175
+ }
176
+ function replyError(id, code, message) {
177
+ send({ jsonrpc: '2.0', id, error: { code, message } });
178
+ }
179
+ async function handle(req) {
180
+ const { id, method, params } = req;
181
+ const isNotification = id === undefined || id === null;
182
+ try {
183
+ if (method === 'initialize') {
184
+ reply(id, {
185
+ protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
186
+ capabilities: { tools: {} },
187
+ serverInfo: SERVER_INFO,
188
+ });
189
+ return;
190
+ }
191
+ if (method === 'notifications/initialized' || method === 'initialized') {
192
+ return; // notification, no response
193
+ }
194
+ if (method === 'ping') {
195
+ reply(id, {});
196
+ return;
197
+ }
198
+ if (method === 'tools/list') {
199
+ reply(id, { tools: TOOLS });
200
+ return;
201
+ }
202
+ if (method === 'tools/call') {
203
+ const toolName = params?.name;
204
+ try {
205
+ const text = await callTool(toolName, params?.arguments || {});
206
+ reply(id, { content: [{ type: 'text', text }] });
207
+ }
208
+ catch (err) {
209
+ const message = err instanceof Error ? err.message : String(err);
210
+ reply(id, { content: [{ type: 'text', text: `Error: ${message}` }], isError: true });
211
+ }
212
+ return;
213
+ }
214
+ if (!isNotification)
215
+ replyError(id, -32601, `Method not found: ${method}`);
216
+ }
217
+ catch (err) {
218
+ const message = err instanceof Error ? err.message : String(err);
219
+ if (!isNotification)
220
+ replyError(id, -32603, message);
221
+ else
222
+ process.stderr.write(`[ia-qa-self-healing] error handling ${method}: ${message}\n`);
223
+ }
224
+ }
225
+ function main() {
226
+ process.stderr.write('[ia-qa-self-healing] MCP server ready (stdio)\n');
227
+ let buffer = '';
228
+ process.stdin.setEncoding('utf8');
229
+ process.stdin.on('data', (chunk) => {
230
+ buffer += chunk;
231
+ let nl;
232
+ while ((nl = buffer.indexOf('\n')) !== -1) {
233
+ const line = buffer.slice(0, nl).trim();
234
+ buffer = buffer.slice(nl + 1);
235
+ if (!line)
236
+ continue;
237
+ let req;
238
+ try {
239
+ req = JSON.parse(line);
240
+ }
241
+ catch {
242
+ process.stderr.write(`[ia-qa-self-healing] non-JSON line ignored: ${line.slice(0, 80)}\n`);
243
+ continue;
244
+ }
245
+ void handle(req);
246
+ }
247
+ });
248
+ process.stdin.on('end', () => process.exit(0));
249
+ }
250
+ main();
251
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { Page } from 'playwright';
2
+ import { MappedElement } from '../aom';
3
+ /**
4
+ * Runtime self-healing helpers for Playwright.
5
+ *
6
+ * Usage in a test:
7
+ * import { aiClick } from '@ia-qa/self-healing';
8
+ * await aiClick(page, 'button#login');
9
+ *
10
+ * If the standard action fails (timeout / not found), the helper reads the
11
+ * page mapping produced by `ia-qa-heal map`, re-scans the live page, and finds
12
+ * the element that semantically matches the original (same role + closest
13
+ * accessible name — or a custom LLM resolver if provided). It then retries the
14
+ * action on the healed selector and logs a loud warning so the developer knows
15
+ * the test needs updating.
16
+ */
17
+ export interface HealerOptions {
18
+ /** Mapping name (`.ia-qa/mapping/<page>.json`). Inferred from the current URL + config.pages if omitted. */
19
+ page?: string;
20
+ /** Timeout for the initial attempt AND the healed retry (ms). Default 5000. */
21
+ timeout?: number;
22
+ /** Override the mapping directory (defaults to `<cwd>/.ia-qa/mapping`). */
23
+ mappingDir?: string;
24
+ /**
25
+ * Optional semantic resolver — e.g. an LLM call. Receives the original
26
+ * element and the live candidates; returns the healed selector or null to
27
+ * fall back to the built-in heuristic. See `llmResolverStub` below.
28
+ */
29
+ llmResolver?: (target: MappedElement, candidates: MappedElement[]) => Promise<string | null>;
30
+ }
31
+ export declare function aiClick(page: Page, selector: string, options?: HealerOptions): Promise<void>;
32
+ export declare function aiFill(page: Page, selector: string, value: string, options?: HealerOptions): Promise<void>;
33
+ /**
34
+ * LLM resolver stub — TO COMPLETE. Wire this to a real model (e.g.
35
+ * `POST https://www.ia-qa.com/mcp/call` with an embedding/similarity tool, or
36
+ * your own provider) and pass it via `options.llmResolver`. It must return the
37
+ * selector of the best candidate, or null to defer to the heuristic.
38
+ */
39
+ export declare function llmResolverStub(_target: MappedElement, _candidates: MappedElement[]): Promise<string | null>;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.aiClick = aiClick;
4
+ exports.aiFill = aiFill;
5
+ exports.llmResolverStub = llmResolverStub;
6
+ const config_1 = require("../config");
7
+ const aom_1 = require("../aom");
8
+ const match_1 = require("../browser/match");
9
+ const DEFAULT_TIMEOUT = 5000;
10
+ async function aiClick(page, selector, options = {}) {
11
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
12
+ try {
13
+ await page.click(selector, { timeout });
14
+ }
15
+ catch (error) {
16
+ const healed = await heal(page, selector, options, error);
17
+ await page.click(healed, { timeout });
18
+ }
19
+ }
20
+ async function aiFill(page, selector, value, options = {}) {
21
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
22
+ try {
23
+ await page.fill(selector, value, { timeout });
24
+ }
25
+ catch (error) {
26
+ const healed = await heal(page, selector, options, error);
27
+ await page.fill(healed, value, { timeout });
28
+ }
29
+ }
30
+ async function heal(page, selector, options, originalError) {
31
+ const pageName = options.page ?? inferPageName(page);
32
+ const dir = options.mappingDir ?? (0, config_1.mappingDir)();
33
+ const mapping = (0, aom_1.loadMapping)(pageName, dir);
34
+ const target = mapping.elements.find((e) => e.selector === selector);
35
+ if (!target) {
36
+ throw chain(originalError, `Selector "${selector}" is not in the mapping for page "${pageName}" — re-run \`ia-qa-heal map ${pageName}\` so the healer knows what this selector used to point at.`);
37
+ }
38
+ const candidates = await (0, aom_1.extractInteractiveElements)(page);
39
+ let healedSelector = null;
40
+ if (options.llmResolver) {
41
+ healedSelector = await options.llmResolver(target, candidates);
42
+ }
43
+ if (!healedSelector) {
44
+ const match = (0, match_1.matchElement)(target, candidates);
45
+ if (match && 'ambiguous' in match && match.ambiguous) {
46
+ throw chain(originalError, `Self-healing refused for "${selector}" on page "${pageName}": ${match.count} elements match role="${target.role}" name="${target.name}" and context "${target.context ?? ''}" doesn't disambiguate. Healing the wrong one is worse than failing — fix the selector by hand, or give the element a data-testid.`);
47
+ }
48
+ if (match && match.matched)
49
+ healedSelector = match.selector;
50
+ }
51
+ if (!healedSelector || healedSelector === selector) {
52
+ throw chain(originalError, `Self-healing failed for "${selector}" on page "${pageName}": no live element matches role="${target.role}" name="${target.name}".`);
53
+ }
54
+ console.warn(`\n⚠️ [ia-qa self-healing] Selector "${selector}" failed on page "${pageName}".\n` +
55
+ ` Healed to "${healedSelector}" (matched role="${target.role}", name="${target.name}").\n` +
56
+ ` Update your test to use the new selector, then re-run \`ia-qa-heal map ${pageName}\`.\n`);
57
+ return healedSelector;
58
+ }
59
+ /**
60
+ * LLM resolver stub — TO COMPLETE. Wire this to a real model (e.g.
61
+ * `POST https://www.ia-qa.com/mcp/call` with an embedding/similarity tool, or
62
+ * your own provider) and pass it via `options.llmResolver`. It must return the
63
+ * selector of the best candidate, or null to defer to the heuristic.
64
+ */
65
+ async function llmResolverStub(_target, _candidates) {
66
+ return null;
67
+ }
68
+ function inferPageName(page) {
69
+ const currentPath = new URL(page.url()).pathname.replace(/\/+$/, '') || '/';
70
+ const config = (0, config_1.loadConfig)();
71
+ const match = config.pages.find((p) => {
72
+ const mapped = new URL(p.url, config.baseUrl).pathname.replace(/\/+$/, '') || '/';
73
+ return mapped === currentPath;
74
+ });
75
+ if (!match) {
76
+ throw new Error(`Cannot infer the mapping name for ${page.url()} — no entry in config.pages matches this path. Pass { page: '<name>' } explicitly.`);
77
+ }
78
+ return match.name;
79
+ }
80
+ function chain(originalError, message) {
81
+ const reason = originalError instanceof Error ? originalError.message : String(originalError);
82
+ return new Error(`${message}\nOriginal error: ${reason}`);
83
+ }
84
+ //# sourceMappingURL=healer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healer.js","sourceRoot":"","sources":["../../src/playwright/healer.ts"],"names":[],"mappings":";;AAwCA,0BAYC;AAED,wBAaC;AAyDD,0CAKC;AAhID,sCAAmD;AACnD,gCAAgF;AAChF,4CAAgD;AAmChD,MAAM,eAAe,GAAG,IAAI,CAAC;AAEtB,KAAK,UAAU,OAAO,CAC3B,IAAU,EACV,QAAgB,EAChB,UAAyB,EAAE;IAE3B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,MAAM,CAC1B,IAAU,EACV,QAAgB,EAChB,KAAa,EACb,UAAyB,EAAE;IAE3B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI,CACjB,IAAU,EACV,QAAgB,EAChB,OAAsB,EACtB,aAAsB;IAEtB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,IAAI,IAAA,mBAAU,GAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAA,iBAAW,EAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;IACrE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,KAAK,CACT,aAAa,EACb,aAAa,QAAQ,qCAAqC,QAAQ,+BAA+B,QAAQ,6DAA6D,CACvK,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,IAAA,gCAA0B,EAAC,IAAI,CAAC,CAAC;IAE1D,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,cAAc,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,oBAAY,EAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAC/C,IAAI,KAAK,IAAI,WAAW,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACrD,MAAM,KAAK,CACT,aAAa,EACb,6BAA6B,QAAQ,cAAc,QAAQ,MAAM,KAAK,CAAC,KAAK,yBAAyB,MAAM,CAAC,IAAI,WAAW,MAAM,CAAC,IAAI,kBAAkB,MAAM,CAAC,OAAO,IAAI,EAAE,oIAAoI,CACjT,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO;YAAE,cAAc,GAAG,KAAK,CAAC,QAAQ,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,cAAc,IAAI,cAAc,KAAK,QAAQ,EAAE,CAAC;QACnD,MAAM,KAAK,CACT,aAAa,EACb,4BAA4B,QAAQ,cAAc,QAAQ,oCAAoC,MAAM,CAAC,IAAI,WAAW,MAAM,CAAC,IAAI,IAAI,CACpI,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,IAAI,CACV,wCAAwC,QAAQ,qBAAqB,QAAQ,MAAM;QACjF,kBAAkB,cAAc,oBAAoB,MAAM,CAAC,IAAI,YAAY,MAAM,CAAC,IAAI,OAAO;QAC7F,8EAA8E,QAAQ,OAAO,CAChG,CAAC;IACF,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,eAAe,CACnC,OAAsB,EACtB,WAA4B;IAE5B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,IAAU;IAC/B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IAC5E,MAAM,MAAM,GAAG,IAAA,mBAAU,GAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;QAClF,OAAO,MAAM,KAAK,WAAW,CAAC;IAChC,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,qCAAqC,IAAI,CAAC,GAAG,EAAE,oFAAoF,CACpI,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC;AAED,SAAS,KAAK,CAAC,aAAsB,EAAE,OAAe;IACpD,MAAM,MAAM,GAAG,aAAa,YAAY,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC9F,OAAO,IAAI,KAAK,CAAC,GAAG,OAAO,qBAAqB,MAAM,EAAE,CAAC,CAAC;AAC5D,CAAC"}