sela-core 1.0.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.
Files changed (144) hide show
  1. package/README.md +93 -0
  2. package/bin/sela.js +3 -0
  3. package/dist/cli/ErrorHandler.d.ts +10 -0
  4. package/dist/cli/ErrorHandler.d.ts.map +1 -0
  5. package/dist/cli/ErrorHandler.js +70 -0
  6. package/dist/cli/commands/bulk.d.ts +3 -0
  7. package/dist/cli/commands/bulk.d.ts.map +1 -0
  8. package/dist/cli/commands/bulk.js +140 -0
  9. package/dist/cli/commands/find.d.ts +3 -0
  10. package/dist/cli/commands/find.d.ts.map +1 -0
  11. package/dist/cli/commands/find.js +51 -0
  12. package/dist/cli/commands/init.d.ts +3 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -0
  14. package/dist/cli/commands/init.js +133 -0
  15. package/dist/cli/commands/list.d.ts +3 -0
  16. package/dist/cli/commands/list.d.ts.map +1 -0
  17. package/dist/cli/commands/list.js +56 -0
  18. package/dist/cli/commands/refactor.d.ts +3 -0
  19. package/dist/cli/commands/refactor.d.ts.map +1 -0
  20. package/dist/cli/commands/refactor.js +30 -0
  21. package/dist/cli/commands/status.d.ts +3 -0
  22. package/dist/cli/commands/status.d.ts.map +1 -0
  23. package/dist/cli/commands/status.js +51 -0
  24. package/dist/cli/commands/sync.d.ts +3 -0
  25. package/dist/cli/commands/sync.d.ts.map +1 -0
  26. package/dist/cli/commands/sync.js +123 -0
  27. package/dist/cli/index.d.ts +2 -0
  28. package/dist/cli/index.d.ts.map +1 -0
  29. package/dist/cli/index.js +42 -0
  30. package/dist/cli/ui/DnaTable.d.ts +3 -0
  31. package/dist/cli/ui/DnaTable.d.ts.map +1 -0
  32. package/dist/cli/ui/DnaTable.js +70 -0
  33. package/dist/cli/ui/LocatorPicker.d.ts +8 -0
  34. package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
  35. package/dist/cli/ui/LocatorPicker.js +33 -0
  36. package/dist/cli/ui/ProgressReporter.d.ts +11 -0
  37. package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
  38. package/dist/cli/ui/ProgressReporter.js +33 -0
  39. package/dist/cli/ui/RefactorWizard.d.ts +26 -0
  40. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
  41. package/dist/cli/ui/RefactorWizard.js +269 -0
  42. package/dist/config/ConfigLoader.d.ts +15 -0
  43. package/dist/config/ConfigLoader.d.ts.map +1 -0
  44. package/dist/config/ConfigLoader.js +174 -0
  45. package/dist/config/SelaConfig.d.ts +67 -0
  46. package/dist/config/SelaConfig.d.ts.map +1 -0
  47. package/dist/config/SelaConfig.js +57 -0
  48. package/dist/config/defineConfig.d.ts +3 -0
  49. package/dist/config/defineConfig.d.ts.map +1 -0
  50. package/dist/config/defineConfig.js +9 -0
  51. package/dist/engine/FixwrightEngine.d.ts +24 -0
  52. package/dist/engine/FixwrightEngine.d.ts.map +1 -0
  53. package/dist/engine/FixwrightEngine.js +403 -0
  54. package/dist/engine/HealingRegistry.d.ts +40 -0
  55. package/dist/engine/HealingRegistry.d.ts.map +1 -0
  56. package/dist/engine/HealingRegistry.js +98 -0
  57. package/dist/engine/singleton.d.ts +3 -0
  58. package/dist/engine/singleton.d.ts.map +1 -0
  59. package/dist/engine/singleton.js +5 -0
  60. package/dist/fixtures/expectProxy.d.ts +12 -0
  61. package/dist/fixtures/expectProxy.d.ts.map +1 -0
  62. package/dist/fixtures/expectProxy.js +228 -0
  63. package/dist/fixtures/index.d.ts +6 -0
  64. package/dist/fixtures/index.d.ts.map +1 -0
  65. package/dist/fixtures/index.js +688 -0
  66. package/dist/fixtures/moduleExpect.d.ts +2 -0
  67. package/dist/fixtures/moduleExpect.d.ts.map +1 -0
  68. package/dist/fixtures/moduleExpect.js +46 -0
  69. package/dist/index.d.ts +7 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +28 -0
  72. package/dist/services/ASTSourceUpdater.d.ts +79 -0
  73. package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
  74. package/dist/services/ASTSourceUpdater.js +3177 -0
  75. package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
  76. package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
  77. package/dist/services/ArgumentTypeAnalyzer.js +92 -0
  78. package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
  79. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
  80. package/dist/services/BlastRadiusAnalyzer.js +103 -0
  81. package/dist/services/ChainValidator.d.ts +76 -0
  82. package/dist/services/ChainValidator.d.ts.map +1 -0
  83. package/dist/services/ChainValidator.js +569 -0
  84. package/dist/services/CrossFileHealer.d.ts +6 -0
  85. package/dist/services/CrossFileHealer.d.ts.map +1 -0
  86. package/dist/services/CrossFileHealer.js +134 -0
  87. package/dist/services/DefinitionTracer.d.ts +41 -0
  88. package/dist/services/DefinitionTracer.d.ts.map +1 -0
  89. package/dist/services/DefinitionTracer.js +350 -0
  90. package/dist/services/DnaEditorService.d.ts +31 -0
  91. package/dist/services/DnaEditorService.d.ts.map +1 -0
  92. package/dist/services/DnaEditorService.js +198 -0
  93. package/dist/services/DnaIndexService.d.ts +24 -0
  94. package/dist/services/DnaIndexService.d.ts.map +1 -0
  95. package/dist/services/DnaIndexService.js +131 -0
  96. package/dist/services/HealingAdvisory.d.ts +22 -0
  97. package/dist/services/HealingAdvisory.d.ts.map +1 -0
  98. package/dist/services/HealingAdvisory.js +42 -0
  99. package/dist/services/HealthReportService.d.ts +10 -0
  100. package/dist/services/HealthReportService.d.ts.map +1 -0
  101. package/dist/services/HealthReportService.js +84 -0
  102. package/dist/services/InitializerUpdater.d.ts +16 -0
  103. package/dist/services/InitializerUpdater.d.ts.map +1 -0
  104. package/dist/services/InitializerUpdater.js +37 -0
  105. package/dist/services/IntentAuditor.d.ts +39 -0
  106. package/dist/services/IntentAuditor.d.ts.map +1 -0
  107. package/dist/services/IntentAuditor.js +302 -0
  108. package/dist/services/LLMService.d.ts +100 -0
  109. package/dist/services/LLMService.d.ts.map +1 -0
  110. package/dist/services/LLMService.js +439 -0
  111. package/dist/services/SafetyGuard.d.ts +65 -0
  112. package/dist/services/SafetyGuard.d.ts.map +1 -0
  113. package/dist/services/SafetyGuard.js +524 -0
  114. package/dist/services/SnapshotService.d.ts +11 -0
  115. package/dist/services/SnapshotService.d.ts.map +1 -0
  116. package/dist/services/SnapshotService.js +349 -0
  117. package/dist/services/SourceLinkService.d.ts +26 -0
  118. package/dist/services/SourceLinkService.d.ts.map +1 -0
  119. package/dist/services/SourceLinkService.js +156 -0
  120. package/dist/services/SourceUpdater.d.ts +45 -0
  121. package/dist/services/SourceUpdater.d.ts.map +1 -0
  122. package/dist/services/SourceUpdater.js +1021 -0
  123. package/dist/services/TemplateDiffService.d.ts +25 -0
  124. package/dist/services/TemplateDiffService.d.ts.map +1 -0
  125. package/dist/services/TemplateDiffService.js +331 -0
  126. package/dist/services/types.d.ts +59 -0
  127. package/dist/services/types.d.ts.map +1 -0
  128. package/dist/services/types.js +2 -0
  129. package/dist/storage/SnapshotManager.d.ts +5 -0
  130. package/dist/storage/SnapshotManager.d.ts.map +1 -0
  131. package/dist/storage/SnapshotManager.js +31 -0
  132. package/dist/types/index.d.ts +95 -0
  133. package/dist/types/index.d.ts.map +1 -0
  134. package/dist/types/index.js +7 -0
  135. package/dist/utils/DOMUtils.d.ts +33 -0
  136. package/dist/utils/DOMUtils.d.ts.map +1 -0
  137. package/dist/utils/DOMUtils.js +179 -0
  138. package/dist/utils/StackUtils.d.ts +11 -0
  139. package/dist/utils/StackUtils.d.ts.map +1 -0
  140. package/dist/utils/StackUtils.js +120 -0
  141. package/dist/vendor/enquirer.d.ts +33 -0
  142. package/dist/vendor/enquirer.d.ts.map +1 -0
  143. package/dist/vendor/enquirer.js +11 -0
  144. package/package.json +67 -0
@@ -0,0 +1,349 @@
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.SnapshotService = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class SnapshotService {
40
+ baseDir;
41
+ constructor() {
42
+ this.baseDir = path.join(process.cwd(), "fixwright-snapshots");
43
+ if (!fs.existsSync(this.baseDir)) {
44
+ fs.mkdirSync(this.baseDir, { recursive: true });
45
+ }
46
+ }
47
+ async save(key, data) {
48
+ if (!key || key === "unknown_location")
49
+ return;
50
+ const filePath = path.join(this.baseDir, `${key}.json`);
51
+ try {
52
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
53
+ console.log(`[Fixwright] 📸 Snapshot saved: ${key}.json`);
54
+ }
55
+ catch (err) {
56
+ console.error(`[Fixwright] ❌ Failed to save snapshot:`, err);
57
+ }
58
+ }
59
+ async load(key) {
60
+ const filePath = path.join(this.baseDir, `${key}.json`);
61
+ if (!fs.existsSync(filePath))
62
+ return null;
63
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
64
+ if (raw.schemaVersion !== 2) {
65
+ return this.migrateToV2(raw);
66
+ }
67
+ return raw;
68
+ }
69
+ // ─────────────────────────────────────────────────────────────
70
+ // captureElement — single-pass atomic extraction of all v2 metrics.
71
+ //
72
+ // All data collection (core props, ancestry, visibility, anchors,
73
+ // shadow-DOM flag) runs inside ONE page.evaluate() call so the
74
+ // browser serialises a single IPC payload. Returns null on any
75
+ // failure — callers must treat null as "capture skipped" and not
76
+ // block the heal.
77
+ // ─────────────────────────────────────────────────────────────
78
+ async captureElement(page, selector, framePath = []) {
79
+ try {
80
+ // Navigate into the frame chain before resolving the element selector,
81
+ // mirroring the same frame-traversal pattern used by fetchLiveText().
82
+ let ctx = page;
83
+ for (const frameSel of framePath) {
84
+ ctx = ctx.frameLocator(frameSel);
85
+ }
86
+ const raw = await ctx
87
+ .locator(selector)
88
+ .first()
89
+ .evaluate((el) => {
90
+ // ── helpers ──────────────────────────────────────────────────
91
+ function makeFingerprint(node) {
92
+ const nodeId = node.id || "";
93
+ const classes = Array.from(node.classList).sort().join(",");
94
+ return `${node.tagName.toLowerCase()}#${nodeId}[${classes}]`;
95
+ }
96
+ function isShadowRoot(node) {
97
+ // nodeType 11 = DOCUMENT_FRAGMENT_NODE; ShadowRoot also has .host
98
+ return node.nodeType === 11 && "host" in node;
99
+ }
100
+ // ── 1. Core properties ────────────────────────────────────────
101
+ const tagName = el.tagName.toLowerCase();
102
+ const elId = el.id || null;
103
+ const text = el.innerText?.trim() || el.textContent?.trim() || null;
104
+ const classes = Array.from(el.classList);
105
+ const attributes = {};
106
+ for (const attr of Array.from(el.attributes)) {
107
+ attributes[attr.name] = attr.value;
108
+ }
109
+ const role = el.getAttribute("role") || null;
110
+ const domRect = el.getBoundingClientRect();
111
+ const rect = {
112
+ x: domRect.x,
113
+ y: domRect.y,
114
+ width: domRect.width,
115
+ height: domRect.height,
116
+ };
117
+ // ── 2. Shadow DOM detection ───────────────────────────────────
118
+ let isInShadowDom = false;
119
+ let shadowHost = null;
120
+ let walkNode = el;
121
+ while (walkNode?.parentNode) {
122
+ if (isShadowRoot(walkNode.parentNode)) {
123
+ isInShadowDom = true;
124
+ shadowHost = walkNode.parentNode.host;
125
+ break;
126
+ }
127
+ walkNode = walkNode.parentNode;
128
+ }
129
+ // ── 3. Ancestry (depth ≤ 5, stop at shadow host boundary) ─────
130
+ const ancestry = [];
131
+ let ancestor = el.parentElement;
132
+ for (let depth = 0; depth < 5 && ancestor; depth++) {
133
+ const aId = ancestor.id || null;
134
+ const aClasses = Array.from(ancestor.classList).sort();
135
+ ancestry.push({
136
+ tag: ancestor.tagName.toLowerCase(),
137
+ id: aId,
138
+ classes: aClasses,
139
+ fingerprint: makeFingerprint(ancestor),
140
+ });
141
+ if (ancestor === shadowHost)
142
+ break;
143
+ ancestor = ancestor.parentElement;
144
+ }
145
+ const parentContext = ancestry[0]?.fingerprint ?? "";
146
+ // ── 4. Visibility ─────────────────────────────────────────────
147
+ const cs = window.getComputedStyle(el);
148
+ const opacity = parseFloat(cs.opacity) || 0;
149
+ let isGhost = false;
150
+ let ghostReason = null;
151
+ if (cs.display === "none") {
152
+ isGhost = true;
153
+ ghostReason = "G1_DISPLAY_NONE";
154
+ }
155
+ else if (cs.visibility === "hidden") {
156
+ isGhost = true;
157
+ ghostReason = "G2_VISIBILITY_HIDDEN";
158
+ }
159
+ else if (opacity < 0.05) {
160
+ // Decision: opacity < 0.05 treated as Ghost
161
+ isGhost = true;
162
+ ghostReason = "G3_OPACITY_ZERO";
163
+ }
164
+ else if (cs.pointerEvents === "none") {
165
+ // Child exception: ghost only if no immediate child re-enables pointer events
166
+ const hasInteractableChild = Array.from(el.children).some((child) => window.getComputedStyle(child).pointerEvents !==
167
+ "none");
168
+ if (!hasInteractableChild) {
169
+ isGhost = true;
170
+ ghostReason = "G4_POINTER_EVENTS_NONE";
171
+ }
172
+ }
173
+ else if (rect.width === 0 && rect.height === 0) {
174
+ isGhost = true;
175
+ ghostReason = "G5_ZERO_AREA";
176
+ }
177
+ else if (cs.getPropertyValue("content-visibility") === "hidden") {
178
+ isGhost = true;
179
+ ghostReason = "G6_CONTENT_VISIBILITY_HIDDEN";
180
+ }
181
+ // Occlusion: non-ghost only; null from elementFromPoint → assume clear (§1.2)
182
+ let isOccluded = false;
183
+ if (!isGhost && rect.width > 0 && rect.height > 0) {
184
+ try {
185
+ const cx = rect.x + rect.width / 2;
186
+ const cy = rect.y + rect.height / 2;
187
+ const topEl = document.elementFromPoint(cx, cy);
188
+ if (topEl !== null && topEl !== el && !el.contains(topEl)) {
189
+ isOccluded = true;
190
+ }
191
+ }
192
+ catch {
193
+ // elementFromPoint failure (unsized viewport, etc.) → assume clear
194
+ }
195
+ }
196
+ // ── 5. Anchors ────────────────────────────────────────────────
197
+ let closestLabel = null;
198
+ // P1: aria-labelledby
199
+ const labelledBy = el.getAttribute("aria-labelledby");
200
+ if (labelledBy) {
201
+ const labelEl = document.getElementById(labelledBy);
202
+ const t = labelEl ? (labelEl.textContent || "").trim() : "";
203
+ if (t)
204
+ closestLabel = t;
205
+ }
206
+ // P2: aria-label
207
+ if (!closestLabel) {
208
+ const ariaLabel = (el.getAttribute("aria-label") || "").trim();
209
+ if (ariaLabel)
210
+ closestLabel = ariaLabel;
211
+ }
212
+ // P3: explicit label[for="id"]
213
+ if (!closestLabel && el.id) {
214
+ const escaped = el.id.replace(/["\\]/g, "\\$&");
215
+ const labelEl = document.querySelector(`label[for="${escaped}"]`);
216
+ if (labelEl) {
217
+ const t = (labelEl.textContent || "").trim();
218
+ if (t)
219
+ closestLabel = t;
220
+ }
221
+ }
222
+ // P4: implicit <label> wrapper — clone and strip the form element
223
+ if (!closestLabel) {
224
+ const wrapper = el.closest("label");
225
+ if (wrapper) {
226
+ const clone = wrapper.cloneNode(true);
227
+ const inner = clone.querySelector(el.tagName.toLowerCase());
228
+ if (inner)
229
+ inner.remove();
230
+ const t = (clone.textContent || "").trim();
231
+ if (t)
232
+ closestLabel = t;
233
+ }
234
+ }
235
+ // P5: preceding visible sibling (< 80 chars)
236
+ if (!closestLabel) {
237
+ let sib = el.previousElementSibling;
238
+ while (sib) {
239
+ const sibCs = window.getComputedStyle(sib);
240
+ if (sibCs.display !== "none" &&
241
+ sibCs.visibility !== "hidden") {
242
+ const t = (sib.textContent || "").trim();
243
+ if (t && t.length < 80) {
244
+ closestLabel = t;
245
+ break;
246
+ }
247
+ }
248
+ sib = sib.previousElementSibling;
249
+ }
250
+ }
251
+ // Row anchor: longest unique cell text in the containing table row
252
+ let rowAnchor = null;
253
+ const row = el.closest("tr") || el.closest('[role="row"]');
254
+ if (row) {
255
+ const cells = Array.from(row.querySelectorAll('td, th, [role="cell"], [role="gridcell"]')).filter((cell) => !cell.contains(el));
256
+ let bestText = "";
257
+ for (const cell of cells) {
258
+ const t = (cell.textContent || "").trim();
259
+ if (t.length > 0 &&
260
+ t.length < 120 &&
261
+ t.length > bestText.length) {
262
+ bestText = t;
263
+ }
264
+ }
265
+ if (bestText)
266
+ rowAnchor = bestText;
267
+ }
268
+ // Neighbor texts: closest siblings first, alternating, max 5, < 100 chars
269
+ const neighborTexts = [];
270
+ if (el.parentElement) {
271
+ const siblings = Array.from(el.parentElement.children);
272
+ const selfIdx = siblings.indexOf(el);
273
+ let lo = selfIdx - 1;
274
+ let hi = selfIdx + 1;
275
+ while (neighborTexts.length < 5 &&
276
+ (lo >= 0 || hi < siblings.length)) {
277
+ if (lo >= 0) {
278
+ const t = (siblings[lo].textContent || "").trim();
279
+ if (t && t.length < 100)
280
+ neighborTexts.push(t);
281
+ lo--;
282
+ }
283
+ if (hi < siblings.length && neighborTexts.length < 5) {
284
+ const t = (siblings[hi].textContent || "").trim();
285
+ if (t && t.length < 100)
286
+ neighborTexts.push(t);
287
+ hi++;
288
+ }
289
+ }
290
+ }
291
+ return {
292
+ tagName,
293
+ id: elId,
294
+ text,
295
+ classes,
296
+ attributes,
297
+ role,
298
+ parentContext,
299
+ rect,
300
+ ancestry,
301
+ visibility: { isGhost, ghostReason, isOccluded, opacity },
302
+ anchors: { closestLabel, rowAnchor, neighborTexts },
303
+ isInShadowDom,
304
+ };
305
+ }, undefined, { timeout: 3000 });
306
+ return {
307
+ ...raw,
308
+ schemaVersion: 2,
309
+ visibility: {
310
+ ...raw.visibility,
311
+ ghostReason: raw.visibility.ghostReason,
312
+ },
313
+ timestamp: new Date().toISOString(),
314
+ };
315
+ }
316
+ catch (err) {
317
+ console.debug(`[Fixwright] 🧬 captureElement skipped for "${selector}": ${err.message}`);
318
+ return null;
319
+ }
320
+ }
321
+ // ─────────────────────────────────────────────────────────────
322
+ // migrateToV2 — backfills default v2 fields onto a v1 snapshot.
323
+ //
324
+ // ancestry, visibility, and anchors are empty / safe defaults.
325
+ // The migrated snapshot is NOT re-saved to disk automatically —
326
+ // the engine decides when to flush.
327
+ // ─────────────────────────────────────────────────────────────
328
+ migrateToV2(v1) {
329
+ console.log(`[Fixwright] 🔄 Migrating v1 snapshot to v2 schema (key: "${v1.tagName}/${v1.id ?? "no-id"}")`);
330
+ return {
331
+ ...v1,
332
+ schemaVersion: 2,
333
+ ancestry: [],
334
+ visibility: {
335
+ isGhost: false,
336
+ ghostReason: null,
337
+ isOccluded: false,
338
+ opacity: 1,
339
+ },
340
+ anchors: {
341
+ closestLabel: null,
342
+ rowAnchor: null,
343
+ neighborTexts: [],
344
+ },
345
+ isInShadowDom: false,
346
+ };
347
+ }
348
+ }
349
+ exports.SnapshotService = SnapshotService;
@@ -0,0 +1,26 @@
1
+ import { type ASTUpdateResult } from './ASTSourceUpdater.js';
2
+ import type { DnaRecord, DnaEditPatch } from './types.js';
3
+ interface CallerContext {
4
+ filePath: string;
5
+ line: number;
6
+ }
7
+ export interface LocatorEnumeration {
8
+ index: number;
9
+ preview: string;
10
+ selector: string;
11
+ method: string;
12
+ }
13
+ export interface ISourceLinkService {
14
+ resolveCallerContext(record: DnaRecord): CallerContext;
15
+ enumerateLocatorsOnLine(filePath: string, line: number): LocatorEnumeration[];
16
+ applySourceUpdate(record: DnaRecord, patch: DnaEditPatch): Promise<ASTUpdateResult>;
17
+ }
18
+ export declare class SourceLinkService implements ISourceLinkService {
19
+ private readonly updater;
20
+ private readonly project;
21
+ resolveCallerContext(record: DnaRecord): CallerContext;
22
+ enumerateLocatorsOnLine(filePath: string, line: number): LocatorEnumeration[];
23
+ applySourceUpdate(record: DnaRecord, patch: DnaEditPatch): Promise<ASTUpdateResult>;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=SourceLinkService.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SourceLinkService = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = require("fs");
9
+ const ts_morph_1 = require("ts-morph");
10
+ const ASTSourceUpdater_js_1 = require("./ASTSourceUpdater.js");
11
+ // Mirror of ASTSourceUpdater's module-scoped ALL_SELECTOR_METHODS.
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',
22
+ ]);
23
+ function dnaError(message, code) {
24
+ const err = new Error(message);
25
+ err.code = code;
26
+ return err;
27
+ }
28
+ // Extract first-argument selector string from a call expression.
29
+ // Returns the raw arg text (without outer quotes) for string literals.
30
+ function extractSelector(call) {
31
+ const args = call.getArguments();
32
+ if (args.length === 0)
33
+ return null;
34
+ const first = args[0];
35
+ const kind = first.getKind();
36
+ if (kind === ts_morph_1.SyntaxKind.StringLiteral || kind === ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
37
+ const raw = first.getText();
38
+ return raw.slice(1, -1); // strip surrounding quotes/backticks
39
+ }
40
+ // For template literals with expressions, identifiers, etc. — return raw text.
41
+ return first.getText();
42
+ }
43
+ // Get the method name from a CallExpression's callee.
44
+ function calleeMethodName(call) {
45
+ const expr = call.getExpression();
46
+ const kind = expr.getKind();
47
+ if (kind === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
48
+ return expr.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAccessExpression).getName();
49
+ }
50
+ if (kind === ts_morph_1.SyntaxKind.Identifier) {
51
+ return expr.getText();
52
+ }
53
+ return null;
54
+ }
55
+ class SourceLinkService {
56
+ updater = new ASTSourceUpdater_js_1.ASTSourceUpdater();
57
+ // Shared Project so source files are cached across calls.
58
+ project = new ts_morph_1.Project({
59
+ skipAddingFilesFromTsConfig: true,
60
+ skipFileDependencyResolution: true,
61
+ });
62
+ resolveCallerContext(record) {
63
+ return { filePath: record.sourceFile, line: record.sourceLine };
64
+ }
65
+ enumerateLocatorsOnLine(filePath, line) {
66
+ const absPath = path_1.default.resolve(filePath);
67
+ if (!(0, fs_1.existsSync)(absPath))
68
+ return [];
69
+ let sf;
70
+ try {
71
+ // addSourceFileAtPath throws if already added — use getOrAdd pattern.
72
+ sf =
73
+ this.project.getSourceFile(absPath) ??
74
+ this.project.addSourceFileAtPath(absPath);
75
+ sf.refreshFromFileSystemSync();
76
+ }
77
+ catch {
78
+ return [];
79
+ }
80
+ const candidates = [];
81
+ sf.forEachDescendant((node) => {
82
+ if (node.getKind() !== ts_morph_1.SyntaxKind.CallExpression)
83
+ return;
84
+ const call = node;
85
+ if (call.getStartLineNumber() !== line)
86
+ return;
87
+ const method = calleeMethodName(call);
88
+ if (!method || !ALL_SELECTOR_METHODS.has(method))
89
+ return;
90
+ const selector = extractSelector(call);
91
+ if (selector === null)
92
+ return;
93
+ const raw = call.getText();
94
+ const preview = raw.length > 80 ? raw.slice(0, 79) + '…' : raw;
95
+ candidates.push({
96
+ col: call.getStart(),
97
+ preview,
98
+ selector,
99
+ method,
100
+ });
101
+ });
102
+ // Sort left-to-right by column (getStart() is absolute file offset, same line → same as column order).
103
+ candidates.sort((a, b) => a.col - b.col);
104
+ return candidates.map(({ preview, selector, method }, i) => ({
105
+ index: i,
106
+ preview,
107
+ selector,
108
+ method,
109
+ }));
110
+ }
111
+ async applySourceUpdate(record, patch) {
112
+ if (!record.sourceFile) {
113
+ return { success: false, reason: 'DNA record has no sourceFile', strategy: 'none' };
114
+ }
115
+ if (!(0, fs_1.existsSync)(path_1.default.resolve(record.sourceFile))) {
116
+ throw dnaError(`Source file not found: ${record.sourceFile}`, 'SOURCE_FILE_MISSING');
117
+ }
118
+ if (record.sourceLine <= 0) {
119
+ return { success: false, reason: 'DNA record has no valid sourceLine', strategy: 'none' };
120
+ }
121
+ const caller = this.resolveCallerContext(record);
122
+ if (patch.field === 'selector') {
123
+ const newSelector = patch.newValue;
124
+ // Resolve oldSelector: prefer patch.oldValue, fall back to the locator
125
+ // at patch.locatorIndex on the recorded line.
126
+ let oldSelector = patch.oldValue;
127
+ if (!oldSelector) {
128
+ const locators = this.enumerateLocatorsOnLine(record.sourceFile, record.sourceLine);
129
+ const target = locators[patch.locatorIndex] ?? locators[0];
130
+ if (!target) {
131
+ return {
132
+ success: false,
133
+ reason: `No locator found at index ${patch.locatorIndex} on line ${record.sourceLine}`,
134
+ strategy: 'none',
135
+ };
136
+ }
137
+ oldSelector = target.selector;
138
+ }
139
+ return this.updater.update(caller, oldSelector, newSelector);
140
+ }
141
+ if (patch.field === 'text') {
142
+ // Use ASTSourceUpdater's contentChange path for text-content edits.
143
+ const contentChange = {
144
+ oldText: String(patch.oldValue ?? ''),
145
+ newText: String(patch.newValue ?? ''),
146
+ };
147
+ return this.updater.update(caller, record.selector, record.selector, undefined, undefined, contentChange);
148
+ }
149
+ return {
150
+ success: false,
151
+ reason: `Source update not supported for field: ${patch.field}`,
152
+ strategy: 'none',
153
+ };
154
+ }
155
+ }
156
+ exports.SourceLinkService = SourceLinkService;
@@ -0,0 +1,45 @@
1
+ import { SelectorSegment } from "./LLMService";
2
+ import type { SmartChainSegment } from "./LLMService";
3
+ interface UpdateResult {
4
+ success: boolean;
5
+ reason: string;
6
+ lineUpdated?: number;
7
+ /** Canonical locator string written to disk — use this for runtime execution. */
8
+ healedLocatorString?: string;
9
+ /** Populated by semantic dispatch: where the definition was mutated. */
10
+ definitionSite?: {
11
+ file: string;
12
+ line: number;
13
+ };
14
+ /** Number of test functions referencing the mutated constant. */
15
+ blastRadius?: number;
16
+ }
17
+ interface CallerContext {
18
+ filePath: string;
19
+ line: number;
20
+ }
21
+ export declare class SourceUpdater {
22
+ static update(caller: CallerContext, oldSelector: string, newSelector: string, domContext?: string, dnaAttrHint?: string, fullSelectorContext?: string, aiSegments?: SelectorSegment[], contentChange?: {
23
+ oldText: string;
24
+ newText: string;
25
+ }, smartChainSegments?: SmartChainSegment[], originalChainHint?: SmartChainSegment[], updateStrategy?: "inPlace" | "commentOnly" | "branch", autoCommit?: boolean): UpdateResult;
26
+ private static applyCommentOnly;
27
+ private static _coreUpdate;
28
+ private static strategyChainCollapse_Regex;
29
+ private static strategyAssertionHealing;
30
+ private static strategyLayeredHealing;
31
+ private static findVariableOrigin;
32
+ private static strategyChainCollapse;
33
+ private static strategyDirectLiteral;
34
+ private static strategyUpstreamVariable;
35
+ private static strategyChainedLocator;
36
+ private static strategyFunctionReturn;
37
+ private static strategyGlobalScan;
38
+ private static extractAttrValueFromDom;
39
+ static resolveFilePath(raw: string): string | null;
40
+ static updateArgument(caller: CallerContext, action: string, oldArgument: string, newArgument: string): UpdateResult;
41
+ /** Flush buffered advisories to console. Call after Playwright prints its test result. */
42
+ static flushAdvisories(): void;
43
+ }
44
+ export {};
45
+ //# sourceMappingURL=SourceUpdater.d.ts.map
@@ -0,0 +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;AAMtD,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;AA0YD,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;IAwS1B,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;IASlD,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"}