@ia-qa/self-healing 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,23 @@ npm i -D @ia-qa/self-healing
33
33
  npx ia-qa-heal init
34
34
  ```
35
35
 
36
+ ## Working with an AI agent — `ia-qa-heal skill`
37
+
38
+ An agent that asks a browser where a button went is doing by hand what `.ia-qa/mapping/*.json` already answers offline: every interactive element's **role, accessible name and selector**, captured. A `link` the suite still calls a `button` shows up in one `grep`.
39
+
40
+ `ia-qa-heal skill` installs that reflex — plus the rules that must not be broken — as a skill your agent loads:
41
+
42
+ ```bash
43
+ ia-qa-heal skill # where it would go, and whether it is current — writes nothing
44
+ ia-qa-heal skill --install # → ./.claude/skills/ia-qa-heal/SKILL.md
45
+ ia-qa-heal skill --install --user # → ~/.claude/skills/, for every project on the machine
46
+ ia-qa-heal skill --print # to stdout, for a different agent's format
47
+ ```
48
+
49
+ It is short on purpose (which verb answers which question; the flags stay in `--help`) and its centre of gravity is the **prohibitions** — never hand-rewrite a locator reported `unattributable`, never gate on `audit`, never read a green `run` as a green app, never diff a `map` baseline against a `run` capture. Those are the mistakes only an agent makes, and it makes them while trying to help.
50
+
51
+ Installing is a separate step because agents read skills from `.claude/skills/`, never from `node_modules` — shipped alone, the file would never be loaded. The command is non-interactive, idempotent, prints the absolute path before writing, and refuses to overwrite a copy you have edited unless you pass `--force`.
52
+
36
53
  ## The whole flow — one tool, or all three together
37
54
 
38
55
  This one package ships **three commands**. Use just the first, or chain all three — they read the same contract, so they never disagree.
package/TUTORIAL.md CHANGED
@@ -105,7 +105,7 @@ Find your agent's MCP config file and add the `ia-qa-self-healing` block below.
105
105
  }
106
106
  ```
107
107
 
108
- > ⚠️ Copy it exactly — the `-p` matters. The package ships two commands (`ia-qa-heal` and `ia-qa-heal-mcp`), and `-p` is what tells `npx` which one to run. Without it, nothing starts.
108
+ > ⚠️ Copy it exactly — the `-p` matters. The package ships three commands (`ia-qa-heal`, `ia-qa-heal-mcp`, and the optional `ia-qa-heal-ai` — see [§9](#9-optional-ai-suggestions-for-the-lost-ones-byok)), and `-p` is what tells `npx` which one to run. Without it, nothing starts.
109
109
 
110
110
  <details>
111
111
  <summary><b>Running from the source repo instead?</b> (click)</summary>
@@ -132,6 +132,20 @@ Then point the config at the built file (use the **absolute** path):
132
132
  ```
133
133
  </details>
134
134
 
135
+ ### Step 4.1b — Teach your agent how to use it (recommended)
136
+
137
+ The MCP block above gives your agent four *tools*. It does not give it the **judgment** to use them: when to read the contract instead of opening a browser, which verb answers which question, and — most importantly — which locators it must never rewrite on its own.
138
+
139
+ ```bash
140
+ npx -y -p @ia-qa/self-healing ia-qa-heal skill --install
141
+ ```
142
+
143
+ That writes `.claude/skills/ia-qa-heal/SKILL.md` in your project (`--user` puts it in `~/.claude/skills/` for every project on the machine; run it with no flag first to see where it would go). Agents read skills from `.claude/skills/` only — installed in `node_modules`, the file is never loaded, which is why this is a separate command.
144
+
145
+ Why it matters: without it, an agent that sees a renamed button reported as **not rewritable** tends to "help" by editing the test anyway. Those cases (`getByText`, `cy.contains`) name a *string*, not an element — nothing proves the test meant the renamed button rather than a heading that never moved, so editing one **breaks a test that was passing**. The skill states that, and the other three rules like it.
146
+
147
+ It is safe to re-run: identical content is a no-op, and it refuses to overwrite a copy you edited unless you pass `--force`.
148
+
135
149
  ### Step 4.2 — Restart your agent
136
150
 
137
151
  Fully quit and reopen it. Otherwise it won't see the new tool.
@@ -278,6 +292,35 @@ Plus, for the whole app:
278
292
 
279
293
  Commit them.
280
294
 
295
+ ### Step 5.4 — Day one, before any drift: `audit`
296
+
297
+ Everything after this point pays off at the *next* UI change. `audit` pays off now: it asks **does my test suite still name things that exist?**
298
+
299
+ ```bash
300
+ npx ia-qa-heal ingest tests/ # inventory what your suite actually uses → .ia-qa/usage.json
301
+ npx ia-qa-heal audit # cross-reference it against what you just mapped
302
+ ```
303
+
304
+ It reads your locators, checks each one against the current mapping, and lists the ones that match no element on any mapped page. No baseline needed, nothing is ever edited.
305
+
306
+ > ⚠️ **It is advisory on purpose — exit code 0 unless you pass `--strict`.** A locator it calls "missing" may simply live on a page you haven't mapped, or in a state nothing captured (behind a tab, a modal, a mode toggle). Treat the output as a list to look at, **not as a CI gate**. `--strict` exists for the day your mapping really does cover everything.
307
+
308
+ It only judges locators that state a role — `getByRole`, `getByLabel`, `By.linkText`. A `getByText('Save')` is counted but never judged, because the contract holds interactive elements only and nothing proves that text was a button.
309
+
310
+ ### Step 5.5 — Is the app itself sound? `check`
311
+
312
+ A diff can tell you nothing moved. It cannot tell you the page was already broken — a green diff over a page whose links all 404 is still green. `check` asks that other question:
313
+
314
+ ```bash
315
+ npx ia-qa-heal check # add --offline to skip the link requests entirely
316
+ ```
317
+
318
+ Four findings: **dead links** (a fail — the app itself stated that URL), **elements with no accessible name** (an icon button your tests can only reach by CSS), **two elements sharing one role + name** (a locator coin flip — drift that hasn't happened yet), and **pages nothing links to**. Findings are de-duplicated across pages: a broken navbar link on 142 pages is one finding, because it's one fix.
319
+
320
+ > 🔒 The link check is the only part that touches the network, and it only ever requests **your own `baseUrl`** — GET only, same site, and never a URL that acts (`/logout`, `/delete`…). It prints the host and the number of requests *before* the first one. `--offline` removes that half completely.
321
+
322
+ Warnings exit 0 (`--strict` makes them fail); a dead link always fails.
323
+
281
324
  ---
282
325
 
283
326
  ## 6. Your first real workflow (the whole point)
@@ -598,7 +641,12 @@ npx ia-qa-heal fix .ia-qa/baseline/checkout.json .ia-qa/mapping/checkout.json te
598
641
  npx ia-qa-heal fix .ia-qa/baseline/checkout.json .ia-qa/mapping/checkout.json tests/ # apply (never commits)
599
642
 
600
643
  npx ia-qa-heal ingest tests/ # inventory the selectors your suite uses → .ia-qa/usage.json
644
+ npx ia-qa-heal audit # day one, no baseline: do my locators still name anything? (advisory)
645
+ npx ia-qa-heal check # is the app sound? dead links · unnamed elements · name collisions · orphan pages
646
+ npx ia-qa-heal graph --format markdown # the navigation graph, one card per page (RAG chunk / Confluence)
601
647
  npx ia-qa-heal run # the whole loop: capture (your suite, IAQA_CAPTURE=1) → diff → confirm → fix → re-run to verify
648
+
649
+ npx ia-qa-heal skill --install # teach your AI agent the rules (→ .claude/skills/ia-qa-heal/)
602
650
  ```
603
651
 
604
652
  Talking to an agent instead:
package/dist/cli/args.js CHANGED
@@ -26,6 +26,7 @@ exports.KNOWN_FLAGS = {
26
26
  check: ['--strict', '--json', '--offline'],
27
27
  graph: ['--format', '--kind', '--out', '--open'],
28
28
  run: ['--yes', '--no-verify', '--locators', '--report', '--open'],
29
+ skill: ['--install', '--user', '--dir', '--print', '--force'],
29
30
  };
30
31
  function rejectUnknownFlags(command, args) {
31
32
  const known = exports.KNOWN_FLAGS[command];
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;AA0BA,gDAeC;AAzCD;;;;;;;;;;;GAWG;AACU,QAAA,WAAW,GAAsC;IAC5D,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC;IACjG,GAAG,EAAE,EAAE;IACP,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;IAC3D,GAAG,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC;IACzC,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC7B,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC;IAC1C,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC;IAChD,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC;CAClE,CAAC;AAEF,SAAgB,kBAAkB,CAAC,OAAe,EAAE,IAAc;IAChE,MAAM,KAAK,GAAG,mBAAW,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAChC,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,eAAe,OAAO,0FAA0F;gBAC9G,4GAA4G;gBAC5G,8FAA8F,CACjG,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,qBAAqB,OAAO,8CAA8C,CAAC,CAAC;IAChH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;AA2BA,gDAeC;AA1CD;;;;;;;;;;;GAWG;AACU,QAAA,WAAW,GAAsC;IAC5D,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC;IACjG,GAAG,EAAE,EAAE;IACP,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;IAC3D,GAAG,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC;IACzC,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC7B,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC;IAC1C,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC;IAChD,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC;IACjE,KAAK,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC;CAC9D,CAAC;AAEF,SAAgB,kBAAkB,CAAC,OAAe,EAAE,IAAc;IAChE,MAAM,KAAK,GAAG,mBAAW,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAChC,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,eAAe,OAAO,0FAA0F;gBAC9G,4GAA4G;gBAC5G,8FAA8F,CACjG,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,qBAAqB,OAAO,8CAA8C,CAAC,CAAC;IAChH,CAAC;AACH,CAAC"}
package/dist/cli/index.js CHANGED
@@ -14,6 +14,7 @@ const check_1 = require("./check");
14
14
  const run_1 = require("./run");
15
15
  const discover_1 = require("./discover");
16
16
  const graph_1 = require("./graph");
17
+ const skill_1 = require("./skill");
17
18
  // dist/cli/ → package root; npm ships package.json in every tarball.
18
19
  const VERSION = require('../../package.json').version;
19
20
  const HELP = `ia-qa-heal — self-healing E2E toolkit (MVP)
@@ -27,6 +28,9 @@ drifted. In non-interactive shells, write .ia-qa/config.json directly instead
27
28
  of \`init\` (see the wizard's non-TTY guidance). The AI layer is the separate
28
29
  \`ia-qa-heal-ai\` binary (BYOK suggestions for lost/ambiguous rows) — this
29
30
  binary has no --ai flag: the loop and its CI verdict stay deterministic.
31
+ Read .ia-qa/mapping/<page>.json before opening a browser — it already holds
32
+ every element's role, accessible name and selector. \`ia-qa-heal skill\` installs
33
+ that reflex, and the rules you must not break, as a skill in your project.
30
34
 
31
35
  Usage:
32
36
  ia-qa-heal init Interactive setup wizard (writes .ia-qa/config.json)
@@ -113,6 +117,17 @@ Usage:
113
117
  --report [file.html] branded ia-qa.com HTML report — for run it
114
118
  also carries the applied old→new edits and the verify verdict
115
119
  --open open that report in the browser (local, not CI)
120
+ ia-qa-heal skill Show / install the agent instructions for this CLI as a
121
+ skill: read the contract instead of opening a browser,
122
+ which verb answers which question, and what must never
123
+ be rewritten. No arguments: print where it would go and
124
+ whether it is current. Agents only load skills from
125
+ .claude/skills/ — in node_modules it is never read.
126
+ --install write <root>/.claude/skills/ia-qa-heal/SKILL.md
127
+ --user install into ~/.claude/skills/ instead
128
+ --dir <path> project root to install into (default: cwd)
129
+ --print write the skill to stdout, install nothing
130
+ --force overwrite a locally modified copy
116
131
  ia-qa-heal version Print the installed version (also --version, -v)
117
132
  ia-qa-heal help Show this help
118
133
 
@@ -205,6 +220,9 @@ async function main() {
205
220
  case 'run':
206
221
  await (0, run_1.runRun)(rest);
207
222
  break;
223
+ case 'skill':
224
+ await (0, skill_1.runSkill)(rest);
225
+ break;
208
226
  case 'version':
209
227
  case '--version':
210
228
  case '-v':
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `ia-qa-heal skill` — install the agent instructions for this CLI.
3
+ *
4
+ * Shipping a SKILL.md inside the tarball does nothing on its own: Claude Code loads
5
+ * skills from `.claude/skills/` (project) or `~/.claude/skills/` (user), never from
6
+ * node_modules. So the markdown is the payload and this verb is the load-bearing
7
+ * half — without it the file is a fourth copy of the doctrine nobody reads.
8
+ *
9
+ * `init` cannot host this: it is an interactive wizard that refuses without a TTY,
10
+ * which is exactly the population that needs the skill. This verb is therefore
11
+ * non-interactive by construction, idempotent, and announces the absolute path
12
+ * before writing — writing into someone's `.claude/` uninvited is the same
13
+ * overreach as `discover --apply` doing more than it says.
14
+ */
15
+ export declare const SKILL_NAME = "ia-qa-heal";
16
+ interface SkillOpts {
17
+ install: boolean;
18
+ user: boolean;
19
+ print: boolean;
20
+ force: boolean;
21
+ dir?: string;
22
+ }
23
+ /**
24
+ * `dist/cli/` and `src/cli/` are both two levels under the package root, so the
25
+ * same relative path resolves whether this runs from the published build or from
26
+ * the TypeScript sources under test.
27
+ */
28
+ export declare function skillSourcePath(): string;
29
+ export declare function readSkill(): string;
30
+ /** Target is a project (or home) root; the skill always lands in `<root>/.claude/skills/<name>/`. */
31
+ export declare function skillTargetDir(opts: Pick<SkillOpts, 'user' | 'dir'>): string;
32
+ export declare function runSkill(args: string[]): Promise<void>;
33
+ export {};
@@ -0,0 +1,149 @@
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.SKILL_NAME = void 0;
37
+ exports.skillSourcePath = skillSourcePath;
38
+ exports.readSkill = readSkill;
39
+ exports.skillTargetDir = skillTargetDir;
40
+ exports.runSkill = runSkill;
41
+ const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
43
+ const path = __importStar(require("path"));
44
+ /**
45
+ * `ia-qa-heal skill` — install the agent instructions for this CLI.
46
+ *
47
+ * Shipping a SKILL.md inside the tarball does nothing on its own: Claude Code loads
48
+ * skills from `.claude/skills/` (project) or `~/.claude/skills/` (user), never from
49
+ * node_modules. So the markdown is the payload and this verb is the load-bearing
50
+ * half — without it the file is a fourth copy of the doctrine nobody reads.
51
+ *
52
+ * `init` cannot host this: it is an interactive wizard that refuses without a TTY,
53
+ * which is exactly the population that needs the skill. This verb is therefore
54
+ * non-interactive by construction, idempotent, and announces the absolute path
55
+ * before writing — writing into someone's `.claude/` uninvited is the same
56
+ * overreach as `discover --apply` doing more than it says.
57
+ */
58
+ exports.SKILL_NAME = 'ia-qa-heal';
59
+ /**
60
+ * `dist/cli/` and `src/cli/` are both two levels under the package root, so the
61
+ * same relative path resolves whether this runs from the published build or from
62
+ * the TypeScript sources under test.
63
+ */
64
+ function skillSourcePath() {
65
+ return path.join(__dirname, '..', '..', 'skills', exports.SKILL_NAME, 'SKILL.md');
66
+ }
67
+ function readSkill() {
68
+ const src = skillSourcePath();
69
+ if (!fs.existsSync(src)) {
70
+ throw new Error(`Skill source missing at ${src}.\n` +
71
+ `The installed @ia-qa/self-healing package is incomplete — reinstall it.`);
72
+ }
73
+ return fs.readFileSync(src, 'utf8');
74
+ }
75
+ /** Target is a project (or home) root; the skill always lands in `<root>/.claude/skills/<name>/`. */
76
+ function skillTargetDir(opts) {
77
+ const root = opts.dir !== undefined ? opts.dir : opts.user ? os.homedir() : process.cwd();
78
+ return path.join(root, '.claude', 'skills', exports.SKILL_NAME);
79
+ }
80
+ function parseArgs(args) {
81
+ const opts = { install: false, user: false, print: false, force: false };
82
+ for (let i = 0; i < args.length; i++) {
83
+ const a = args[i];
84
+ if (a === '--install')
85
+ opts.install = true;
86
+ else if (a === '--user')
87
+ opts.user = true;
88
+ else if (a === '--print')
89
+ opts.print = true;
90
+ else if (a === '--force')
91
+ opts.force = true;
92
+ else if (a === '--dir') {
93
+ const next = args[i + 1];
94
+ if (next === undefined || next.startsWith('-')) {
95
+ throw new Error('--dir needs a directory argument, e.g. --dir ../my-app.');
96
+ }
97
+ opts.dir = next;
98
+ i++;
99
+ }
100
+ else if (a.startsWith('--dir=')) {
101
+ opts.dir = a.slice('--dir='.length);
102
+ }
103
+ }
104
+ if (opts.user && opts.dir !== undefined) {
105
+ throw new Error('--user and --dir target different roots — pass only one.');
106
+ }
107
+ return opts;
108
+ }
109
+ async function runSkill(args) {
110
+ const opts = parseArgs(args);
111
+ const content = readSkill();
112
+ if (opts.print) {
113
+ process.stdout.write(content);
114
+ return;
115
+ }
116
+ const dir = skillTargetDir(opts);
117
+ const file = path.join(dir, 'SKILL.md');
118
+ const exists = fs.existsSync(file);
119
+ const current = exists ? fs.readFileSync(file, 'utf8') : null;
120
+ if (!opts.install) {
121
+ console.log('\n🧠 ia-qa-heal skill — agent instructions for this CLI\n');
122
+ console.log(` source: ${skillSourcePath()}`);
123
+ console.log(` target: ${file}`);
124
+ if (!exists)
125
+ console.log(' status: not installed → ia-qa-heal skill --install');
126
+ else if (current === content)
127
+ console.log(' status: installed, up to date');
128
+ else
129
+ console.log(' status: installed, differs from this version → --install --force to overwrite');
130
+ console.log('\n Agents load skills from .claude/skills/ (project) or ~/.claude/skills/ (--user).');
131
+ console.log(' Sitting in node_modules, it is never read — it has to land in one of those.\n');
132
+ return;
133
+ }
134
+ if (current === content) {
135
+ console.log(`\n✅ Already up to date: ${file}\n`);
136
+ return;
137
+ }
138
+ if (exists && !opts.force) {
139
+ throw new Error(`${file} already exists and differs from the version in this package.\n` +
140
+ `It may have been edited on purpose, so this stops rather than overwrite it.\n` +
141
+ `Compare first — ia-qa-heal skill --print > SKILL.new.md\n` +
142
+ `Then overwrite — ia-qa-heal skill --install --force`);
143
+ }
144
+ console.log(`\n📝 Writing ${file}`);
145
+ fs.mkdirSync(dir, { recursive: true });
146
+ fs.writeFileSync(file, content, 'utf8');
147
+ console.log(`✅ ${exists ? 'Updated' : 'Installed'} — start a new agent session to load it.\n`);
148
+ }
149
+ //# sourceMappingURL=skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/cli/skill.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.2",
3
+ "version": "1.6.3",
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",
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "files": [
54
54
  "dist",
55
+ "skills",
55
56
  "README.md",
56
57
  "TUTORIAL.md",
57
58
  "ROADMAP.md"
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: ia-qa-heal
3
+ description: >-
4
+ Repair, audit and reason about E2E test locators (Playwright, Cypress, Selenium) by
5
+ reading a local page contract instead of opening a browser. Use when a UI test fails on
6
+ a locator (selector not found, strict-mode violation, a renamed button or label), when
7
+ tests must be updated after a UI change, when asked which pages a suite covers, or
8
+ whether a suite still names elements that exist. Requires @ia-qa/self-healing
9
+ (`ia-qa-heal`). Runs entirely on the user's machine.
10
+ ---
11
+
12
+ # ia-qa-heal — heal locators from the contract, not from a browser
13
+
14
+ ## 1. Read the contract before you open anything
15
+
16
+ `.ia-qa/mapping/<page>.json` is a captured snapshot of every interactive element on a page:
17
+
18
+ ```json
19
+ {
20
+ "page": "checkout", "url": "/checkout", "capturedAt": "2026-08-02T17:40:20.067Z",
21
+ "elements": [
22
+ { "role": "button", "name": "Pay now", "selector": "#root form > button", "context": "main" },
23
+ { "role": "link", "name": "Back to cart", "selector": "a[href=\"/cart\"]", "context": "nav", "href": "/cart" }
24
+ ]
25
+ }
26
+ ```
27
+
28
+ A failing locator is usually answered by **reading that file**: the element's current role, its
29
+ accessible name, a selector that works. A test asserting a `button` where the page now renders a
30
+ `link` is one `grep` away — no browser, no screenshot, no round-trip. Do this first.
31
+
32
+ Where things live:
33
+
34
+ | Path | What it is |
35
+ |---|---|
36
+ | `.ia-qa/mapping/` | the current capture |
37
+ | `.ia-qa/baseline/` | the committed reference `diff` compares against |
38
+ | `.ia-qa/mapping/_layouts/` | elements shared by every page (navbar, footer) — one fix, not N |
39
+ | `.ia-qa/usage.json` | the locators the suite actually uses (produced by `ingest`) |
40
+ | `.ia-qa/mapping/_overview.md`, `_navigation.md`/`.svg` | app index + navigation graph |
41
+
42
+ Open a browser only when the contract *cannot* answer: the page is not mapped, or the element
43
+ exists only in a state nothing captured (behind a tab, a modal, a mode toggle).
44
+
45
+ ## 2. Which verb answers which question
46
+
47
+ | The user's question | Verb |
48
+ |---|---|
49
+ | "my tests broke — fix them" | `run` (capture during their suite → diff → confirm → fix → re-run to verify) |
50
+ | "did anything drift?" | `diff` → PASS/FIX/BLOCK (the CI gate) |
51
+ | "day one, no baseline — is my suite still valid?" | `audit` |
52
+ | "is the app I mapped even sound?" | `check` (dead links · unnamed elements · name collisions · orphan pages) |
53
+ | "which pages am I *not* covering?" | `discover` (`--sitemap` / `--crawl`; suggests only) |
54
+ | "give me the app's structure" | `graph --format mermaid\|svg\|json\|markdown` |
55
+ | "apply the rewrites" | `fix` — `--dry-run` first, always |
56
+ | "what do my tests actually use?" | `ingest` |
57
+ | first-time setup | write `.ia-qa/config.json` directly — `init` is a TTY wizard and **will refuse in your shell** |
58
+
59
+ Exact flags: `ia-qa-heal <verb> --help`. Do not guess them from this file.
60
+
61
+ ## 3. Reading a verdict
62
+
63
+ - **PASS** — nothing moved.
64
+ - **FIX** — every drifted element was re-identified; `fix` rewrites them deterministically.
65
+ - **BLOCK** — a human decides.
66
+
67
+ Row statuses: `ok`, `renamed`, `healable`, `ambiguous`, `lost`, `added`. Any row may also carry
68
+ `rebound: true` + `reboundTo`.
69
+
70
+ BLOCK is raised by `lost` (element gone), `ambiguous` (several candidates — a coin flip), `rebound`
71
+ (the old selector still resolves, but onto a **different** element: the test stays green while
72
+ clicking the wrong thing), or a name-drift finding that is not attributable.
73
+
74
+ ## 4. Never do this
75
+
76
+ 1. **Never hand-rewrite a locator reported `unattributable`.** `getByText`, `cy.contains`,
77
+ `getByTitle`, `getByAltText` name a **string, not an element** — nothing proves the test meant
78
+ the renamed button rather than a heading that never moved. They are reported with `file:line`
79
+ and hold the verdict at BLOCK *on purpose*. Editing one breaks a test that was **passing** —
80
+ the only outcome worse than the drift. Surface it to the human instead.
81
+ 2. **Never treat `audit` as a gate.** It is advisory (exit 0) unless `--strict`: a "missing"
82
+ locator may live on an unmapped page, or an unmapped *state*.
83
+ 3. **Never call a green `run` a green app.** `run` only re-captures the pages the suite actually
84
+ exercised; the rest keep the baseline's `capturedAt`, are `stale`, and sit outside the verdict.
85
+ Read the coverage line it prints before concluding anything.
86
+ 4. **Never diff a `map` baseline against a `run` capture.** The two read a page differently and
87
+ produce phantom drift. The tool warns — take the warning, don't explain it away.
88
+ 5. **Never run `fix` blind.** `--dry-run` first, show the plan, then apply. It never commits;
89
+ neither should you without the human seeing the diff.
90
+ 6. **There is no `--ai` flag on `ia-qa-heal`,** and adding one is not the fix — the gate stays
91
+ deterministic by design. The BYOK layer is the separate `ia-qa-heal-ai` binary: it *suggests*
92
+ matches for `lost`/`ambiguous` rows for a human to confirm, and never gates CI.
93
+ 7. **Renames need an inventory.** `diff` can only gate on a changed *label* if `ingest` has
94
+ inventoried what the suite uses. No `usage.json` ⇒ no name escalation — so if their tests
95
+ locate by `getByRole(..., { name })`, run `ingest` before trusting a PASS.
96
+
97
+ ## 5. What leaves the machine
98
+
99
+ Nothing, except: requests to the user's **own** `baseUrl` (`check`'s link half, `discover --crawl`
100
+ — GET only, same origin, never a `/logout` or `/delete` URL), and `ia-qa-heal-ai` calling the
101
+ user's own LLM if they opted in. Say that plainly rather than implying more or less.