@ia-qa/self-healing 1.6.7 → 1.6.8

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 (61) hide show
  1. package/README.md +94 -0
  2. package/TUTORIAL.md +112 -2
  3. package/assets/logoKawaiiGreenTiny.png +0 -0
  4. package/dist/cli/args.js +4 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/diff.d.ts +6 -1
  7. package/dist/cli/diff.js +114 -4
  8. package/dist/cli/diff.js.map +1 -1
  9. package/dist/cli/emit.d.ts +71 -0
  10. package/dist/cli/emit.js +150 -0
  11. package/dist/cli/emit.js.map +1 -0
  12. package/dist/cli/fix.js +36 -0
  13. package/dist/cli/fix.js.map +1 -1
  14. package/dist/cli/history.d.ts +12 -0
  15. package/dist/cli/history.js +95 -0
  16. package/dist/cli/history.js.map +1 -0
  17. package/dist/cli/index.js +53 -1
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/init.js +32 -0
  20. package/dist/cli/init.js.map +1 -1
  21. package/dist/cli/map.js +60 -0
  22. package/dist/cli/map.js.map +1 -1
  23. package/dist/cli/run.js +77 -7
  24. package/dist/cli/run.js.map +1 -1
  25. package/dist/cli/ui.d.ts +16 -0
  26. package/dist/cli/ui.js +275 -0
  27. package/dist/cli/ui.js.map +1 -0
  28. package/dist/evidence.d.ts +130 -0
  29. package/dist/evidence.js +232 -0
  30. package/dist/evidence.js.map +1 -0
  31. package/dist/history.d.ts +145 -0
  32. package/dist/history.js +230 -0
  33. package/dist/history.js.map +1 -0
  34. package/dist/htmlReport.d.ts +47 -0
  35. package/dist/htmlReport.js +76 -1
  36. package/dist/htmlReport.js.map +1 -1
  37. package/dist/junit.d.ts +53 -0
  38. package/dist/junit.js +150 -0
  39. package/dist/junit.js.map +1 -0
  40. package/dist/mcp/server.js +41 -0
  41. package/dist/mcp/server.js.map +1 -1
  42. package/dist/ui/icon.d.ts +41 -0
  43. package/dist/ui/icon.js +115 -0
  44. package/dist/ui/icon.js.map +1 -0
  45. package/dist/ui/lock.d.ts +40 -0
  46. package/dist/ui/lock.js +138 -0
  47. package/dist/ui/lock.js.map +1 -0
  48. package/dist/ui/page.d.ts +1 -0
  49. package/dist/ui/page.js +584 -0
  50. package/dist/ui/page.js.map +1 -0
  51. package/dist/ui/registry.d.ts +62 -0
  52. package/dist/ui/registry.js +139 -0
  53. package/dist/ui/registry.js.map +1 -0
  54. package/dist/ui/server.d.ts +67 -0
  55. package/dist/ui/server.js +604 -0
  56. package/dist/ui/server.js.map +1 -0
  57. package/dist/ui/shortcut.d.ts +90 -0
  58. package/dist/ui/shortcut.js +176 -0
  59. package/dist/ui/shortcut.js.map +1 -0
  60. package/package.json +2 -1
  61. package/skills/ia-qa-heal/SKILL.md +27 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * A desktop shortcut that opens this project's console — `ia-qa-heal ui --shortcut`.
3
+ *
4
+ * The console already opens a browser by itself, so the only thing standing
5
+ * between a manual QA and their drift report is a terminal they have no reason
6
+ * to own. This removes it: double-click an icon, the console starts in the
7
+ * right project directory and the browser opens on it. No URL is ever typed,
8
+ * copied, or seen — which is also what keeps the session token out of the way
9
+ * instead of making it something a person has to handle.
10
+ *
11
+ * Nothing about the icon is cosmetic-only: an unlabelled `.cmd` on a desktop is
12
+ * a file people are right to be wary of double-clicking.
13
+ *
14
+ * **Per platform, and the honest limits.**
15
+ *
16
+ * - **Windows** — a real `.lnk`, which is the only shape that can carry a custom
17
+ * icon. It is created by shelling out to PowerShell's `WScript.Shell` (present
18
+ * on every supported Windows, and not a dependency), and it targets a `.cmd`
19
+ * kept *inside* the project rather than a second file on the desktop. If
20
+ * PowerShell refuses, the `.cmd` is placed on the desktop instead and the
21
+ * caller says so — a working shortcut with the default icon beats none.
22
+ * - **Linux** — a `.desktop` entry, whose `Icon=` takes the PNG directly.
23
+ * - **macOS** — a `.command`, with the **system's default icon**. Setting a
24
+ * custom one needs a resource fork via `Rez`/`fileicon` or Finder scripting;
25
+ * both are fragile and neither is worth a silent failure. Stated, not hidden.
26
+ */
27
+ export type Platform = 'win32' | 'darwin' | 'linux';
28
+ export interface ShortcutFile {
29
+ file: string;
30
+ contents: string;
31
+ mode?: number;
32
+ }
33
+ /** A Windows `.lnk`, which cannot be written as text — see `installShortcut`. */
34
+ export interface LnkPlan {
35
+ file: string;
36
+ target: string;
37
+ workingDir: string;
38
+ iconPath: string;
39
+ description: string;
40
+ }
41
+ export interface ShortcutPlan {
42
+ /** Plain-text files to write, in order. */
43
+ files: ShortcutFile[];
44
+ /** Present on Windows only. */
45
+ lnk?: LnkPlan;
46
+ /** Where the .ico has to be written for the lnk to find it. */
47
+ icoPath?: string;
48
+ /** True when this platform gets the system's default icon. */
49
+ defaultIconOnly?: boolean;
50
+ }
51
+ /**
52
+ * The desktop name. It carries the site because this icon sits on the desktop
53
+ * of every QA who uses the tool, which makes it the cheapest honest place to
54
+ * say where it comes from — and because "console" alone, months later, names
55
+ * nothing.
56
+ */
57
+ export declare const SHORTCUT_NAME = "ia-qa-heal console (ia-qa.com)";
58
+ /**
59
+ * What would be written, without writing it. Split out so the shape of every
60
+ * platform's launcher is testable on any platform — the one thing that would
61
+ * otherwise only ever be exercised on the maintainer's own OS.
62
+ */
63
+ export declare function planShortcut(opts: {
64
+ platform: Platform;
65
+ desktop: string;
66
+ projectDir: string;
67
+ configDir: string;
68
+ nodePath: string;
69
+ cliEntry: string;
70
+ logoPath: string;
71
+ name?: string;
72
+ }): ShortcutPlan;
73
+ /**
74
+ * Create the Windows `.lnk`. PowerShell's COM shell object is the only way to
75
+ * write one without either a native module or hand-assembling the binary Shell
76
+ * Link format. Returns false rather than throwing: the caller has a working
77
+ * fallback and a shortcut is not worth failing a command over.
78
+ *
79
+ * Every value is passed as an environment variable, never interpolated into the
80
+ * script text — a project path containing a quote would otherwise end up being
81
+ * executed as PowerShell.
82
+ */
83
+ export declare function createWindowsLnk(lnk: LnkPlan): boolean;
84
+ /**
85
+ * Where the desktop is. `XDG_DESKTOP_DIR` and localized folder names exist and
86
+ * are not resolvable without reading user config, so a missing `Desktop` falls
87
+ * back to the home directory rather than guessing wrong and writing an icon
88
+ * nobody finds.
89
+ */
90
+ export declare function desktopDir(home?: string): string;
@@ -0,0 +1,176 @@
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.SHORTCUT_NAME = void 0;
37
+ exports.planShortcut = planShortcut;
38
+ exports.createWindowsLnk = createWindowsLnk;
39
+ exports.desktopDir = desktopDir;
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ /**
44
+ * The desktop name. It carries the site because this icon sits on the desktop
45
+ * of every QA who uses the tool, which makes it the cheapest honest place to
46
+ * say where it comes from — and because "console" alone, months later, names
47
+ * nothing.
48
+ */
49
+ exports.SHORTCUT_NAME = 'ia-qa-heal console (ia-qa.com)';
50
+ function quoteWin(s) {
51
+ return `"${s.replace(/"/g, '""')}"`;
52
+ }
53
+ function quotePosix(s) {
54
+ return `'${s.replace(/'/g, `'\\''`)}'`;
55
+ }
56
+ /**
57
+ * What would be written, without writing it. Split out so the shape of every
58
+ * platform's launcher is testable on any platform — the one thing that would
59
+ * otherwise only ever be exercised on the maintainer's own OS.
60
+ */
61
+ function planShortcut(opts) {
62
+ const name = opts.name ?? exports.SHORTCUT_NAME;
63
+ const description = 'Open the local selector-drift console for this project — ia-qa.com';
64
+ if (opts.platform === 'win32') {
65
+ // The .cmd lives in the project, not on the desktop: the desktop gets one
66
+ // icon, not a launcher plus its helper.
67
+ const cmd = path.join(opts.configDir, 'console.cmd');
68
+ return {
69
+ files: [
70
+ {
71
+ file: cmd,
72
+ contents: `@echo off\r\n` +
73
+ `title ${name}\r\n` +
74
+ `cd /d ${quoteWin(opts.projectDir)}\r\n` +
75
+ `echo ia-qa-heal console - ia-qa.com\r\n` +
76
+ `echo Starting... your browser will open on its own.\r\n` +
77
+ `echo Close this window to stop it.\r\n` +
78
+ `echo.\r\n` +
79
+ `${quoteWin(opts.nodePath)} ${quoteWin(opts.cliEntry)} ui\r\n` +
80
+ `if errorlevel 1 pause\r\n`,
81
+ },
82
+ ],
83
+ lnk: {
84
+ file: path.join(opts.desktop, `${name}.lnk`),
85
+ target: cmd,
86
+ workingDir: opts.projectDir,
87
+ iconPath: path.join(opts.configDir, 'console.ico'),
88
+ description,
89
+ },
90
+ icoPath: path.join(opts.configDir, 'console.ico'),
91
+ };
92
+ }
93
+ if (opts.platform === 'darwin') {
94
+ return {
95
+ files: [
96
+ {
97
+ file: path.join(opts.desktop, `${name}.command`),
98
+ contents: `#!/bin/sh\n` +
99
+ `# ${description}\n` +
100
+ `cd ${quotePosix(opts.projectDir)} || exit 1\n` +
101
+ `echo " ia-qa-heal console - ia-qa.com"\n` +
102
+ `echo " Starting... your browser will open on its own."\n` +
103
+ `echo " Close this window to stop it."\n` +
104
+ `exec ${quotePosix(opts.nodePath)} ${quotePosix(opts.cliEntry)} ui\n`,
105
+ mode: 0o755,
106
+ },
107
+ ],
108
+ defaultIconOnly: true,
109
+ };
110
+ }
111
+ return {
112
+ files: [
113
+ {
114
+ file: path.join(opts.desktop, `${name}.desktop`),
115
+ contents: `[Desktop Entry]\n` +
116
+ `Type=Application\n` +
117
+ `Name=${name}\n` +
118
+ `Comment=${description}\n` +
119
+ `Icon=${opts.logoPath}\n` +
120
+ `Exec=sh -c "cd ${quotePosix(opts.projectDir)} && ${quotePosix(opts.nodePath)} ${quotePosix(opts.cliEntry)} ui"\n` +
121
+ `Terminal=true\n` +
122
+ `Categories=Development;\n`,
123
+ mode: 0o755,
124
+ },
125
+ ],
126
+ };
127
+ }
128
+ /**
129
+ * Create the Windows `.lnk`. PowerShell's COM shell object is the only way to
130
+ * write one without either a native module or hand-assembling the binary Shell
131
+ * Link format. Returns false rather than throwing: the caller has a working
132
+ * fallback and a shortcut is not worth failing a command over.
133
+ *
134
+ * Every value is passed as an environment variable, never interpolated into the
135
+ * script text — a project path containing a quote would otherwise end up being
136
+ * executed as PowerShell.
137
+ */
138
+ function createWindowsLnk(lnk) {
139
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
140
+ const { execFileSync } = require('child_process');
141
+ const script = '$s = (New-Object -ComObject WScript.Shell).CreateShortcut($env:IAQA_LNK); ' +
142
+ '$s.TargetPath = $env:IAQA_TARGET; ' +
143
+ '$s.WorkingDirectory = $env:IAQA_CWD; ' +
144
+ '$s.Description = $env:IAQA_DESC; ' +
145
+ 'if (Test-Path $env:IAQA_ICON) { $s.IconLocation = $env:IAQA_ICON }; ' +
146
+ '$s.Save()';
147
+ try {
148
+ execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
149
+ stdio: 'ignore',
150
+ timeout: 15000,
151
+ env: {
152
+ ...process.env,
153
+ IAQA_LNK: lnk.file,
154
+ IAQA_TARGET: lnk.target,
155
+ IAQA_CWD: lnk.workingDir,
156
+ IAQA_DESC: lnk.description,
157
+ IAQA_ICON: lnk.iconPath,
158
+ },
159
+ });
160
+ return fs.existsSync(lnk.file);
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
166
+ /**
167
+ * Where the desktop is. `XDG_DESKTOP_DIR` and localized folder names exist and
168
+ * are not resolvable without reading user config, so a missing `Desktop` falls
169
+ * back to the home directory rather than guessing wrong and writing an icon
170
+ * nobody finds.
171
+ */
172
+ function desktopDir(home = os.homedir()) {
173
+ const candidate = path.join(home, 'Desktop');
174
+ return fs.existsSync(candidate) ? candidate : home;
175
+ }
176
+ //# sourceMappingURL=shortcut.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcut.js","sourceRoot":"","sources":["../../src/ui/shortcut.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-qa/self-healing",
3
- "version": "1.6.7",
3
+ "version": "1.6.8",
4
4
  "description": "Local-first self-healing for UI tests: a local MCP server + CLI that map your app's pages to a role/name/selector contract, diff selector drift (PASS/FIX/BLOCK), and apply deterministic fixes to Cypress/Playwright/Selenium tests. Runs on your machine — nothing leaves it.",
5
5
  "keywords": [
6
6
  "self-healing",
@@ -53,6 +53,7 @@
53
53
  "files": [
54
54
  "dist",
55
55
  "skills",
56
+ "assets",
56
57
  "README.md",
57
58
  "TUTORIAL.md",
58
59
  "ROADMAP.md"
@@ -38,6 +38,8 @@ Where things live:
38
38
  | `.ia-qa/mapping/_layouts/` | elements shared by every page (navbar, footer) — one fix, not N |
39
39
  | `.ia-qa/usage.json` | the locators the suite actually uses (produced by `ingest`) |
40
40
  | `.ia-qa/mapping/_overview.md`, `_navigation.md`/`.svg` | app index + navigation graph |
41
+ | `.ia-qa/history.jsonl` | one line per `diff`/`run`: verdict, counts, capture source, page coverage |
42
+ | `.ia-qa/mapping/_shots/` | page screenshots + element positions from `map --screenshots` — artefacts, never diffed |
41
43
 
42
44
  Open a browser only when the contract *cannot* answer: the page is not mapped, or the element
43
45
  exists only in a state nothing captured (behind a tab, a modal, a mode toggle).
@@ -54,10 +56,19 @@ exists only in a state nothing captured (behind a tab, a modal, a mode toggle).
54
56
  | "give me the app's structure" | `graph --format mermaid\|svg\|json\|markdown` |
55
57
  | "apply the rewrites" | `fix` — `--dry-run` first, always |
56
58
  | "what do my tests actually use?" | `ingest` |
59
+ | "is this getting better or worse?" | `history` (`--json`) — the trend no single run can reconstruct |
60
+ | "gate my pipeline on it" | `diff --junit <file>` — every major CI charts the trend natively |
61
+ | "let me look at it / decide myself" | tell the human to run `ia-qa-heal ui` — **do not run it yourself** (§4.8) |
57
62
  | first-time setup | write `.ia-qa/config.json` directly — `init` is a TTY wizard and **will refuse in your shell** |
58
63
 
59
64
  Exact flags: `ia-qa-heal <verb> --help`. Do not guess them from this file.
60
65
 
66
+ ### Machine-readable outputs
67
+
68
+ `diff`, `run`, `audit`, `check` and `history` all take `--json`. Prefer it over parsing prose.
69
+ Under `run --json`, every human line moves to stderr so stdout stays one parseable document.
70
+ `diff --junit` / `run --junit` additionally write JUnit XML — the format a CI already knows.
71
+
61
72
  ## 3. Reading a verdict
62
73
 
63
74
  - **PASS** — nothing moved.
@@ -93,9 +104,25 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
93
104
  7. **Renames need an inventory.** `diff` can only gate on a changed *label* if `ingest` has
94
105
  inventoried what the suite uses. No `usage.json` ⇒ no name escalation — so if their tests
95
106
  locate by `getByRole(..., { name })`, run `ingest` before trusting a PASS.
107
+ 8. **Never run `ia-qa-heal ui`.** It starts a local web console and **blocks until a human closes
108
+ the browser tab** — in your shell that is a hung command, not a result. It also refuses to
109
+ start under CI for the same reason. When the verdict is BLOCK and the human has to choose
110
+ between candidates, *tell them the command*; they run it. Everything the console shows is
111
+ available to you as JSON (`diff --json`) — the console adds pictures for a person, not data
112
+ for you.
113
+ 9. **Never read an empty history as "nothing drifted".** `.ia-qa/history.jsonl` is written by
114
+ `diff` and `run`; absent means *not recorded*, exactly like a missing `usage.json` means *not
115
+ measured*. And when you report a trend, repeat what it excluded: runs captured a different way
116
+ (a `map` baseline vs a live suite capture) are deliberately held out, because charting both
117
+ invents a trend out of a capture artifact. `history --json` and the `heal_history` MCP tool
118
+ both hand you that exclusion — pass it on rather than presenting the series as complete.
96
119
 
97
120
  ## 5. What leaves the machine
98
121
 
99
122
  Nothing, except: requests to the user's **own** `baseUrl` (`check`'s link half, `discover --crawl`
100
123
  — GET only, same origin, never a `/logout` or `/delete` URL), and `ia-qa-heal-ai` calling the
101
124
  user's own LLM if they opted in. Say that plainly rather than implying more or less.
125
+
126
+ `ui` does not change that: it binds `127.0.0.1` only, is gated by a per-process token, and serves a
127
+ page with no external asset. If asked whether it "hosts" anything, the answer is no — it is a local
128
+ process that dies with the browser tab.