@ia-qa/self-healing 1.15.0 → 1.16.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.
package/dist/index.d.ts CHANGED
@@ -46,6 +46,8 @@ export type { AuditOutcome, AuditSuccess } from './cli/audit';
46
46
  export { detectTestCommand } from './cli/init';
47
47
  export { groupNumericLabelDrift, maskDigits } from './nameHint';
48
48
  export type { NameDriftGroup, RenamedRowLike } from './nameHint';
49
+ export { groupListChurn, listShape } from './listChurn';
50
+ export type { ListChurnGroup, ChurnElementLike, ChurnRowLike } from './listChurn';
49
51
  export { planSetup, writeSetup, readSitemap, isWebAddress } from './ui/setup';
50
52
  export type { SetupRequest, SetupPlan, SetupWrite, SitemapAnswer, LoginMode } from './ui/setup';
51
53
  export type { CheckReport, CheckVerdict } from './checks';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.captureDir = exports.unionElements = exports.mergeCaptures = exports.globToRegex = exports.readTestDepth = exports.collectUnattributable = exports.computePageDepth = exports.computeTestDepth = exports.saveExercised = exports.EXERCISED_FILENAME = exports.contractPageNames = exports.exercisedPath = exports.loadExercised = exports.usagePath = exports.usageFiles = exports.loadUsage = exports.saveUsage = exports.ingestPaths = exports.scanText = exports.crawlApp = exports.sitemapsFromRobots = exports.isSitemapIndex = exports.extractLocs = exports.discoverFromSitemap = exports.UNSAFE_URL_RE = exports.mergePages = exports.toCandidate = exports.normalizedPath = exports.isSafeCandidateUrl = exports.resolvePageName = exports.pageNameFromUrl = exports.mapUrl = exports.markdownPath = exports.saveMarkdown = exports.renderPageContract = exports.saveMapping = exports.loadMapping = exports.extractInteractiveElements = exports.renderModelList = exports.defaultKeyEnv = exports.findProvider = exports.AI_PROVIDERS = exports.parseModelJson = exports.buildPrompt = exports.prefilter = exports.aiResolve = exports.createAiResolver = exports.llmResolverStub = exports.aiFill = exports.aiClick = void 0;
4
- exports.NAVIGATION_SVG_FILENAME = exports.NAVIGATION_FILENAME = exports.OVERVIEW_FILENAME = exports.navigationSvgPath = exports.navigationPath = exports.overviewPath = exports.writeOverview = exports.navigationGraph = exports.layoutGraph = exports.renderNavigationSvg = exports.renderNavigation = exports.renderOverview = exports.buildOverview = exports.minPagesForLayout = exports.stripLayoutFromPages = exports.extractLayout = exports.isWebAddress = exports.readSitemap = exports.writeSetup = exports.planSetup = exports.maskDigits = exports.groupNumericLabelDrift = exports.detectTestCommand = exports.auditProject = exports.runFix = exports.checkCounts = exports.checkHeadline = exports.checkVerdict = exports.checkProject = exports.dirDiffAggregate = exports.runBaseline = exports.LoginWallError = exports.runMap = exports.baselineDir = exports.mappingDir = exports.setBaseDir = exports.LAYOUT_DIRNAME = exports.layoutPath = exports.layoutDir = exports.resolveSecret = exports.loadConfig = exports.replaceQuoted = exports.collectFiles = exports.applyRewrites = exports.computeRewrites = exports.launchBrowser = exports.CAPTURE_DIRNAME = void 0;
4
+ exports.NAVIGATION_SVG_FILENAME = exports.NAVIGATION_FILENAME = exports.OVERVIEW_FILENAME = exports.navigationSvgPath = exports.navigationPath = exports.overviewPath = exports.writeOverview = exports.navigationGraph = exports.layoutGraph = exports.renderNavigationSvg = exports.renderNavigation = exports.renderOverview = exports.buildOverview = exports.minPagesForLayout = exports.stripLayoutFromPages = exports.extractLayout = exports.isWebAddress = exports.readSitemap = exports.writeSetup = exports.planSetup = exports.listShape = exports.groupListChurn = exports.maskDigits = exports.groupNumericLabelDrift = exports.detectTestCommand = exports.auditProject = exports.runFix = exports.checkCounts = exports.checkHeadline = exports.checkVerdict = exports.checkProject = exports.dirDiffAggregate = exports.runBaseline = exports.LoginWallError = exports.runMap = exports.baselineDir = exports.mappingDir = exports.setBaseDir = exports.LAYOUT_DIRNAME = exports.layoutPath = exports.layoutDir = exports.resolveSecret = exports.loadConfig = exports.replaceQuoted = exports.collectFiles = exports.applyRewrites = exports.computeRewrites = exports.launchBrowser = exports.CAPTURE_DIRNAME = void 0;
5
5
  var healer_1 = require("./playwright/healer");
6
6
  Object.defineProperty(exports, "aiClick", { enumerable: true, get: function () { return healer_1.aiClick; } });
7
7
  Object.defineProperty(exports, "aiFill", { enumerable: true, get: function () { return healer_1.aiFill; } });
@@ -118,6 +118,10 @@ Object.defineProperty(exports, "detectTestCommand", { enumerable: true, get: fun
118
118
  var nameHint_1 = require("./nameHint");
119
119
  Object.defineProperty(exports, "groupNumericLabelDrift", { enumerable: true, get: function () { return nameHint_1.groupNumericLabelDrift; } });
120
120
  Object.defineProperty(exports, "maskDigits", { enumerable: true, get: function () { return nameHint_1.maskDigits; } });
121
+ // Lists whose items are data — noticed here, so every surface groups them the same way.
122
+ var listChurn_1 = require("./listChurn");
123
+ Object.defineProperty(exports, "groupListChurn", { enumerable: true, get: function () { return listChurn_1.groupListChurn; } });
124
+ Object.defineProperty(exports, "listShape", { enumerable: true, get: function () { return listChurn_1.listShape; } });
121
125
  // The setup door `ui` uses: preview and write through one plan, never overwriting, no secret field.
122
126
  var setup_1 = require("./ui/setup");
123
127
  Object.defineProperty(exports, "planSetup", { enumerable: true, get: function () { return setup_1.planSetup; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAAA,8CAAyF;AAAhF,iGAAA,OAAO,OAAA;AAAE,gGAAA,MAAM,OAAA;AAAE,yGAAA,eAAe,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAE3D,0CAAkF;AAAzE,qGAAA,SAAS,OAAA;AAAE,qGAAA,SAAS,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AAE1D,sCAAyF;AAAhF,sGAAA,YAAY,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,yGAAA,eAAe,OAAA;AAEnE,6BAA6E;AAApE,iHAAA,0BAA0B,OAAA;AAAE,kGAAA,WAAW,OAAA;AAAE,kGAAA,WAAW,OAAA;AAE7D,uCAA4E;AAAnE,8GAAA,kBAAkB,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,wGAAA,YAAY,OAAA;AACvD,mCAAmD;AAA1C,gGAAA,MAAM,OAAA;AAAE,yGAAA,eAAe,OAAA;AAChC,uCAA6C;AAApC,2GAAA,eAAe,OAAA;AACxB,6CAM4B;AAL1B,4GAAA,kBAAkB,OAAA;AAClB,wGAAA,cAAc,OAAA;AACd,qGAAA,WAAW,OAAA;AACX,oGAAA,UAAU,OAAA;AACV,uGAAA,aAAa,OAAA;AAGf,+CAK6B;AAJ3B,8GAAA,mBAAmB,OAAA;AACnB,sGAAA,WAAW,OAAA;AACX,yGAAA,cAAc,OAAA;AACd,6GAAA,kBAAkB,OAAA;AAGpB,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AAGjB,mCAA8F;AAArF,kGAAA,QAAQ,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,mGAAA,SAAS,OAAA;AAE3E,qFAAqF;AACrF,2FAA2F;AAC3F,gFAAgF;AAChF,yCAAkG;AAAzF,0GAAA,aAAa,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,+GAAA,kBAAkB,OAAA;AAC5E,4FAA4F;AAC5F,iFAAiF;AACjF,yCAA4C;AAAnC,0GAAA,aAAa,OAAA;AAEtB,yCAAuG;AAA9F,6GAAA,gBAAgB,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAAE,kHAAA,qBAAqB,OAAA;AAAE,0GAAA,aAAa,OAAA;AACjF,sFAAsF;AACtF,qEAAqE;AACrE,uCAAyC;AAAhC,uGAAA,WAAW,OAAA;AAEpB,+CAA2F;AAAlF,6GAAA,aAAa,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,0GAAA,UAAU,OAAA;AAAE,+GAAA,eAAe,OAAA;AAElE,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AAGtB,yCAA0F;AAAjF,4GAAA,eAAe,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,0GAAA,aAAa,OAAA;AACpE,mCAAiI;AAAxH,oGAAA,UAAU,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,wGAAA,cAAc,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,qGAAA,WAAW,OAAA;AAC9G,2FAA2F;AAC3F,4FAA4F;AAC5F,iCAAmD;AAA1C,6FAAA,MAAM,OAAA;AAAE,qGAAA,cAAc,OAAA;AAE/B,2CAA6C;AAApC,uGAAA,WAAW,OAAA;AACpB,mCAA8C;AAArC,wGAAA,gBAAgB,OAAA;AAEzB,qCAA2C;AAAlC,qGAAA,YAAY,OAAA;AAErB,mCAAoE;AAA3D,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,qGAAA,WAAW,OAAA;AACjD,+GAA+G;AAC/G,iCAAmC;AAA1B,6FAAA,MAAM,OAAA;AAEf,qCAA2C;AAAlC,qGAAA,YAAY,OAAA;AAErB,mCAA+C;AAAtC,yGAAA,iBAAiB,OAAA;AAC1B,gHAAgH;AAChH,uCAAgE;AAAvD,kHAAA,sBAAsB,OAAA;AAAE,sGAAA,UAAU,OAAA;AAE3C,oGAAoG;AACpG,oCAA8E;AAArE,kGAAA,SAAS,OAAA;AAAE,mGAAA,UAAU,OAAA;AAAE,oGAAA,WAAW,OAAA;AAAE,qGAAA,YAAY,OAAA;AAIzD,mCAAkF;AAAzE,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAE/D,uCAcoB;AAblB,yGAAA,aAAa,OAAA;AACb,0GAAA,cAAc,OAAA;AACd,4GAAA,gBAAgB,OAAA;AAChB,+GAAA,mBAAmB,OAAA;AACnB,uGAAA,WAAW,OAAA;AACX,2GAAA,eAAe,OAAA;AACf,yGAAA,aAAa,OAAA;AACb,wGAAA,YAAY,OAAA;AACZ,0GAAA,cAAc,OAAA;AACd,6GAAA,iBAAiB,OAAA;AACjB,6GAAA,iBAAiB,OAAA;AACjB,+GAAA,mBAAmB,OAAA;AACnB,mHAAA,uBAAuB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Data-list churn — the noise class two field reports named first.
3
+ *
4
+ * A catalogue, a feed, a results table: the list is UI, its items are data. When the data
5
+ * changes, the contract diff is right row by row and useless as a whole — measured on the
6
+ * witness app, one list of eight products rotating half its items produced 5 ambiguous,
7
+ * 1 lost, 6 rebound, 4 healable and 6 added, and a real lost button next to it was one line
8
+ * among eighteen.
9
+ *
10
+ * This module only *notices*, like `nameHint.ts`: the rows, the counts and the verdict are
11
+ * untouched. What a caller does with a group is its own policy.
12
+ *
13
+ * A group is claimed on three facts, and each one exists because a narrower rule was wrong:
14
+ * - **a shape** — elements of one role whose selector differs only by a position
15
+ * (`li:nth-of-type(n)`) or whose link differs only by an id-like path segment
16
+ * (`/item/101`). Shape alone is not enough: inserting one button above three others shifts
17
+ * every position too, and that is the `rebound` this package exists to catch.
18
+ * - **a list, not a toolbar** — at least `MIN_MEMBERS` elements of that shape in the baseline.
19
+ * - **replacement both ways** — at least `MIN_REPLACED` names gone *and* as many new names
20
+ * arrived. An insertion keeps every old name; an emptied list brings no new one. Both stay
21
+ * loud: an empty catalogue is a regression, not data.
22
+ */
23
+ export interface ChurnElementLike {
24
+ role: string;
25
+ name: string;
26
+ selector: string;
27
+ href?: string;
28
+ }
29
+ export interface ChurnRowLike {
30
+ status: string;
31
+ role: string;
32
+ name: string;
33
+ selector: string;
34
+ href?: string;
35
+ }
36
+ export interface ListChurnGroup {
37
+ role: string;
38
+ /** The shape, readable: `a[href="/item/:id"]` or `#latest > li:nth-of-type(*) > button`. */
39
+ shape: string;
40
+ /** Baseline members whose name is nowhere in the current list. */
41
+ gone: string[];
42
+ /** Current members whose name was nowhere in the baseline list. */
43
+ appeared: string[];
44
+ /** Members present on both sides. */
45
+ kept: number;
46
+ /** Baseline selectors of the rows this group accounts for — every non-`ok` row of the shape. */
47
+ selectors: string[];
48
+ }
49
+ export declare const MIN_MEMBERS = 4;
50
+ export declare const MIN_REPLACED = 2;
51
+ /**
52
+ * The shape an element belongs to, or `null` when it has none — most elements.
53
+ * A link is shaped by its path with id-like segments collapsed; anything else by its
54
+ * selector with positions collapsed. A link whose path holds no id-like segment is not
55
+ * shaped at all: `/products` and `/cart` are navigation, never a list.
56
+ */
57
+ export declare function listShape(el: {
58
+ role: string;
59
+ selector: string;
60
+ href?: string;
61
+ }): string | null;
62
+ /** Lists on one page whose items were replaced. Pure; empty on an ordinary page. */
63
+ export declare function groupListChurn(baseline: ChurnElementLike[], current: ChurnElementLike[], rows: ChurnRowLike[]): ListChurnGroup[];
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /**
3
+ * Data-list churn — the noise class two field reports named first.
4
+ *
5
+ * A catalogue, a feed, a results table: the list is UI, its items are data. When the data
6
+ * changes, the contract diff is right row by row and useless as a whole — measured on the
7
+ * witness app, one list of eight products rotating half its items produced 5 ambiguous,
8
+ * 1 lost, 6 rebound, 4 healable and 6 added, and a real lost button next to it was one line
9
+ * among eighteen.
10
+ *
11
+ * This module only *notices*, like `nameHint.ts`: the rows, the counts and the verdict are
12
+ * untouched. What a caller does with a group is its own policy.
13
+ *
14
+ * A group is claimed on three facts, and each one exists because a narrower rule was wrong:
15
+ * - **a shape** — elements of one role whose selector differs only by a position
16
+ * (`li:nth-of-type(n)`) or whose link differs only by an id-like path segment
17
+ * (`/item/101`). Shape alone is not enough: inserting one button above three others shifts
18
+ * every position too, and that is the `rebound` this package exists to catch.
19
+ * - **a list, not a toolbar** — at least `MIN_MEMBERS` elements of that shape in the baseline.
20
+ * - **replacement both ways** — at least `MIN_REPLACED` names gone *and* as many new names
21
+ * arrived. An insertion keeps every old name; an emptied list brings no new one. Both stay
22
+ * loud: an empty catalogue is a regression, not data.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.MIN_REPLACED = exports.MIN_MEMBERS = void 0;
26
+ exports.listShape = listShape;
27
+ exports.groupListChurn = groupListChurn;
28
+ exports.MIN_MEMBERS = 4;
29
+ exports.MIN_REPLACED = 2;
30
+ const ID_SEGMENT = /\d/;
31
+ function hrefOf(el) {
32
+ if (el.href)
33
+ return el.href;
34
+ const m = /^a\[href="([^"]*)"\]$/i.exec(el.selector.trim());
35
+ return m ? m[1] : undefined;
36
+ }
37
+ /**
38
+ * The shape an element belongs to, or `null` when it has none — most elements.
39
+ * A link is shaped by its path with id-like segments collapsed; anything else by its
40
+ * selector with positions collapsed. A link whose path holds no id-like segment is not
41
+ * shaped at all: `/products` and `/cart` are navigation, never a list.
42
+ */
43
+ function listShape(el) {
44
+ const href = hrefOf(el);
45
+ if (href !== undefined) {
46
+ const pathOnly = href.split(/[?#]/)[0];
47
+ const segments = pathOnly.split('/');
48
+ if (!segments.some((s) => ID_SEGMENT.test(s)))
49
+ return null;
50
+ return `${el.role} a[href="${segments.map((s) => (ID_SEGMENT.test(s) ? ':id' : s)).join('/')}"]`;
51
+ }
52
+ if (!/:nth-of-type\(\d+\)/.test(el.selector))
53
+ return null;
54
+ return `${el.role} ${el.selector.replace(/:nth-of-type\(\d+\)/g, ':nth-of-type(*)')}`;
55
+ }
56
+ function byShape(elements) {
57
+ const out = new Map();
58
+ for (const el of elements) {
59
+ const shape = listShape(el);
60
+ if (!shape)
61
+ continue;
62
+ out.set(shape, [...(out.get(shape) ?? []), el]);
63
+ }
64
+ return out;
65
+ }
66
+ /** Lists on one page whose items were replaced. Pure; empty on an ordinary page. */
67
+ function groupListChurn(baseline, current, rows) {
68
+ const before = byShape(baseline);
69
+ const after = byShape(current);
70
+ const groups = [];
71
+ for (const [shape, members] of before) {
72
+ if (members.length < exports.MIN_MEMBERS)
73
+ continue;
74
+ const oldNames = new Set(members.map((m) => m.name));
75
+ const newNames = new Set((after.get(shape) ?? []).map((m) => m.name));
76
+ const gone = [...oldNames].filter((n) => !newNames.has(n));
77
+ const appeared = [...newNames].filter((n) => !oldNames.has(n));
78
+ if (gone.length < exports.MIN_REPLACED || appeared.length < exports.MIN_REPLACED)
79
+ continue;
80
+ const selectors = rows.filter((r) => r.status !== 'ok' && listShape(r) === shape).map((r) => r.selector);
81
+ groups.push({
82
+ role: members[0].role,
83
+ shape: shape.slice(members[0].role.length + 1),
84
+ gone,
85
+ appeared,
86
+ kept: [...oldNames].filter((n) => newNames.has(n)).length,
87
+ selectors,
88
+ });
89
+ }
90
+ return groups;
91
+ }
92
+ //# sourceMappingURL=listChurn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listChurn.js","sourceRoot":"","sources":["../src/listChurn.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.15.0",
3
+ "version": "1.16.0",
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",