@ia-qa/self-healing 1.18.0 → 1.19.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 (51) hide show
  1. package/README.md +98 -1
  2. package/TUTORIAL.md +58 -0
  3. package/dist/ai/models.d.ts +12 -0
  4. package/dist/ai/models.js +33 -1
  5. package/dist/ai/models.js.map +1 -1
  6. package/dist/ai/resolver.d.ts +29 -6
  7. package/dist/ai/resolver.js +42 -7
  8. package/dist/ai/resolver.js.map +1 -1
  9. package/dist/cli/args.js +2 -0
  10. package/dist/cli/args.js.map +1 -1
  11. package/dist/cli/diff.js +34 -10
  12. package/dist/cli/diff.js.map +1 -1
  13. package/dist/cli/fix.js +9 -2
  14. package/dist/cli/fix.js.map +1 -1
  15. package/dist/cli/index.js +47 -0
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/cli/map.d.ts +15 -4
  18. package/dist/cli/map.js +66 -13
  19. package/dist/cli/map.js.map +1 -1
  20. package/dist/cli/secret.d.ts +1 -0
  21. package/dist/cli/secret.js +172 -0
  22. package/dist/cli/secret.js.map +1 -0
  23. package/dist/cli/session.d.ts +24 -0
  24. package/dist/cli/session.js +119 -0
  25. package/dist/cli/session.js.map +1 -0
  26. package/dist/cli/skill.d.ts +37 -14
  27. package/dist/cli/skill.js +36 -30
  28. package/dist/cli/skill.js.map +1 -1
  29. package/dist/config.d.ts +23 -2
  30. package/dist/config.js +50 -0
  31. package/dist/config.js.map +1 -1
  32. package/dist/index.d.ts +5 -2
  33. package/dist/index.js +11 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/secretStore.d.ts +49 -0
  36. package/dist/secretStore.js +244 -0
  37. package/dist/secretStore.js.map +1 -0
  38. package/dist/sessionCheck.d.ts +96 -0
  39. package/dist/sessionCheck.js +182 -0
  40. package/dist/sessionCheck.js.map +1 -0
  41. package/dist/unrepaired.d.ts +73 -0
  42. package/dist/unrepaired.js +0 -0
  43. package/dist/unrepaired.js.map +1 -0
  44. package/dist/volatile.d.ts +8 -0
  45. package/dist/volatile.js +24 -7
  46. package/dist/volatile.js.map +1 -1
  47. package/dist/volatileBlindSpot.d.ts +67 -0
  48. package/dist/volatileBlindSpot.js +0 -0
  49. package/dist/volatileBlindSpot.js.map +1 -0
  50. package/package.json +1 -1
  51. package/skills/ia-qa-heal/SKILL.md +29 -3
@@ -0,0 +1,73 @@
1
+ import type { Usage } from './ingest';
2
+ /**
3
+ * "No test file referenced any of the drifted selectors" — the false negative.
4
+ *
5
+ * Field report: that line was printed over a project whose Page Object contains, verbatim,
6
+ * `a[href="/index.php/cart"]` — one of the drifted selectors. The statement was simply
7
+ * untrue, and the reader could only conclude the inventory was broken.
8
+ *
9
+ * The line was measuring the wrong set. `rewrites` holds the rows the tool decided it could
10
+ * **repair**; a row that drifted and could not be repaired is in neither `rewrites` nor the
11
+ * resulting edits, so "nothing matched a file" got reported as "your tests do not use any of
12
+ * this". Two very different sentences: one is about the suite, the other about the tool's
13
+ * own reach.
14
+ *
15
+ * What the reader needed was the third sentence, which nobody was writing: **your tests do
16
+ * reference it, and here is why I cannot repair it.** Saying "no" where the honest answer is
17
+ * "I don't know, for this reason" is the single failure mode that costs the most trust,
18
+ * because it is indistinguishable from a working check.
19
+ *
20
+ * Everything here is pure, and it reads the two files that were already on disk the whole
21
+ * time: the drift rows, and `usage.json`.
22
+ */
23
+ export interface UnrepairedRow {
24
+ status: string;
25
+ role?: string;
26
+ name?: string;
27
+ selector?: string;
28
+ newName?: string;
29
+ candidateCount?: number;
30
+ nameMasked?: boolean;
31
+ }
32
+ export interface UnrepairedFinding {
33
+ selector?: string;
34
+ role: string;
35
+ name: string;
36
+ status: string;
37
+ /** Why this one could not be repaired, in a sentence about the element. */
38
+ why: string;
39
+ /** How the suite reaches it. */
40
+ via: Array<'selector' | 'name'>;
41
+ sites: Array<{
42
+ file: string;
43
+ line: number;
44
+ }>;
45
+ }
46
+ /**
47
+ * Why a drifted row the suite uses was left alone.
48
+ *
49
+ * Each sentence names the property of the ELEMENT that makes the repair unprovable, not the
50
+ * internal status word — a reader who is told `ambiguous` has learned a vocabulary, and a
51
+ * reader told "two elements now share that role and name, so a repair would be a coin flip"
52
+ * has learned what to go and look at.
53
+ */
54
+ export declare function reasonFor(row: UnrepairedRow): string;
55
+ /**
56
+ * Rows that drifted, were not repaired, and that the suite demonstrably uses.
57
+ *
58
+ * `repaired` is the set of selector literals the fix is about to rewrite — anything in it is
59
+ * already reported as an edit and must not be listed again as a gap.
60
+ *
61
+ * No inventory ⇒ no findings, and the caller must then say "not measured" rather than
62
+ * anything resembling "no". That distinction is the whole point of this module.
63
+ */
64
+ export declare function referencedButUnrepaired(rows: UnrepairedRow[], repaired: Set<string>, usage: Usage | null): UnrepairedFinding[];
65
+ /**
66
+ * The block that replaces "no test file referenced any of the drifted selectors".
67
+ *
68
+ * Three states, and they must stay three: repaired nothing *and* your tests use none of it;
69
+ * repaired nothing *but* your tests use these, here is why; and repaired nothing *and*
70
+ * nobody has told me what your tests use. The middle one did not exist, and the third was
71
+ * being printed as the first.
72
+ */
73
+ export declare function unrepairedLines(findings: UnrepairedFinding[], hasInventory: boolean): string[];
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unrepaired.js","sourceRoot":"","sources":["../src/unrepaired.ts"],"names":[],"mappings":";;AAuDA,8BAsBC;AAWD,0DAwCC;AAUD,0CA6BC;AAtKD,iDAA+C;AA8C/C;;;;;;;GAOG;AACH,SAAgB,SAAS,CAAC,GAAkB;IAC1C,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QACnB,OAAO,0HAA0H,CAAC;IACpI,CAAC;IACD,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,WAAW;YACd,OAAO,GACL,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,cAAc,WAAW,CAAC,CAAC,CAAC,uBACpF,wHAAwH,CAAC;QAC3H,KAAK,MAAM;YACT,OAAO,wLAAwL,CAAC;QAClM,KAAK,SAAS;YACZ,OAAO,8HAA8H,CAAC;QACxI,KAAK,SAAS;YACZ,OAAO,0JAA0J,CAAC;QACpK,KAAK,gBAAgB;YACnB,OAAO,0KAA0K,CAAC;QACpL,KAAK,QAAQ;YACX,OAAO,+GAA+G,CAAC;QACzH;YACE,OAAO,WAAW,GAAG,CAAC,MAAM,mDAAmD,CAAC;IACpF,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,uBAAuB,CACrC,IAAqB,EACrB,QAAqB,EACrB,KAAmB;IAEnB,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IACjC,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU;YAAE,SAAS;QACzF,IAAI,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,SAAS;QAEzD,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAA,4BAAa,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEnE,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACpE,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,MAAM,GAAG,GAA+B,EAAE,CAAC;QAC3C,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE3C,GAAG,CAAC,IAAI,CAAC;YACP,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;YACpB,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC;YACnB,GAAG;YACH,KAAK,EAAE,CAAC,GAAG,aAAa,EAAE,GAAG,SAAS,CAAC;iBACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;iBAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SAC7F,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,eAAe,CAAC,QAA6B,EAAE,YAAqB;IAClF,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO;YACL,mDAAmD;YACnD,4FAA4F;YAC5F,oGAAoG;SACrG,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,wFAAwF,CAAC,CAAC;IACpG,CAAC;IACD,MAAM,KAAK,GAAG;QACZ,6EAA6E,QAAQ,CAAC,MAAM,MAAM;YAChG,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,GAAG;KACpE,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/E,KAAK,CAAC,IAAI,CACR,QAAQ,CAAC,CAAC,IAAI,IAAI,SAAS,KAAK,CAAC,CAAC,IAAI,IAAI,sBAAsB,oBAAoB,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EACzG,UAAU,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAC7E,UAAU,CAAC,CAAC,GAAG,GAAG,CACnB,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9E,KAAK,CAAC,IAAI,CACR,qFAAqF,EACrF,gFAAgF,CACjF,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -29,8 +29,16 @@ export interface VolatilePattern {
29
29
  export declare function parseVolatilePatterns(patterns: string[] | undefined): VolatilePattern[];
30
30
  /** Shared with `nameMask.ts`, so both declarations speak one pattern dialect. */
31
31
  export declare function globToRegex(glob: string): RegExp;
32
+ /** The pattern that excludes this element, or null. Named, because the message needs it. */
33
+ export declare function matchVolatile(el: MappedElement, patterns: VolatilePattern[]): VolatilePattern | null;
32
34
  export declare function isVolatile(el: MappedElement, patterns: VolatilePattern[]): boolean;
35
+ /** An element that left the contract, with the pattern that removed it. */
36
+ export interface VolatileExclusion {
37
+ element: MappedElement;
38
+ pattern: string;
39
+ }
33
40
  export declare function filterVolatile(elements: MappedElement[], patterns: VolatilePattern[]): {
34
41
  kept: MappedElement[];
35
42
  excluded: number;
43
+ exclusions: VolatileExclusion[];
36
44
  };
package/dist/volatile.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseVolatilePatterns = parseVolatilePatterns;
4
4
  exports.globToRegex = globToRegex;
5
+ exports.matchVolatile = matchVolatile;
5
6
  exports.isVolatile = isVolatile;
6
7
  exports.filterVolatile = filterVolatile;
7
8
  const FIELDS = ['href', 'selector', 'name', 'role'];
@@ -22,27 +23,43 @@ function globToRegex(glob) {
22
23
  const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
23
24
  return new RegExp(`^${escaped}$`, 'i');
24
25
  }
25
- function isVolatile(el, patterns) {
26
+ /** The pattern that excludes this element, or null. Named, because the message needs it. */
27
+ function matchVolatile(el, patterns) {
26
28
  for (const p of patterns) {
27
29
  if (p.field === 'any') {
28
30
  if ((el.href !== undefined && p.regex.test(el.href)) ||
29
31
  p.regex.test(el.selector) ||
30
32
  p.regex.test(el.name)) {
31
- return true;
33
+ return p;
32
34
  }
33
35
  }
34
36
  else {
35
37
  const value = el[p.field];
36
38
  if (value !== undefined && p.regex.test(value))
37
- return true;
39
+ return p;
38
40
  }
39
41
  }
40
- return false;
42
+ return null;
43
+ }
44
+ function isVolatile(el, patterns) {
45
+ return matchVolatile(el, patterns) !== null;
41
46
  }
42
47
  function filterVolatile(elements, patterns) {
43
48
  if (patterns.length === 0)
44
- return { kept: elements, excluded: 0 };
45
- const kept = elements.filter((el) => !isVolatile(el, patterns));
46
- return { kept, excluded: elements.length - kept.length };
49
+ return { kept: elements, excluded: 0, exclusions: [] };
50
+ const kept = [];
51
+ const exclusions = [];
52
+ for (const el of elements) {
53
+ const hit = matchVolatile(el, patterns);
54
+ // The excluded elements are kept, not just counted. They are the only evidence that
55
+ // can answer "is anything my tests use being silently dropped?" — see
56
+ // `volatileBlindSpots`, and they exist nowhere else: by construction they never
57
+ // reach a contract.
58
+ if (hit)
59
+ exclusions.push({ element: el, pattern: hit.raw });
60
+ else
61
+ kept.push(el);
62
+ }
63
+ return { kept, excluded: exclusions.length, exclusions };
47
64
  }
48
65
  //# sourceMappingURL=volatile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"volatile.js","sourceRoot":"","sources":["../src/volatile.ts"],"names":[],"mappings":";;AAiCA,sDAWC;AAGD,kCAGC;AAED,gCAgBC;AAED,wCAOC;AA9CD,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AAE7D,SAAgB,qBAAqB,CAAC,QAA8B;IAClE,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAClC,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,MAAM,OAAO,GAAI,MAA4B,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/D,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAE,MAAmC,CAAC,CAAC,CAAC,KAAK;YAC7D,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACxD,GAAG;SACJ,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,iFAAiF;AACjF,SAAgB,WAAW,CAAC,IAAY;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChF,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,UAAU,CAAC,EAAiB,EAAE,QAA2B;IACvE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YACtB,IACE,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;gBAChD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC;gBACzB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EACrB,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC9D,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAgB,cAAc,CAC5B,QAAyB,EACzB,QAA2B;IAE3B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IAClE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;AAC3D,CAAC"}
1
+ {"version":3,"file":"volatile.js","sourceRoot":"","sources":["../src/volatile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { Usage } from './ingest';
2
+ import type { VolatileExclusion } from './volatile';
3
+ /**
4
+ * The declaration that removes an element, checked against the suite that uses it.
5
+ *
6
+ * `volatile` is the most destructive declaration this tool takes: a matching element never
7
+ * enters a contract at all — not its selector, not its name, not its href, not its
8
+ * coverage. That is correct for a rotating feed item, and it is a **permanent blind spot**
9
+ * for anything else. Nothing ever reports drift on an element that is not in the contract,
10
+ * because from the gate's point of view it does not exist.
11
+ *
12
+ * Until now `map` printed only a count — `N volatile excluded` — and a count cannot
13
+ * distinguish "40 feed cards" from "40 feed cards and the cart button your suite clicks".
14
+ * Field report: an element excluded by a pattern was referenced by a Page Object, and
15
+ * nobody was told for fifteen days.
16
+ *
17
+ * **Its conservative sibling already does this.** `nameMask.ts` refuses a mask that would
18
+ * swallow a label `usage.names` says the tests locate by — the exact same cross-check,
19
+ * between the exact same two files. `volatile` is the one that removes more and checked
20
+ * less.
21
+ *
22
+ * Three restraints, and they are what makes this a warning worth reading:
23
+ *
24
+ * **It warns, it never refuses.** A mask is a comparison-time policy and can be declined
25
+ * without losing anything; `volatile` is applied at capture, and refusing it would either
26
+ * write a contract the user did not ask for or abandon the page. The user declared this on
27
+ * purpose. What they did not choose is being uninformed about it.
28
+ *
29
+ * **No inventory ⇒ no message.** A project that has never run `ingest` has no evidence
30
+ * either way, and "no test uses it" is exactly the false negative this file exists to stop
31
+ * being printed. Silence here means *not measured*, and the caller says so rather than
32
+ * implying the exclusions are safe.
33
+ *
34
+ * **Silent on the good case.** An exclusion nothing references produces no line at all. A
35
+ * warning that fires on the ordinary run is one people learn to scroll past, and then it is
36
+ * not there on the day it matters.
37
+ */
38
+ export interface VolatileBlindSpot {
39
+ /** What was excluded, as the contract would have addressed it. */
40
+ selector: string;
41
+ name: string;
42
+ role: string;
43
+ /** The `volatile` pattern that removed it — the line the user would edit. */
44
+ pattern: string;
45
+ /** How the suite reaches it: by selector, by accessible name, or both. */
46
+ via: Array<'selector' | 'name'>;
47
+ /** Where, in the user's own files. */
48
+ sites: Array<{
49
+ file: string;
50
+ line: number;
51
+ }>;
52
+ }
53
+ /**
54
+ * Which excluded elements the suite actually uses.
55
+ *
56
+ * Deduplicated by selector + name: a navbar element excluded on 142 pages is one blind
57
+ * spot and one fix, not 142 findings — the same reasoning `check` applies to dead links.
58
+ */
59
+ export declare function volatileBlindSpots(exclusions: VolatileExclusion[], usage: Usage | null): VolatileBlindSpot[];
60
+ /**
61
+ * The warning, as one block.
62
+ *
63
+ * Rendered here rather than in the CLI so the message cannot be worded one way by `map` and
64
+ * another by whatever reads this next — the `checkHeadline` rule. Returns null when there
65
+ * is nothing to say, which is the ordinary case.
66
+ */
67
+ export declare function volatileBlindSpotLines(spots: VolatileBlindSpot[]): string[] | null;
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volatileBlindSpot.js","sourceRoot":"","sources":["../src/volatileBlindSpot.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.18.0",
3
+ "version": "1.19.1",
4
4
  "description": "Your Playwright, Cypress or Selenium tests break when a selector moves — this finds the element again and rewrites the test. Deterministic: no LLM decides whether your build passes. Runs entirely on your machine, with a local MCP server for agents.",
5
5
  "keywords": [
6
6
  "self-healing",
@@ -64,6 +64,7 @@ exists only in a state nothing captured (behind a tab, a modal, a mode toggle).
64
64
  | "is this getting better or worse?" | `history` (`--json`) — the trend no single run can reconstruct |
65
65
  | "gate my pipeline on it" | `diff --junit <file>` — every major CI charts the trend natively |
66
66
  | `map` says "login wall" / the app is behind SSO | if a storageState already exists (most authenticated Playwright suites write one in `globalSetup`): `--session <file>`, `IAQA_SESSION`, or `"session"` in config.json — no second login. Otherwise tell the human to run `ia-qa-heal login` — **you cannot do it** (§4.12) |
67
+ | "is the session still good?" / before blaming the app for a wall | `session --check`. It tells the three causes **apart**: `expired` (every cookie's date is past) and `wrong-host` (nothing is scoped to `baseUrl`) are decided **from the file, no browser**; `rotated` (the file is fine, the server still answers a login) needs one request. Exit 0 holds · 1 does not · 2 could not tell. `--offline` skips the browser entirely. Run it **before** a long `map`, and when a tour or a map reports a wall — a `wrong-host` is the one no amount of logging in will fix |
67
68
  | "let me look at it / decide myself" | tell the human to run `ia-qa-heal ui` — **do not run it yourself** (§4.8) |
68
69
  | first-time setup | `ia-qa-heal init --yes --base-url <url> --discover [--tests <paths>] [--bootstrap]` — asks nothing, so it cannot hang. `--discover` fills `pages` from their sitemap (plain fetch, no browser); `--bootstrap` also chains ingest → map → baseline. Writing `.ia-qa/config.json` by hand still works |
69
70
  | "288 lost, red everywhere, on an app that looks fine" | read the **systemic delta** block `diff` prints — the same element gone from ~every page is one fact about the *capture*, not N drifts. Usually a consent banner, a tour or a flag: the fix is `prepare` in config.json (§4.16), not a rewrite |
@@ -125,6 +126,9 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
125
126
  included, so a real break on that button would never be caught again. And never propose a mask
126
127
  over a label the tests locate by name: the tool refuses it out loud, and talking the human past
127
128
  that refusal is putting a green gate over a red suite.
129
+ **`map` now names the elements a `volatile` pattern removed that the suite still uses**, with
130
+ `file:line` and the pattern to narrow — relay that warning in full. It fires only on the bad
131
+ case, so if you see it, something the tests click will never be gated again.
128
132
  10. **Never read a `map` that exited 1 as "nothing was mapped".** A page that fails is now isolated:
129
133
  the rest are still written, its own previous contract is left untouched, and the summary says
130
134
  `N of M pages mapped`. Read that line — the pages it did not reach are `stale` in the next diff,
@@ -139,7 +143,13 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
139
143
  12. **Never try to log a suite in yourself.** When `map` reports a login wall it prints the whole
140
144
  remedy: a filled-in `auth` block when the form is readable, and the fact that a form fill
141
145
  cannot pass SSO with MFA, a consent screen or a magic link. Read past the first line before
142
- concluding anything. For everything a form cannot answer there is exactly one step, and it
146
+ concluding anything. **Run `session --check` first**: it says *which* of expired / wrong-host /
147
+ rotated it is, and two of those need no browser. `wrong-host` means the session and the
148
+ `baseUrl` are about different applications — telling the human to log in again there wastes
149
+ their time and produces the identical file. Note that with a session in use, `map` now stops
150
+ at the first wall instead of walking every remaining page into it: `N of M pages mapped` with
151
+ the rest **not attempted** (they keep their previous contracts and read `stale`), so do not
152
+ report those as pages that failed. For everything a form cannot answer there is exactly one step, and it
143
153
  belongs to the human: `ia-qa-heal login` opens a visible browser, they log in once, and the
144
154
  session is reused by `map`. It refuses without a TTY and under CI — that refusal is aimed at
145
155
  you. Never ask for their password, never put a credential in `config.json` (it holds `secrets`
@@ -197,11 +207,19 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
197
207
  `run`'s live coverage and `map`'s "N of M pages": a surface left unmeasured is never a pass.
198
208
  The remedy is `--deep-budget <n>` or a narrower `--depth 1`, never ignoring the line.
199
209
 
200
- 19. **Never present `--deep` as free.** It clicks its way through the page's states, so it costs
210
+ 19. **Never read "nothing could be rewritten" as "their tests do not use it".** `fix` prints
211
+ three different sentences and they mean three different things: *nothing to repair and your
212
+ tests reference none of it*; *nothing to repair **but** your tests DO reference these — here
213
+ is each one with `file:line` and why it could not be repaired*; and *not measured, because no
214
+ `usage.json` exists*. The middle one is the interesting case and the last one is not a "no".
215
+ Relay the reason per element — "two elements now answer to that role and name, so repairing
216
+ would be a coin flip" is what the human acts on, not the status word.
217
+
218
+ 20. **Never present `--deep` as free.** It clicks its way through the page's states, so it costs
201
219
  ~1 min per page where a plain `map` costs seconds. Recommend it for building or refreshing a
202
220
  **baseline**, not for a per-commit gate, and say so when you propose it.
203
221
 
204
- 20. **Never raise `--depth` to "get better coverage".** The default is 1 because it *finishes*.
222
+ 21. **Never raise `--depth` to "get better coverage".** The default is 1 because it *finishes*.
205
223
  Depth 2 was measured at +30% elements for 3× the time and **never completed at any budget** —
206
224
  80, 150 and 250 clicks all returned the same elements, so the extra clicks re-walk states that
207
225
  hold nothing new. If a user asks for deeper, tell them what it costs and that the contract
@@ -227,6 +245,14 @@ Nothing, except: requests to the user's **own** `baseUrl` (`check`'s link half,
227
245
  — GET only, same origin, never a `/logout` or `/delete` URL), and `ia-qa-heal-ai` calling the
228
246
  user's own LLM if they opted in. Say that plainly rather than implying more or less.
229
247
 
248
+ **Where the key lives.** `ia-qa-heal-ai` needs a provider key, and it is never a file value.
249
+ Prefer the OS store — hand the human `ia-qa-heal secret set <NAME>` to run themselves (it asks
250
+ for the value on a terminal and refuses a pipe or an argument), then reference it as
251
+ `{ "source": "keychain", "key": "<NAME>" }`. You can confirm it resolves with
252
+ `secret check <NAME>`; **there is no verb that prints a stored value and you must not look for
253
+ one**. In CI there is no store and no person: keep `{ "source": "env" }` and let the runner
254
+ hold the secret. Never ask the human to paste a key to you; if they do, tell them to rotate it.
255
+
230
256
  `ui` does not change that: it binds `127.0.0.1` only, is gated by a per-process token, and serves a
231
257
  page with no external asset. If asked whether it "hosts" anything, the answer is no — it is a local
232
258
  process that dies with the browser tab.