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.
- package/README.md +93 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +140 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +133 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- package/dist/cli/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +123 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +70 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +269 -0
- package/dist/config/ConfigLoader.d.ts +15 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +174 -0
- package/dist/config/SelaConfig.d.ts +67 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +57 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +98 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +228 -0
- package/dist/fixtures/index.d.ts +6 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +688 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +46 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/services/ASTSourceUpdater.d.ts +79 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3177 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +103 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +569 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +134 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +350 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +198 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealingAdvisory.d.ts +22 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +42 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +37 -0
- package/dist/services/IntentAuditor.d.ts +39 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +302 -0
- package/dist/services/LLMService.d.ts +100 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +439 -0
- package/dist/services/SafetyGuard.d.ts +65 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +524 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +349 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +156 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1021 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +331 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +31 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +33 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +179 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- 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 @@
|
|
|
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"}
|