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,25 @@
1
+ import { TemplateExpression } from "ts-morph";
2
+ export type DiffPatchResult = {
3
+ ok: true;
4
+ /** Full patched template string, backtick-wrapped, ready for replaceWithText(). */
5
+ patchedTemplate: string;
6
+ /** Zero-based indices of static segments that changed. */
7
+ changedSegmentIndices: number[];
8
+ } | {
9
+ ok: false;
10
+ reason: string;
11
+ /**
12
+ * Populated when alignWithNew fails due to structural simplification:
13
+ * what each span expression needs to evaluate to satisfy resolvedNew.
14
+ * Use for span-tracing fallback (resolve each span's source independently).
15
+ */
16
+ newSpanValues?: string[];
17
+ };
18
+ export declare class TemplateDiffService {
19
+ static diffAndPatch(templateText: string, resolvedOld: string, resolvedNew: string): DiffPatchResult;
20
+ static computeNodePatch(templateNode: TemplateExpression, resolvedOld: string, resolvedNew: string): DiffPatchResult;
21
+ static extractNodeSpanValues(templateNode: TemplateExpression, resolved: string): string[] | null;
22
+ }
23
+ /** Escape characters that would break a template literal. */
24
+ export declare function escapeForTemplate(text: string): string;
25
+ //# sourceMappingURL=TemplateDiffService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateDiffService.d.ts","sourceRoot":"","sources":["../../src/services/TemplateDiffService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,kBAAkB,EAAc,MAAM,UAAU,CAAC;AAMhE,MAAM,MAAM,eAAe,GACvB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,mFAAmF;IACnF,eAAe,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AA6BN,qBAAa,mBAAmB;IAW9B,MAAM,CAAC,YAAY,CACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,eAAe;IA6GlB,MAAM,CAAC,gBAAgB,CACrB,YAAY,EAAE,kBAAkB,EAChC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,eAAe;IAoFlB,MAAM,CAAC,qBAAqB,CAC1B,YAAY,EAAE,kBAAkB,EAChC,QAAQ,EAAE,MAAM,GACf,MAAM,EAAE,GAAG,IAAI;CASnB;AAgJD,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD"}
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TemplateDiffService = void 0;
4
+ exports.escapeForTemplate = escapeForTemplate;
5
+ // ═══════════════════════════════════════════════════════════════════
6
+ // TemplateDiffService
7
+ //
8
+ // T-8: String-based API — diffAndPatch()
9
+ // Given a template source like `input[data-role="${role}_legacy"]`,
10
+ // its resolved old string, and an AI-proposed new string, compute
11
+ // the patched template that preserves ${...} spans while updating
12
+ // only the changed static segments.
13
+ //
14
+ // T-8.1: Node-based API — computeNodePatch()
15
+ // Wraps diffAndPatch() using the TemplateExpression AST node as the
16
+ // source of truth (avoids text-parsing ambiguity). Returns the full
17
+ // patched template text for use with templateNode.replaceWithText().
18
+ // ═══════════════════════════════════════════════════════════════════
19
+ class TemplateDiffService {
20
+ // ─────────────────────────────────────────────────────────────────
21
+ // T-8: String-based API
22
+ //
23
+ // templateText: the template source (backticks optional), e.g.
24
+ // `input[data-role="${role}_legacy"]` or
25
+ // input[data-role="${role}_legacy"]
26
+ // resolvedOld: what the template evaluates to at the failing call site
27
+ // resolvedNew: the AI-proposed replacement string (flat, no ${...})
28
+ // ─────────────────────────────────────────────────────────────────
29
+ static diffAndPatch(templateText, resolvedOld, resolvedNew) {
30
+ const segments = parseTemplateSource(templateText);
31
+ const staticSegs = segments
32
+ .filter((s) => s.type === "static")
33
+ .map((s) => s.text);
34
+ const exprSegs = segments
35
+ .filter((s) => s.type === "expr")
36
+ .map((s) => s.text);
37
+ // No spans — the "template" is just a plain string.
38
+ if (exprSegs.length === 0) {
39
+ if (resolvedOld !== resolvedNew) {
40
+ return {
41
+ ok: true,
42
+ patchedTemplate: "`" + escapeForTemplate(resolvedNew) + "`",
43
+ changedSegmentIndices: [0],
44
+ };
45
+ }
46
+ return { ok: false, reason: "resolvedOld === resolvedNew — no change needed" };
47
+ }
48
+ // Extract span values from resolvedOld using static segments as anchors.
49
+ const spanValues = extractSpanValues(staticSegs, resolvedOld);
50
+ if (!spanValues) {
51
+ return {
52
+ ok: false,
53
+ reason: "template static segments don't align with resolvedOld — possible desync. " +
54
+ `template="${templateText}", resolvedOld="${resolvedOld}"`,
55
+ };
56
+ }
57
+ // Verify reconstruction — extractSpanValues already guarantees this
58
+ // implicitly, but an explicit check catches any edge-case bugs early.
59
+ {
60
+ let check = "";
61
+ for (let i = 0; i < staticSegs.length; i++) {
62
+ check += staticSegs[i];
63
+ if (i < spanValues.length)
64
+ check += spanValues[i];
65
+ }
66
+ if (check !== resolvedOld) {
67
+ return {
68
+ ok: false,
69
+ reason: `reconstruction mismatch: expected "${resolvedOld}", got "${check}"`,
70
+ };
71
+ }
72
+ }
73
+ // Align resolvedNew against the extracted span values.
74
+ const newStaticSegs = alignWithNew(spanValues, resolvedNew);
75
+ if (!newStaticSegs) {
76
+ return {
77
+ ok: false,
78
+ reason: `dynamic span value(s) [${spanValues.map((v) => `"${v}"`).join(", ")}] ` +
79
+ `not found in resolvedNew="${resolvedNew}" — structural change, manual review required`,
80
+ };
81
+ }
82
+ if (newStaticSegs.length !== staticSegs.length) {
83
+ return {
84
+ ok: false,
85
+ reason: "static segment count mismatch after alignment — structural change",
86
+ };
87
+ }
88
+ // Diff: find which static segments changed.
89
+ const changedIndices = [];
90
+ for (let i = 0; i < staticSegs.length; i++) {
91
+ if (staticSegs[i] !== newStaticSegs[i])
92
+ changedIndices.push(i);
93
+ }
94
+ if (changedIndices.length === 0) {
95
+ return {
96
+ ok: false,
97
+ reason: "no static segment changed — the difference is in a dynamic span. " +
98
+ "Update the argument passed to the function instead.",
99
+ };
100
+ }
101
+ if (changedIndices.length > 1) {
102
+ return {
103
+ ok: false,
104
+ reason: `${changedIndices.length} static segments changed simultaneously — ` +
105
+ "ambiguous, manual review required",
106
+ };
107
+ }
108
+ // Build the patched template.
109
+ const patchedStatics = [...staticSegs];
110
+ patchedStatics[changedIndices[0]] = newStaticSegs[changedIndices[0]];
111
+ return {
112
+ ok: true,
113
+ patchedTemplate: buildTemplate(segments, patchedStatics),
114
+ changedSegmentIndices: changedIndices,
115
+ };
116
+ }
117
+ // ─────────────────────────────────────────────────────────────────
118
+ // T-8.1: Node-based API
119
+ //
120
+ // Uses the TemplateExpression AST node as the authoritative source
121
+ // of static/expr segments (avoids text-parsing edge cases).
122
+ // Returns `patchedText` — pass directly to templateNode.replaceWithText().
123
+ // ─────────────────────────────────────────────────────────────────
124
+ static computeNodePatch(templateNode, resolvedOld, resolvedNew) {
125
+ // Extract static segments and expr texts directly from the AST.
126
+ const head = templateNode.getHead();
127
+ const spans = templateNode.getTemplateSpans();
128
+ const staticSegs = [head.getLiteralText()];
129
+ const exprTexts = [];
130
+ for (const span of spans) {
131
+ exprTexts.push(span.getExpression().getText());
132
+ staticSegs.push(span.getLiteral().getLiteralText());
133
+ }
134
+ // Rebuild equivalent segments array for buildTemplate().
135
+ const segments = [];
136
+ for (let i = 0; i < staticSegs.length; i++) {
137
+ segments.push({ type: "static", text: staticSegs[i] });
138
+ if (i < exprTexts.length) {
139
+ segments.push({ type: "expr", text: exprTexts[i] });
140
+ }
141
+ }
142
+ // No spans case.
143
+ if (exprTexts.length === 0) {
144
+ return {
145
+ ok: true,
146
+ patchedTemplate: "`" + escapeForTemplate(resolvedNew) + "`",
147
+ changedSegmentIndices: [0],
148
+ };
149
+ }
150
+ const spanValues = extractSpanValues(staticSegs, resolvedOld);
151
+ if (!spanValues) {
152
+ return {
153
+ ok: false,
154
+ reason: `template AST doesn't reconstruct to "${resolvedOld}"`,
155
+ };
156
+ }
157
+ const newStaticSegs = alignWithNew(spanValues, resolvedNew);
158
+ if (!newStaticSegs) {
159
+ const newSpanFromNew = extractSpanValues(staticSegs, resolvedNew);
160
+ return {
161
+ ok: false,
162
+ reason: `span values [${spanValues.map((v) => `"${v}"`).join(", ")}] ` +
163
+ `not found in new selector "${resolvedNew}"`,
164
+ ...(newSpanFromNew ? { newSpanValues: newSpanFromNew } : {}),
165
+ };
166
+ }
167
+ const changedIndices = [];
168
+ for (let i = 0; i < staticSegs.length; i++) {
169
+ if (staticSegs[i] !== newStaticSegs[i])
170
+ changedIndices.push(i);
171
+ }
172
+ if (changedIndices.length === 0) {
173
+ return {
174
+ ok: false,
175
+ reason: "difference is in dynamic span — update call-site argument",
176
+ };
177
+ }
178
+ if (changedIndices.length > 1) {
179
+ return {
180
+ ok: false,
181
+ reason: `${changedIndices.length} static segments changed — ambiguous`,
182
+ };
183
+ }
184
+ const patchedStatics = [...staticSegs];
185
+ patchedStatics[changedIndices[0]] = newStaticSegs[changedIndices[0]];
186
+ return {
187
+ ok: true,
188
+ patchedTemplate: buildTemplate(segments, patchedStatics),
189
+ changedSegmentIndices: changedIndices,
190
+ };
191
+ }
192
+ // ─────────────────────────────────────────────────────────────────
193
+ // Extract span values from a TemplateExpression node and a resolved string.
194
+ // Public so callers can derive old/new span values for span tracing.
195
+ // ─────────────────────────────────────────────────────────────────
196
+ static extractNodeSpanValues(templateNode, resolved) {
197
+ const head = templateNode.getHead();
198
+ const spans = templateNode.getTemplateSpans();
199
+ const staticSegs = [head.getLiteralText()];
200
+ for (const span of spans) {
201
+ staticSegs.push(span.getLiteral().getLiteralText());
202
+ }
203
+ return extractSpanValues(staticSegs, resolved);
204
+ }
205
+ }
206
+ exports.TemplateDiffService = TemplateDiffService;
207
+ // ═══════════════════════════════════════════════════════════════════
208
+ // Private helpers
209
+ // ═══════════════════════════════════════════════════════════════════
210
+ /**
211
+ * Parse a template literal source string into alternating static/expr segments.
212
+ * Strips surrounding backticks if present.
213
+ *
214
+ * `` `head${role}_tail` `` → [static:"head", expr:"role", static:"_tail"]
215
+ */
216
+ function parseTemplateSource(text) {
217
+ let src = text.trim();
218
+ if (src.startsWith("`") && src.endsWith("`")) {
219
+ src = src.slice(1, -1);
220
+ }
221
+ const segments = [];
222
+ const exprPattern = /\$\{([^}]+)\}/g;
223
+ let lastIndex = 0;
224
+ let match;
225
+ while ((match = exprPattern.exec(src)) !== null) {
226
+ // Static segment before this expression.
227
+ segments.push({ type: "static", text: src.slice(lastIndex, match.index) });
228
+ segments.push({ type: "expr", text: match[1].trim() });
229
+ lastIndex = match.index + match[0].length;
230
+ }
231
+ // Trailing static segment (may be empty).
232
+ segments.push({ type: "static", text: src.slice(lastIndex) });
233
+ return segments;
234
+ }
235
+ /**
236
+ * Given the static segments of a template and a resolved string,
237
+ * extract the dynamic span values by matching statics as anchors.
238
+ *
239
+ * Algorithm: iterate over all static segments in order. After consuming
240
+ * each static segment, find the next static as the delimiter for the
241
+ * span value — then advance pos to the start of that next static so
242
+ * the following iteration can consume it. This ensures pos is always
243
+ * consistent at the start of each static segment.
244
+ *
245
+ * Returns null if alignment fails (desync or ambiguous adjacent spans).
246
+ */
247
+ function extractSpanValues(staticSegments, resolved) {
248
+ const spanValues = [];
249
+ let pos = 0;
250
+ for (let i = 0; i < staticSegments.length; i++) {
251
+ // Consume static[i].
252
+ const s = staticSegments[i];
253
+ if (resolved.slice(pos, pos + s.length) !== s)
254
+ return null;
255
+ pos += s.length;
256
+ // If this is the last static segment, we're done.
257
+ if (i === staticSegments.length - 1)
258
+ break;
259
+ // Find span[i] value: delimited by static[i+1].
260
+ const nextStatic = staticSegments[i + 1];
261
+ if (nextStatic === "" && i + 1 === staticSegments.length - 1) {
262
+ // Empty trailing static: span value extends to end of string.
263
+ spanValues.push(resolved.slice(pos));
264
+ pos = resolved.length;
265
+ break;
266
+ }
267
+ if (nextStatic === "") {
268
+ // Empty non-trailing static: adjacent spans — boundary is ambiguous.
269
+ return null;
270
+ }
271
+ const delimIdx = resolved.indexOf(nextStatic, pos);
272
+ if (delimIdx === -1)
273
+ return null;
274
+ spanValues.push(resolved.slice(pos, delimIdx));
275
+ // Advance pos to start of nextStatic so the next iteration consumes it.
276
+ pos = delimIdx;
277
+ }
278
+ if (pos !== resolved.length)
279
+ return null;
280
+ return spanValues;
281
+ }
282
+ /**
283
+ * Given known span values (anchors) and a new resolved string,
284
+ * extract what the static segments should be in the patched template.
285
+ *
286
+ * Uses each span value as a positional anchor, in order.
287
+ * Returns null if any span value is missing from resolvedNew.
288
+ */
289
+ function alignWithNew(spanValues, resolvedNew) {
290
+ const newStatics = [];
291
+ let pos = 0;
292
+ for (const spanVal of spanValues) {
293
+ if (spanVal.length === 0) {
294
+ // Can't anchor on empty span value.
295
+ return null;
296
+ }
297
+ const spanPos = resolvedNew.indexOf(spanVal, pos);
298
+ if (spanPos === -1)
299
+ return null;
300
+ newStatics.push(resolvedNew.slice(pos, spanPos));
301
+ pos = spanPos + spanVal.length;
302
+ }
303
+ // Last static segment: everything remaining in resolvedNew.
304
+ newStatics.push(resolvedNew.slice(pos));
305
+ return newStatics;
306
+ }
307
+ /**
308
+ * Reconstruct a full template literal string (with backticks) from
309
+ * alternating segments and new static values.
310
+ */
311
+ function buildTemplate(segments, newStaticValues) {
312
+ let result = "`";
313
+ let staticIdx = 0;
314
+ for (const seg of segments) {
315
+ if (seg.type === "static") {
316
+ result += escapeForTemplate(newStaticValues[staticIdx++]);
317
+ }
318
+ else {
319
+ result += "${" + seg.text + "}";
320
+ }
321
+ }
322
+ result += "`";
323
+ return result;
324
+ }
325
+ /** Escape characters that would break a template literal. */
326
+ function escapeForTemplate(text) {
327
+ return text
328
+ .replace(/\\/g, "\\\\")
329
+ .replace(/`/g, "\\`")
330
+ .replace(/\$\{/g, "\\${");
331
+ }
@@ -0,0 +1,59 @@
1
+ export interface DnaRecord {
2
+ key: string;
3
+ filePath: string;
4
+ testTitle: string;
5
+ sourceFile: string;
6
+ sourceLine: number;
7
+ selector: string;
8
+ tagName: string;
9
+ text: string | null;
10
+ lastHealed: string | null;
11
+ schemaVersion: number;
12
+ isOrphaned: boolean;
13
+ /** 'healed' = AI auto-fixed, pending human verification. 'broken' = AI failed. undefined = healthy/verified. */
14
+ status?: 'healed' | 'broken';
15
+ }
16
+ export interface DnaIndex {
17
+ generatedAt: string;
18
+ snapshotDir: string;
19
+ records: DnaRecord[];
20
+ stats: {
21
+ total: number;
22
+ orphaned: number;
23
+ healedLast7Days: number;
24
+ healedAllTime: number;
25
+ };
26
+ }
27
+ export interface DnaEditPatch {
28
+ key: string;
29
+ field: 'selector' | 'text' | 'tagName' | 'attributes' | 'anchors';
30
+ oldValue: unknown;
31
+ newValue: unknown;
32
+ applySourceUpdate: boolean;
33
+ locatorIndex: number;
34
+ }
35
+ export interface BulkUpdateJob {
36
+ oldSelector: string;
37
+ newSelector: string;
38
+ dryRun: boolean;
39
+ targets: DnaRecord[];
40
+ results: Array<{
41
+ key: string;
42
+ success: boolean;
43
+ reason: string;
44
+ }>;
45
+ }
46
+ export interface HealthReport {
47
+ generatedAt: string;
48
+ snapshotDir: string;
49
+ total: number;
50
+ orphaned: DnaRecord[];
51
+ unverifiedHeals: DnaRecord[];
52
+ recentlyHealed: DnaRecord[];
53
+ neverHealed: DnaRecord[];
54
+ topHealedFiles: Array<{
55
+ sourceFile: string;
56
+ count: number;
57
+ }>;
58
+ }
59
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/services/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,gHAAgH;IAChH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC9B;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,KAAK,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B,cAAc,EAAE,SAAS,EAAE,CAAC;IAC5B,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,cAAc,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export declare class SnapshotManager {
2
+ static save(filePath: string, data: any): Promise<void>;
3
+ static load(filePath: string): Promise<any>;
4
+ }
5
+ //# sourceMappingURL=SnapshotManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SnapshotManager.d.ts","sourceRoot":"","sources":["../../src/storage/SnapshotManager.ts"],"names":[],"mappings":"AAIA,qBAAa,eAAe;WACb,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;WAUhC,IAAI,CAAC,QAAQ,EAAE,MAAM;CAQnC"}
@@ -0,0 +1,31 @@
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.SnapshotManager = void 0;
7
+ // src/storage/SnapshotManager.ts
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ class SnapshotManager {
11
+ static async save(filePath, data) {
12
+ try {
13
+ const dir = path_1.default.dirname(filePath);
14
+ await promises_1.default.mkdir(dir, { recursive: true });
15
+ await promises_1.default.writeFile(filePath, JSON.stringify(data, null, 2));
16
+ }
17
+ catch (error) {
18
+ console.error(`[Fixwright] Failed to save snapshot: ${error}`);
19
+ }
20
+ }
21
+ static async load(filePath) {
22
+ try {
23
+ const content = await promises_1.default.readFile(filePath, "utf-8");
24
+ return JSON.parse(content);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ }
31
+ exports.SnapshotManager = SnapshotManager;
@@ -0,0 +1,95 @@
1
+ export interface AncestorNode {
2
+ tag: string;
3
+ id: string | null;
4
+ classes: string[];
5
+ /** "{tag}#{id}[{classes_sorted_asc}]" — stable fingerprint for drift comparison */
6
+ fingerprint: string;
7
+ }
8
+ export type GhostCondition = "G1_DISPLAY_NONE" | "G2_VISIBILITY_HIDDEN" | "G3_OPACITY_ZERO" | "G4_POINTER_EVENTS_NONE" | "G5_ZERO_AREA" | "G6_CONTENT_VISIBILITY_HIDDEN";
9
+ export interface VisibilityInfo {
10
+ isGhost: boolean;
11
+ ghostReason: GhostCondition | null;
12
+ isOccluded: boolean;
13
+ /** Computed opacity at capture time (0–1). Ghost threshold: < 0.05. */
14
+ opacity: number;
15
+ }
16
+ export interface AnchorInfo {
17
+ /** Resolved via: aria-labelledby → aria-label → label[for] → implicit label → preceding sibling */
18
+ closestLabel: string | null;
19
+ /** Longest unique sibling cell text when element lives in a table row; null otherwise */
20
+ rowAnchor: string | null;
21
+ /** Up to 5 direct-sibling texts (< 100 chars each). Stored for inspection; not scored in v2. */
22
+ neighborTexts: string[];
23
+ }
24
+ /**
25
+ * Disk format for element snapshots.
26
+ *
27
+ * v1 snapshots (no schemaVersion field) carry only the original fields.
28
+ * v2 snapshots (schemaVersion: 2) add ancestry, visibility, anchors, and
29
+ * isInShadowDom. v1 snapshots are migrated to v2 on load by SnapshotService.
30
+ */
31
+ export interface ElementSnapshot {
32
+ tagName: string;
33
+ id: string | null;
34
+ text: string | null;
35
+ classes: string[];
36
+ attributes: Record<string, string>;
37
+ role: string | null;
38
+ parentContext: string;
39
+ timestamp: string;
40
+ rect?: {
41
+ x: number;
42
+ y: number;
43
+ width: number;
44
+ height: number;
45
+ };
46
+ schemaVersion?: 1 | 2;
47
+ ancestry?: AncestorNode[];
48
+ visibility?: VisibilityInfo;
49
+ anchors?: AnchorInfo;
50
+ isInShadowDom?: boolean;
51
+ }
52
+ /** Narrowed type asserting that all v2 fields are present. */
53
+ export type ElementSnapshotV2 = ElementSnapshot & {
54
+ schemaVersion: 2;
55
+ ancestry: AncestorNode[];
56
+ visibility: VisibilityInfo;
57
+ anchors: AnchorInfo;
58
+ isInShadowDom: boolean;
59
+ };
60
+ export declare function isSnapshotV2(s: ElementSnapshot): s is ElementSnapshotV2;
61
+ export interface FixRequest {
62
+ targetIntent: string;
63
+ failedSelector: string;
64
+ previousState: object;
65
+ currentDom: string;
66
+ }
67
+ export interface FixResponse {
68
+ status: "FIXED" | "NOT_FOUND";
69
+ new_selector: string | null;
70
+ confidence: number;
71
+ explanation: string;
72
+ }
73
+ export interface ElementDNA {
74
+ tagName: string;
75
+ text: string;
76
+ attributes: Record<string, string>;
77
+ styles: {
78
+ backgroundColor: string;
79
+ color: string;
80
+ display: string;
81
+ visibility: string;
82
+ };
83
+ rect: {
84
+ x: number;
85
+ y: number;
86
+ width: number;
87
+ height: number;
88
+ };
89
+ hierarchy: {
90
+ parentTag: string;
91
+ parentClass: string;
92
+ neighborTexts: string[];
93
+ };
94
+ }
95
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ // ── DNA v2 Sub-types ─────────────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.isSnapshotV2 = isSnapshotV2;
5
+ function isSnapshotV2(s) {
6
+ return s.schemaVersion === 2 && Array.isArray(s.ancestry);
7
+ }
@@ -0,0 +1,33 @@
1
+ import { Page } from "@playwright/test";
2
+ import { ElementDNA } from "../types";
3
+ export declare class DOMUtils {
4
+ /**
5
+ * Sanitizes a Playwright selector (which may contain `>>`) into a CSS-safe
6
+ * selector by extracting only the last segment. Returns the last meaningful
7
+ * CSS part so querySelector can be used inside the correct context.
8
+ */
9
+ static sanitizeSelector(selector: string): string;
10
+ /**
11
+ * Given a Playwright `>>` selector, returns each segment in order.
12
+ * e.g. "#my-iframe >> #shadow-host >> #btn"
13
+ * → ["#my-iframe", "#shadow-host", "#btn"]
14
+ */
15
+ static parseSegments(selector: string): string[];
16
+ static getNeighborhoodDom(page: Page, fullSelector: string): Promise<{
17
+ dom: string;
18
+ successfulPath: string[];
19
+ }>;
20
+ /**
21
+ * When a selector has multiple ">>" segments, the first segments are
22
+ * typically iframe selectors. This helper drills into the correct frame
23
+ * before running the DOM extraction.
24
+ */
25
+ private static _getNeighborhoodFromFrame;
26
+ /**
27
+ * Runs the neighborhood-DOM extraction script inside the given execution
28
+ * context (either a Page or a Frame).
29
+ */
30
+ private static _evaluateNeighborhood;
31
+ static getElementDNA: (page: Page, selector: string) => Promise<ElementDNA>;
32
+ }
33
+ //# sourceMappingURL=DOMUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DOMUtils.d.ts","sourceRoot":"","sources":["../../src/utils/DOMUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,qBAAa,QAAQ;IACnB;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAKjD;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;WAM5B,kBAAkB,CACpC,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAerD;;;;OAIG;mBACkB,yBAAyB;IAoD9C;;;OAGG;mBACkB,qBAAqB;IAmF1C,MAAM,CAAC,aAAa,GAClB,MAAM,IAAI,EACV,UAAU,MAAM,KACf,OAAO,CAAC,UAAU,CAAC,CAqCpB;CACH"}