sela-core 1.0.7 → 1.0.9

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 (146) hide show
  1. package/README.md +27 -17
  2. package/dist/cli/ErrorHandler.d.ts +1 -1
  3. package/dist/cli/ErrorHandler.js +14 -14
  4. package/dist/cli/commands/bulk.d.ts +1 -1
  5. package/dist/cli/commands/bulk.d.ts.map +1 -1
  6. package/dist/cli/commands/bulk.js +51 -33
  7. package/dist/cli/commands/init.d.ts +1 -1
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +26 -18
  10. package/dist/cli/commands/merge.d.ts +13 -0
  11. package/dist/cli/commands/merge.d.ts.map +1 -0
  12. package/dist/cli/commands/merge.js +119 -0
  13. package/dist/cli/commands/showReport.d.ts +1 -1
  14. package/dist/cli/commands/showReport.d.ts.map +1 -1
  15. package/dist/cli/commands/showReport.js +12 -10
  16. package/dist/cli/commands/status.d.ts +1 -1
  17. package/dist/cli/commands/status.d.ts.map +1 -1
  18. package/dist/cli/commands/status.js +8 -8
  19. package/dist/cli/commands/sync.d.ts +1 -1
  20. package/dist/cli/commands/sync.d.ts.map +1 -1
  21. package/dist/cli/commands/sync.js +38 -30
  22. package/dist/cli/index.js +12 -9
  23. package/dist/cli/ui/DnaTable.d.ts +1 -1
  24. package/dist/cli/ui/DnaTable.d.ts.map +1 -1
  25. package/dist/cli/ui/DnaTable.js +23 -16
  26. package/dist/cli/ui/RefactorWizard.d.ts +3 -3
  27. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
  28. package/dist/cli/ui/RefactorWizard.js +108 -82
  29. package/dist/config/ConfigLoader.d.ts +2 -2
  30. package/dist/config/ConfigLoader.d.ts.map +1 -1
  31. package/dist/config/ConfigLoader.js +15 -15
  32. package/dist/config/DryRunGuard.d.ts +1 -1
  33. package/dist/config/DryRunGuard.js +7 -7
  34. package/dist/config/SelaConfig.d.ts +13 -17
  35. package/dist/config/SelaConfig.d.ts.map +1 -1
  36. package/dist/config/SelaConfig.js +32 -26
  37. package/dist/engine/HealingRegistry.d.ts +1 -1
  38. package/dist/engine/HealingRegistry.js +3 -3
  39. package/dist/engine/SelaEngine.d.ts +1 -1
  40. package/dist/engine/SelaEngine.d.ts.map +1 -1
  41. package/dist/engine/SelaEngine.js +117 -51
  42. package/dist/errors/SelaError.d.ts +8 -1
  43. package/dist/errors/SelaError.d.ts.map +1 -1
  44. package/dist/errors/SelaError.js +14 -7
  45. package/dist/fixtures/expectProxy.d.ts +2 -2
  46. package/dist/fixtures/expectProxy.js +16 -16
  47. package/dist/fixtures/index.d.ts +18 -2
  48. package/dist/fixtures/index.d.ts.map +1 -1
  49. package/dist/fixtures/index.js +47 -27
  50. package/dist/fixtures/moduleExpect.js +7 -7
  51. package/dist/fixtures/proxyTag.d.ts +1 -1
  52. package/dist/fixtures/proxyTag.js +4 -4
  53. package/dist/index.d.ts +8 -8
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +4 -4
  56. package/dist/reporter/SelaReporter.d.ts +45 -12
  57. package/dist/reporter/SelaReporter.d.ts.map +1 -1
  58. package/dist/reporter/SelaReporter.js +139 -39
  59. package/dist/services/ASTSourceUpdater.d.ts +48 -3
  60. package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
  61. package/dist/services/ASTSourceUpdater.js +283 -105
  62. package/dist/services/AnchorResolver.d.ts +157 -0
  63. package/dist/services/AnchorResolver.d.ts.map +1 -0
  64. package/dist/services/AnchorResolver.js +289 -0
  65. package/dist/services/ArgumentTypeAnalyzer.js +7 -7
  66. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
  67. package/dist/services/BlastRadiusAnalyzer.js +5 -2
  68. package/dist/services/ChainValidator.js +22 -22
  69. package/dist/services/CrossFileHealer.js +7 -7
  70. package/dist/services/DecisionEngine.d.ts +51 -0
  71. package/dist/services/DecisionEngine.d.ts.map +1 -0
  72. package/dist/services/DecisionEngine.js +260 -0
  73. package/dist/services/DefinitionTracer.d.ts +1 -1
  74. package/dist/services/DefinitionTracer.d.ts.map +1 -1
  75. package/dist/services/DefinitionTracer.js +28 -13
  76. package/dist/services/DnaEditorService.d.ts +2 -2
  77. package/dist/services/DnaEditorService.d.ts.map +1 -1
  78. package/dist/services/DnaEditorService.js +81 -62
  79. package/dist/services/DnaIndexService.d.ts +1 -1
  80. package/dist/services/DnaIndexService.d.ts.map +1 -1
  81. package/dist/services/DnaIndexService.js +14 -14
  82. package/dist/services/HealReportService.d.ts +31 -55
  83. package/dist/services/HealReportService.d.ts.map +1 -1
  84. package/dist/services/HealReportService.js +864 -1240
  85. package/dist/services/HealingAdvisory.d.ts +9 -1
  86. package/dist/services/HealingAdvisory.d.ts.map +1 -1
  87. package/dist/services/HealingAdvisory.js +9 -1
  88. package/dist/services/HealingCacheService.d.ts +8 -8
  89. package/dist/services/HealingCacheService.d.ts.map +1 -1
  90. package/dist/services/HealingCacheService.js +24 -24
  91. package/dist/services/HealthReportService.d.ts +1 -1
  92. package/dist/services/HealthReportService.d.ts.map +1 -1
  93. package/dist/services/HealthReportService.js +25 -25
  94. package/dist/services/InitializerUpdater.d.ts.map +1 -1
  95. package/dist/services/InitializerUpdater.js +12 -2
  96. package/dist/services/IntentAuditor.d.ts.map +1 -1
  97. package/dist/services/IntentAuditor.js +32 -21
  98. package/dist/services/InteractiveReview.d.ts +8 -17
  99. package/dist/services/InteractiveReview.d.ts.map +1 -1
  100. package/dist/services/InteractiveReview.js +34 -124
  101. package/dist/services/LLMService.d.ts +1 -1
  102. package/dist/services/LLMService.js +16 -16
  103. package/dist/services/MutationApplier.d.ts +55 -0
  104. package/dist/services/MutationApplier.d.ts.map +1 -0
  105. package/dist/services/MutationApplier.js +322 -0
  106. package/dist/services/PRAutomationService.d.ts +2 -2
  107. package/dist/services/PRAutomationService.d.ts.map +1 -1
  108. package/dist/services/PRAutomationService.js +43 -39
  109. package/dist/services/PendingPromptLedger.d.ts +9 -2
  110. package/dist/services/PendingPromptLedger.d.ts.map +1 -1
  111. package/dist/services/PendingPromptLedger.js +31 -6
  112. package/dist/services/ReportGenerator.d.ts +116 -30
  113. package/dist/services/ReportGenerator.d.ts.map +1 -1
  114. package/dist/services/ReportGenerator.js +149 -62
  115. package/dist/services/ReportMergeService.d.ts +95 -0
  116. package/dist/services/ReportMergeService.d.ts.map +1 -0
  117. package/dist/services/ReportMergeService.js +0 -0
  118. package/dist/services/SafetyGuard.d.ts +4 -7
  119. package/dist/services/SafetyGuard.d.ts.map +1 -1
  120. package/dist/services/SafetyGuard.js +29 -106
  121. package/dist/services/SelectorSanitizer.d.ts +52 -0
  122. package/dist/services/SelectorSanitizer.d.ts.map +1 -0
  123. package/dist/services/SelectorSanitizer.js +318 -0
  124. package/dist/services/SnapshotService.js +6 -6
  125. package/dist/services/SourceLinkService.d.ts +2 -2
  126. package/dist/services/SourceLinkService.d.ts.map +1 -1
  127. package/dist/services/SourceLinkService.js +31 -22
  128. package/dist/services/SourceUpdater.d.ts +1 -1
  129. package/dist/services/SourceUpdater.d.ts.map +1 -1
  130. package/dist/services/SourceUpdater.js +44 -26
  131. package/dist/services/TemplateDiffService.d.ts.map +1 -1
  132. package/dist/services/TemplateDiffService.js +18 -15
  133. package/dist/services/TraceBackEngine.d.ts +67 -0
  134. package/dist/services/TraceBackEngine.d.ts.map +1 -0
  135. package/dist/services/TraceBackEngine.js +672 -0
  136. package/dist/services/WorkspaceSnapshotService.d.ts +6 -6
  137. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -1
  138. package/dist/services/WorkspaceSnapshotService.js +10 -12
  139. package/dist/types/index.d.ts +1 -1
  140. package/dist/utils/DOMUtils.d.ts +18 -2
  141. package/dist/utils/DOMUtils.d.ts.map +1 -1
  142. package/dist/utils/DOMUtils.js +335 -49
  143. package/dist/utils/IsolatedDiff.d.ts +2 -2
  144. package/dist/utils/IsolatedDiff.d.ts.map +1 -1
  145. package/dist/utils/IsolatedDiff.js +13 -10
  146. package/package.json +2 -2
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ // src/services/SelectorSanitizer.ts
3
+ //
4
+ // THE ABSOLUTE RULE (Ticket #2 hardening):
5
+ // Playwright's `internal:` engine selectors (e.g.
6
+ // `internal:role=button[name="Update System"i]`) are a PRIVATE runtime
7
+ // serialization. They must NEVER be written verbatim into user source — not
8
+ // as a `.locator()` string argument, and not into a string-literal constant
9
+ // consumed by `.locator()`. Although the engine happens to accept them at
10
+ // runtime, they are undocumented, version-fragile, and leak internals into
11
+ // the developer's clean code (bad DX, the exact bug this module prevents).
12
+ //
13
+ // This module is the single source of truth for:
14
+ // 1. DETECTING an internal engine selector (`isInternalEngineSelector`).
15
+ // 2. TRANSLATING it to the idiomatic public method form
16
+ // (`internalToMethodExpression`) — `getByRole(...)`, `getByText(...)`, …
17
+ // 3. SANITIZING a full locator expression (`sanitizeLocatorExpression`):
18
+ // rewriting `recv.locator("internal:…")` → `recv.getByRole(…)`, and
19
+ // reporting whether anything internal remains (unconvertible ⇒ unsafe).
20
+ //
21
+ // Pure, dependency-light (ts-morph only for expression rewriting). Every
22
+ // write-head (InitializerUpdater, MutationApplier, SourceUpdater regex gate)
23
+ // consults this so the invariant holds across all heal paths.
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.isInternalEngineSelector = isInternalEngineSelector;
26
+ exports.parseInternalSelector = parseInternalSelector;
27
+ exports.buildMethodCall = buildMethodCall;
28
+ exports.internalToMethodExpression = internalToMethodExpression;
29
+ exports.internalToPublicSelectorString = internalToPublicSelectorString;
30
+ exports.sanitizeLocatorExpression = sanitizeLocatorExpression;
31
+ const ts_morph_1 = require("ts-morph");
32
+ // ═══════════════════════════════════════════════════════════════════
33
+ // DETECTION
34
+ // ═══════════════════════════════════════════════════════════════════
35
+ /**
36
+ * True when `value` is (or contains, in a `>>` chain) a Playwright internal
37
+ * engine selector. These start with the `internal:` engine prefix.
38
+ */
39
+ function isInternalEngineSelector(value) {
40
+ if (!value)
41
+ return false;
42
+ return value
43
+ .split(">>")
44
+ .some((seg) => seg.trim().toLowerCase().startsWith("internal:"));
45
+ }
46
+ /**
47
+ * Parse a SINGLE internal selector segment into a method descriptor, or null
48
+ * when it is not a recognised internal form (so callers REFUSE rather than
49
+ * guess). Mirrors the engine→public mapping used by ASTSourceUpdater.
50
+ */
51
+ function parseInternalSelector(selector) {
52
+ // Match against the raw (trimmed) selector. The case-insensitivity `i` flag
53
+ // is significant per-pattern (trailing for text/label, inside `[…]` for
54
+ // role), so it must NOT be stripped globally up front.
55
+ const s = selector.trim();
56
+ // internal:role=button[name="X"i][level=2]
57
+ const roleMatch = s.match(/^internal:role=([a-zA-Z]+)(\[.*\])?$/);
58
+ if (roleMatch) {
59
+ const desc = {
60
+ method: "getByRole",
61
+ primaryArg: roleMatch[1],
62
+ };
63
+ const attrs = roleMatch[2] ?? "";
64
+ const nameMatch = attrs.match(/name="([^"]*)"(i)?/);
65
+ const options = {};
66
+ if (nameMatch) {
67
+ options.name = nameMatch[1];
68
+ if (!nameMatch[2])
69
+ options.exact = true; // no `i` flag → exact match
70
+ }
71
+ const levelMatch = attrs.match(/level=(\d+)/);
72
+ if (levelMatch)
73
+ options.level = Number(levelMatch[1]);
74
+ if (Object.keys(options).length)
75
+ desc.options = options;
76
+ return desc;
77
+ }
78
+ // internal:label="X"i | internal:label=X
79
+ const labelQuoted = s.match(/^internal:label="([^"]*)"(i)?$/);
80
+ if (labelQuoted) {
81
+ return labelQuoted[2]
82
+ ? { method: "getByLabel", primaryArg: labelQuoted[1] }
83
+ : { method: "getByLabel", primaryArg: labelQuoted[1], options: { exact: true } };
84
+ }
85
+ const labelBare = s.match(/^internal:label=([^[]+)$/);
86
+ if (labelBare) {
87
+ return { method: "getByLabel", primaryArg: labelBare[1].trim() };
88
+ }
89
+ // internal:text="X"i
90
+ const textMatch = s.match(/^internal:text="([^"]*)"(i)?$/);
91
+ if (textMatch) {
92
+ return textMatch[2]
93
+ ? { method: "getByText", primaryArg: textMatch[1] }
94
+ : { method: "getByText", primaryArg: textMatch[1], options: { exact: true } };
95
+ }
96
+ // internal:attr=[placeholder="X"] / [alt="X"] / [title="X"]
97
+ const placeholder = s.match(/^internal:attr=\[placeholder="([^"]*)"\]$/);
98
+ if (placeholder)
99
+ return { method: "getByPlaceholder", primaryArg: placeholder[1] };
100
+ const alt = s.match(/^internal:attr=\[alt="([^"]*)"\]$/);
101
+ if (alt)
102
+ return { method: "getByAltText", primaryArg: alt[1] };
103
+ const title = s.match(/^internal:attr=\[title="([^"]*)"\]$/);
104
+ if (title)
105
+ return { method: "getByTitle", primaryArg: title[1] };
106
+ // internal:testid=X | internal:attr=[data-testid="X"]
107
+ const testId = s.match(/^internal:testid=(.+)$/) ||
108
+ s.match(/^internal:attr=\[data-testid="([^"]*)"\]$/);
109
+ if (testId)
110
+ return { method: "getByTestId", primaryArg: testId[1].trim() };
111
+ return null; // unrecognised internal form → caller must refuse
112
+ }
113
+ // ═══════════════════════════════════════════════════════════════════
114
+ // BUILD (method descriptor → idiomatic call text)
115
+ // ═══════════════════════════════════════════════════════════════════
116
+ function q(s) {
117
+ // Prefer double quotes, escaping any embedded ones.
118
+ return `"${s.replace(/"/g, '\\"')}"`;
119
+ }
120
+ function buildMethodCall(receiver, desc) {
121
+ switch (desc.method) {
122
+ case "getByRole": {
123
+ const opts = [];
124
+ if (desc.options?.name !== undefined)
125
+ opts.push(`name: ${q(String(desc.options.name))}`);
126
+ if (desc.options?.exact === true)
127
+ opts.push(`exact: true`);
128
+ if (desc.options?.level !== undefined)
129
+ opts.push(`level: ${desc.options.level}`);
130
+ const optStr = opts.length ? `, { ${opts.join(", ")} }` : "";
131
+ return `${receiver}.getByRole(${q(desc.primaryArg)}${optStr})`;
132
+ }
133
+ case "getByText":
134
+ case "getByLabel": {
135
+ const exact = desc.options?.exact === true ? `, { exact: true }` : "";
136
+ return `${receiver}.${desc.method}(${q(desc.primaryArg)}${exact})`;
137
+ }
138
+ case "getByPlaceholder":
139
+ case "getByAltText":
140
+ case "getByTitle":
141
+ case "getByTestId":
142
+ return `${receiver}.${desc.method}(${q(desc.primaryArg)})`;
143
+ default:
144
+ return `${receiver}.locator(${q(desc.primaryArg)})`;
145
+ }
146
+ }
147
+ // ═══════════════════════════════════════════════════════════════════
148
+ // TRANSLATE (full internal selector string → method expression)
149
+ // ═══════════════════════════════════════════════════════════════════
150
+ /**
151
+ * Translate an internal selector string into an idiomatic Playwright method
152
+ * expression rooted at `receiver`. Handles `>>` chains by chaining each
153
+ * segment. Returns null when ANY segment is an unrecognised internal form
154
+ * (frame `internal:control=…` plumbing, unknown engines) — the caller must
155
+ * then refuse rather than emit something fragile.
156
+ */
157
+ function internalToMethodExpression(selector, receiver = "page") {
158
+ const segments = selector
159
+ .split(">>")
160
+ .map((s) => s.trim())
161
+ .filter(Boolean);
162
+ if (segments.length === 0)
163
+ return null;
164
+ let expr = receiver;
165
+ for (const seg of segments) {
166
+ if (seg.toLowerCase().startsWith("internal:")) {
167
+ const desc = parseInternalSelector(seg);
168
+ if (!desc)
169
+ return null; // unconvertible internal form
170
+ expr = buildMethodCall(expr, desc);
171
+ }
172
+ else {
173
+ // Plain CSS / text segment — stays a public .locator() string.
174
+ expr = `${expr}.locator(${q(seg)})`;
175
+ }
176
+ }
177
+ return expr;
178
+ }
179
+ // ═══════════════════════════════════════════════════════════════════
180
+ // TRANSLATE (internal selector → valid PUBLIC .locator() string)
181
+ // ═══════════════════════════════════════════════════════════════════
182
+ function escAttr(s) {
183
+ return s.replace(/"/g, '\\"');
184
+ }
185
+ /**
186
+ * Convert a single internal segment to a valid PUBLIC `.locator()` selector
187
+ * string (the documented selector engines: `role=`, `text=`, css attribute).
188
+ * Returns null for forms that have NO public string engine (label is an
189
+ * associative relationship, not an attribute — only `getByLabel(...)` works).
190
+ */
191
+ function segmentToPublicSelector(seg) {
192
+ const desc = parseInternalSelector(seg);
193
+ if (!desc)
194
+ return null;
195
+ switch (desc.method) {
196
+ case "getByRole": {
197
+ const attrs = [];
198
+ if (desc.options?.name !== undefined)
199
+ attrs.push(`name="${escAttr(String(desc.options.name))}"`);
200
+ if (desc.options?.level !== undefined)
201
+ attrs.push(`level=${desc.options.level}`);
202
+ const attrStr = attrs.length ? `[${attrs.join("][")}]` : "";
203
+ return `role=${desc.primaryArg}${attrStr}`;
204
+ }
205
+ case "getByText":
206
+ // Quoted text engine = exact-ish; unquoted = substring (i-flag default).
207
+ return desc.options?.exact
208
+ ? `text="${escAttr(desc.primaryArg)}"`
209
+ : `text=${desc.primaryArg}`;
210
+ case "getByPlaceholder":
211
+ return `[placeholder="${escAttr(desc.primaryArg)}"]`;
212
+ case "getByAltText":
213
+ return `[alt="${escAttr(desc.primaryArg)}"]`;
214
+ case "getByTitle":
215
+ return `[title="${escAttr(desc.primaryArg)}"]`;
216
+ case "getByTestId":
217
+ return `[data-testid="${escAttr(desc.primaryArg)}"]`;
218
+ case "getByLabel":
219
+ default:
220
+ return null; // no public string-engine equivalent
221
+ }
222
+ }
223
+ /**
224
+ * Convert an internal engine selector into a VALID PUBLIC `.locator()` string,
225
+ * preserving `>>` chaining. Returns null when ANY internal segment has no
226
+ * public string engine (so the caller must method-shift or refuse instead of
227
+ * stuffing an unusable string into a constant).
228
+ *
229
+ * Example: `internal:role=button[name="Update System"i]`
230
+ * → `role=button[name="Update System"]`
231
+ */
232
+ function internalToPublicSelectorString(selector) {
233
+ const segments = selector
234
+ .split(">>")
235
+ .map((s) => s.trim())
236
+ .filter(Boolean);
237
+ if (segments.length === 0)
238
+ return null;
239
+ const out = [];
240
+ for (const seg of segments) {
241
+ if (seg.toLowerCase().startsWith("internal:")) {
242
+ const pub = segmentToPublicSelector(seg);
243
+ if (!pub)
244
+ return null;
245
+ out.push(pub);
246
+ }
247
+ else {
248
+ out.push(seg); // already a public css/text segment
249
+ }
250
+ }
251
+ return out.join(" >> ");
252
+ }
253
+ let probeCounter = 0;
254
+ /**
255
+ * Rewrite any `recv.locator("internal:…")` / `recv.frameLocator("internal:…")`
256
+ * inside `expr` to its idiomatic method form. Returns `safe:false` when an
257
+ * internal selector remains that cannot be translated, so the caller refuses
258
+ * to write it.
259
+ */
260
+ function sanitizeLocatorExpression(expr) {
261
+ // Fast path: nothing internal anywhere.
262
+ if (!expr.includes("internal:")) {
263
+ return { text: expr, sanitized: false, safe: true };
264
+ }
265
+ const project = new ts_morph_1.Project({
266
+ useInMemoryFileSystem: true,
267
+ compilerOptions: { allowJs: true, noEmit: true, skipLibCheck: true, noResolve: true },
268
+ });
269
+ let current = expr;
270
+ let sanitized = false;
271
+ // Iteratively rewrite the first internal locator call until none remain.
272
+ // replaceWithText invalidates nodes, so we re-parse each pass.
273
+ for (let pass = 0; pass < 32; pass++) {
274
+ let sf;
275
+ try {
276
+ sf = project.createSourceFile(`__san${probeCounter++}.ts`, `const __x = ${current};`, {
277
+ overwrite: true,
278
+ });
279
+ }
280
+ catch {
281
+ return { text: expr, sanitized, safe: false };
282
+ }
283
+ const call = sf
284
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
285
+ .find((c) => {
286
+ const callee = c.getExpression();
287
+ if (!ts_morph_1.Node.isPropertyAccessExpression(callee))
288
+ return false;
289
+ const m = callee.getName();
290
+ if (m !== "locator" && m !== "frameLocator")
291
+ return false;
292
+ const arg = c.getArguments()[0];
293
+ if (!arg)
294
+ return false;
295
+ const lit = ts_morph_1.Node.isStringLiteral(arg) || ts_morph_1.Node.isNoSubstitutionTemplateLiteral(arg)
296
+ ? arg.getLiteralValue()
297
+ : null;
298
+ return lit !== null && isInternalEngineSelector(lit);
299
+ });
300
+ if (!call)
301
+ break; // no more internal locator calls
302
+ const callee = call.getExpression();
303
+ const receiver = callee.getExpression().getText();
304
+ const arg = call.getArguments()[0];
305
+ const internalSel = arg.getLiteralValue();
306
+ const methodExpr = internalToMethodExpression(internalSel, receiver);
307
+ if (!methodExpr) {
308
+ // Unconvertible — leave as-is and report unsafe.
309
+ return { text: current, sanitized, safe: false };
310
+ }
311
+ call.replaceWithText(methodExpr);
312
+ current = sf.getVariableDeclaration("__x").getInitializer().getText();
313
+ sanitized = true;
314
+ }
315
+ // Final integrity check: nothing internal must survive.
316
+ const safe = !isInternalEngineSelector(current) && !current.includes("internal:");
317
+ return { text: current, sanitized, safe };
318
+ }
@@ -39,7 +39,7 @@ const path = __importStar(require("path"));
39
39
  class SnapshotService {
40
40
  baseDir;
41
41
  constructor() {
42
- this.baseDir = path.join(process.cwd(), "fixwright-snapshots");
42
+ this.baseDir = path.join(process.cwd(), "sela-snapshots");
43
43
  if (!fs.existsSync(this.baseDir)) {
44
44
  fs.mkdirSync(this.baseDir, { recursive: true });
45
45
  }
@@ -67,12 +67,12 @@ class SnapshotService {
67
67
  return raw;
68
68
  }
69
69
  // ─────────────────────────────────────────────────────────────
70
- // captureElement single-pass atomic extraction of all v2 metrics.
70
+ // captureElement - single-pass atomic extraction of all v2 metrics.
71
71
  //
72
72
  // All data collection (core props, ancestry, visibility, anchors,
73
73
  // shadow-DOM flag) runs inside ONE page.evaluate() call so the
74
74
  // browser serialises a single IPC payload. Returns null on any
75
- // failure callers must treat null as "capture skipped" and not
75
+ // failure - callers must treat null as "capture skipped" and not
76
76
  // block the heal.
77
77
  // ─────────────────────────────────────────────────────────────
78
78
  async captureElement(page, selector, framePath = []) {
@@ -219,7 +219,7 @@ class SnapshotService {
219
219
  closestLabel = t;
220
220
  }
221
221
  }
222
- // P4: implicit <label> wrapper clone and strip the form element
222
+ // P4: implicit <label> wrapper - clone and strip the form element
223
223
  if (!closestLabel) {
224
224
  const wrapper = el.closest("label");
225
225
  if (wrapper) {
@@ -318,10 +318,10 @@ class SnapshotService {
318
318
  }
319
319
  }
320
320
  // ─────────────────────────────────────────────────────────────
321
- // migrateToV2 backfills default v2 fields onto a v1 snapshot.
321
+ // migrateToV2 - backfills default v2 fields onto a v1 snapshot.
322
322
  //
323
323
  // ancestry, visibility, and anchors are empty / safe defaults.
324
- // The migrated snapshot is NOT re-saved to disk automatically
324
+ // The migrated snapshot is NOT re-saved to disk automatically -
325
325
  // the engine decides when to flush.
326
326
  // ─────────────────────────────────────────────────────────────
327
327
  migrateToV2(v1) {
@@ -1,5 +1,5 @@
1
- import { type ASTUpdateResult } from './ASTSourceUpdater.js';
2
- import type { DnaRecord, DnaEditPatch } from './types.js';
1
+ import { type ASTUpdateResult } from "./ASTSourceUpdater.js";
2
+ import type { DnaRecord, DnaEditPatch } from "./types.js";
3
3
  interface CallerContext {
4
4
  filePath: string;
5
5
  line: number;
@@ -1 +1 @@
1
- {"version":3,"file":"SourceLinkService.d.ts","sourceRoot":"","sources":["../../src/services/SourceLinkService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1D,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAiDD,MAAM,WAAW,kBAAkB;IACjC,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IACvD,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IAC9E,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACrF;AAED,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGrB;IAEH,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa;IAItD,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAyDvE,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;CAyD1F"}
1
+ {"version":3,"file":"SourceLinkService.d.ts","sourceRoot":"","sources":["../../src/services/SourceLinkService.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1D,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAoDD,MAAM,WAAW,kBAAkB;IACjC,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IACvD,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IAC9E,iBAAiB,CACf,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7B;AAED,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGrB;IAEH,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa;IAItD,uBAAuB,CACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,kBAAkB,EAAE;IAyDjB,iBAAiB,CACrB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,CAAC;CAuE5B"}
@@ -10,15 +10,15 @@ const ts_morph_1 = require("ts-morph");
10
10
  const ASTSourceUpdater_js_1 = require("./ASTSourceUpdater.js");
11
11
  // Mirror of ASTSourceUpdater's module-scoped ALL_SELECTOR_METHODS.
12
12
  const ALL_SELECTOR_METHODS = new Set([
13
- 'locator',
14
- 'frameLocator',
15
- 'getByRole',
16
- 'getByLabel',
17
- 'getByText',
18
- 'getByTestId',
19
- 'getByPlaceholder',
20
- 'getByAltText',
21
- 'getByTitle',
13
+ "locator",
14
+ "frameLocator",
15
+ "getByRole",
16
+ "getByLabel",
17
+ "getByText",
18
+ "getByTestId",
19
+ "getByPlaceholder",
20
+ "getByAltText",
21
+ "getByTitle",
22
22
  ]);
23
23
  function dnaError(message, code) {
24
24
  const err = new Error(message);
@@ -33,11 +33,12 @@ function extractSelector(call) {
33
33
  return null;
34
34
  const first = args[0];
35
35
  const kind = first.getKind();
36
- if (kind === ts_morph_1.SyntaxKind.StringLiteral || kind === ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
36
+ if (kind === ts_morph_1.SyntaxKind.StringLiteral ||
37
+ kind === ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
37
38
  const raw = first.getText();
38
39
  return raw.slice(1, -1); // strip surrounding quotes/backticks
39
40
  }
40
- // For template literals with expressions, identifiers, etc. return raw text.
41
+ // For template literals with expressions, identifiers, etc. - return raw text.
41
42
  return first.getText();
42
43
  }
43
44
  // Get the method name from a CallExpression's callee.
@@ -68,7 +69,7 @@ class SourceLinkService {
68
69
  return [];
69
70
  let sf;
70
71
  try {
71
- // addSourceFileAtPath throws if already added use getOrAdd pattern.
72
+ // addSourceFileAtPath throws if already added - use getOrAdd pattern.
72
73
  sf =
73
74
  this.project.getSourceFile(absPath) ??
74
75
  this.project.addSourceFileAtPath(absPath);
@@ -91,7 +92,7 @@ class SourceLinkService {
91
92
  if (selector === null)
92
93
  return;
93
94
  const raw = call.getText();
94
- const preview = raw.length > 80 ? raw.slice(0, 79) + '' : raw;
95
+ const preview = raw.length > 80 ? raw.slice(0, 79) + "" : raw;
95
96
  candidates.push({
96
97
  col: call.getStart(),
97
98
  preview,
@@ -110,16 +111,24 @@ class SourceLinkService {
110
111
  }
111
112
  async applySourceUpdate(record, patch) {
112
113
  if (!record.sourceFile) {
113
- return { success: false, reason: 'DNA record has no sourceFile', strategy: 'none' };
114
+ return {
115
+ success: false,
116
+ reason: "DNA record has no sourceFile",
117
+ strategy: "none",
118
+ };
114
119
  }
115
120
  if (!(0, fs_1.existsSync)(path_1.default.resolve(record.sourceFile))) {
116
- throw dnaError(`Source file not found: ${record.sourceFile}`, 'SOURCE_FILE_MISSING');
121
+ throw dnaError(`Source file not found: ${record.sourceFile}`, "SOURCE_FILE_MISSING");
117
122
  }
118
123
  if (record.sourceLine <= 0) {
119
- return { success: false, reason: 'DNA record has no valid sourceLine', strategy: 'none' };
124
+ return {
125
+ success: false,
126
+ reason: "DNA record has no valid sourceLine",
127
+ strategy: "none",
128
+ };
120
129
  }
121
130
  const caller = this.resolveCallerContext(record);
122
- if (patch.field === 'selector') {
131
+ if (patch.field === "selector") {
123
132
  const newSelector = patch.newValue;
124
133
  // Resolve oldSelector: prefer patch.oldValue, fall back to the locator
125
134
  // at patch.locatorIndex on the recorded line.
@@ -131,25 +140,25 @@ class SourceLinkService {
131
140
  return {
132
141
  success: false,
133
142
  reason: `No locator found at index ${patch.locatorIndex} on line ${record.sourceLine}`,
134
- strategy: 'none',
143
+ strategy: "none",
135
144
  };
136
145
  }
137
146
  oldSelector = target.selector;
138
147
  }
139
148
  return this.updater.update(caller, oldSelector, newSelector);
140
149
  }
141
- if (patch.field === 'text') {
150
+ if (patch.field === "text") {
142
151
  // Use ASTSourceUpdater's contentChange path for text-content edits.
143
152
  const contentChange = {
144
- oldText: String(patch.oldValue ?? ''),
145
- newText: String(patch.newValue ?? ''),
153
+ oldText: String(patch.oldValue ?? ""),
154
+ newText: String(patch.newValue ?? ""),
146
155
  };
147
156
  return this.updater.update(caller, record.selector, record.selector, undefined, undefined, contentChange);
148
157
  }
149
158
  return {
150
159
  success: false,
151
160
  reason: `Source update not supported for field: ${patch.field}`,
152
- strategy: 'none',
161
+ strategy: "none",
153
162
  };
154
163
  }
155
164
  }
@@ -4,7 +4,7 @@ interface UpdateResult {
4
4
  success: boolean;
5
5
  reason: string;
6
6
  lineUpdated?: number;
7
- /** Canonical locator string written to disk use this for runtime execution. */
7
+ /** Canonical locator string written to disk - use this for runtime execution. */
8
8
  healedLocatorString?: string;
9
9
  /** Populated by semantic dispatch: where the definition was mutated. */
10
10
  definitionSite?: {
@@ -1 +1 @@
1
- {"version":3,"file":"SourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/SourceUpdater.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA4BtD,UAAU,YAAY;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAqZD,qBAAa,aAAa;IAMxB,MAAM,CAAC,MAAM,CACX,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,UAAU,CAAC,EAAE,eAAe,EAAE,EAC9B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EACxC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EACvC,cAAc,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,QAAQ,EACrD,UAAU,CAAC,EAAE,OAAO,GACnB,YAAY;IAoDf,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAwC/B,OAAO,CAAC,MAAM,CAAC,WAAW;IAiT1B,OAAO,CAAC,MAAM,CAAC,2BAA2B;IA8E1C,OAAO,CAAC,MAAM,CAAC,wBAAwB;IA8EvC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAoErC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAsBjC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA6DpC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAuCpC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IA6CvC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAqCrC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAqGrC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IA0BjC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAatC,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAgBlD,MAAM,CAAC,cAAc,CACnB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,YAAY;IA0Cf,0FAA0F;IAC1F,MAAM,CAAC,eAAe,IAAI,IAAI;CAG/B"}
1
+ {"version":3,"file":"SourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/SourceUpdater.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA6BtD,UAAU,YAAY;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAqZD,qBAAa,aAAa;IAMxB,MAAM,CAAC,MAAM,CACX,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,UAAU,CAAC,EAAE,eAAe,EAAE,EAC9B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EACxC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EACvC,cAAc,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,QAAQ,EACrD,UAAU,CAAC,EAAE,OAAO,GACnB,YAAY;IAoDf,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAwC/B,OAAO,CAAC,MAAM,CAAC,WAAW;IAsU1B,OAAO,CAAC,MAAM,CAAC,2BAA2B;IA8E1C,OAAO,CAAC,MAAM,CAAC,wBAAwB;IA8EvC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAoErC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAsBjC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA6DpC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAuCpC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IA6CvC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAqCrC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAqGrC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IA0BjC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAatC,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAgBlD,MAAM,CAAC,cAAc,CACnB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,YAAY;IA0Cf,0FAA0F;IAC1F,MAAM,CAAC,eAAe,IAAI,IAAI;CAG/B"}