sela-core 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -17
- package/dist/cli/ErrorHandler.d.ts +1 -1
- package/dist/cli/ErrorHandler.js +14 -14
- package/dist/cli/commands/bulk.d.ts +1 -1
- package/dist/cli/commands/bulk.d.ts.map +1 -1
- package/dist/cli/commands/bulk.js +51 -33
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +26 -18
- package/dist/cli/commands/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- package/dist/cli/commands/showReport.d.ts +1 -1
- package/dist/cli/commands/showReport.d.ts.map +1 -1
- package/dist/cli/commands/showReport.js +12 -10
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +8 -8
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +38 -30
- package/dist/cli/index.js +12 -9
- package/dist/cli/ui/DnaTable.d.ts +1 -1
- package/dist/cli/ui/DnaTable.d.ts.map +1 -1
- package/dist/cli/ui/DnaTable.js +23 -16
- package/dist/cli/ui/RefactorWizard.d.ts +3 -3
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
- package/dist/cli/ui/RefactorWizard.js +108 -82
- package/dist/config/ConfigLoader.d.ts +2 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +15 -15
- package/dist/config/DryRunGuard.d.ts +1 -1
- package/dist/config/DryRunGuard.js +7 -7
- package/dist/config/SelaConfig.d.ts +13 -17
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +32 -26
- package/dist/engine/HealingRegistry.d.ts +1 -1
- package/dist/engine/HealingRegistry.js +3 -3
- package/dist/engine/SelaEngine.d.ts +1 -1
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +117 -51
- package/dist/errors/SelaError.d.ts +8 -1
- package/dist/errors/SelaError.d.ts.map +1 -1
- package/dist/errors/SelaError.js +14 -7
- package/dist/fixtures/expectProxy.d.ts +2 -2
- package/dist/fixtures/expectProxy.js +16 -16
- package/dist/fixtures/index.d.ts +18 -2
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +47 -27
- package/dist/fixtures/moduleExpect.js +7 -7
- package/dist/fixtures/proxyTag.d.ts +1 -1
- package/dist/fixtures/proxyTag.js +4 -4
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/reporter/SelaReporter.d.ts +45 -12
- package/dist/reporter/SelaReporter.d.ts.map +1 -1
- package/dist/reporter/SelaReporter.js +139 -39
- package/dist/services/ASTSourceUpdater.d.ts +48 -3
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +283 -105
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- package/dist/services/ArgumentTypeAnalyzer.js +7 -7
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
- package/dist/services/BlastRadiusAnalyzer.js +5 -2
- package/dist/services/ChainValidator.js +22 -22
- package/dist/services/CrossFileHealer.js +7 -7
- package/dist/services/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- package/dist/services/DefinitionTracer.d.ts +1 -1
- package/dist/services/DefinitionTracer.d.ts.map +1 -1
- package/dist/services/DefinitionTracer.js +28 -13
- package/dist/services/DnaEditorService.d.ts +2 -2
- package/dist/services/DnaEditorService.d.ts.map +1 -1
- package/dist/services/DnaEditorService.js +81 -62
- package/dist/services/DnaIndexService.d.ts +1 -1
- package/dist/services/DnaIndexService.d.ts.map +1 -1
- package/dist/services/DnaIndexService.js +14 -14
- package/dist/services/HealReportService.d.ts +31 -55
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +864 -1240
- package/dist/services/HealingAdvisory.d.ts +9 -1
- package/dist/services/HealingAdvisory.d.ts.map +1 -1
- package/dist/services/HealingAdvisory.js +9 -1
- package/dist/services/HealingCacheService.d.ts +8 -8
- package/dist/services/HealingCacheService.d.ts.map +1 -1
- package/dist/services/HealingCacheService.js +24 -24
- package/dist/services/HealthReportService.d.ts +1 -1
- package/dist/services/HealthReportService.d.ts.map +1 -1
- package/dist/services/HealthReportService.js +25 -25
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +12 -2
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +32 -21
- package/dist/services/InteractiveReview.d.ts +8 -17
- package/dist/services/InteractiveReview.d.ts.map +1 -1
- package/dist/services/InteractiveReview.js +34 -124
- package/dist/services/LLMService.d.ts +1 -1
- package/dist/services/LLMService.js +16 -16
- package/dist/services/MutationApplier.d.ts +55 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +322 -0
- package/dist/services/PRAutomationService.d.ts +2 -2
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +43 -39
- package/dist/services/PendingPromptLedger.d.ts +9 -2
- package/dist/services/PendingPromptLedger.d.ts.map +1 -1
- package/dist/services/PendingPromptLedger.js +31 -6
- package/dist/services/ReportGenerator.d.ts +116 -30
- package/dist/services/ReportGenerator.d.ts.map +1 -1
- package/dist/services/ReportGenerator.js +149 -62
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +4 -7
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +29 -106
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.js +6 -6
- package/dist/services/SourceLinkService.d.ts +2 -2
- package/dist/services/SourceLinkService.d.ts.map +1 -1
- package/dist/services/SourceLinkService.js +31 -22
- package/dist/services/SourceUpdater.d.ts +1 -1
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +44 -26
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- package/dist/services/WorkspaceSnapshotService.d.ts +6 -6
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -1
- package/dist/services/WorkspaceSnapshotService.js +10 -12
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/DOMUtils.d.ts +18 -2
- package/dist/utils/DOMUtils.d.ts.map +1 -1
- package/dist/utils/DOMUtils.js +335 -49
- package/dist/utils/IsolatedDiff.d.ts +2 -2
- package/dist/utils/IsolatedDiff.d.ts.map +1 -1
- package/dist/utils/IsolatedDiff.js +13 -10
- package/package.json +2 -2
|
@@ -17,14 +17,14 @@ function bakPath(dir, key) {
|
|
|
17
17
|
}
|
|
18
18
|
// Derive a best-effort selector from engine snapshot fields when none is stored.
|
|
19
19
|
function deriveSelector(snap) {
|
|
20
|
-
const id = snap[
|
|
21
|
-
const classes = snap[
|
|
22
|
-
const tagName = snap[
|
|
23
|
-
if (typeof id ===
|
|
20
|
+
const id = snap["id"];
|
|
21
|
+
const classes = snap["classes"];
|
|
22
|
+
const tagName = snap["tagName"];
|
|
23
|
+
if (typeof id === "string" && id)
|
|
24
24
|
return `#${id}`;
|
|
25
25
|
if (Array.isArray(classes) && classes.length > 0)
|
|
26
|
-
return `.${classes.join(
|
|
27
|
-
return typeof tagName ===
|
|
26
|
+
return `.${classes.join(".")}`;
|
|
27
|
+
return typeof tagName === "string" ? tagName : "unknown";
|
|
28
28
|
}
|
|
29
29
|
function dnaError(message, code) {
|
|
30
30
|
const err = new Error(message);
|
|
@@ -40,15 +40,15 @@ class DnaEditorService {
|
|
|
40
40
|
const target = jsonPath(this.snapshotDir, key);
|
|
41
41
|
let raw;
|
|
42
42
|
try {
|
|
43
|
-
raw = await promises_1.default.readFile(target,
|
|
43
|
+
raw = await promises_1.default.readFile(target, "utf-8");
|
|
44
44
|
}
|
|
45
45
|
catch {
|
|
46
|
-
throw dnaError(`DNA not found: ${key}`,
|
|
46
|
+
throw dnaError(`DNA not found: ${key}`, "DNA_NOT_FOUND");
|
|
47
47
|
}
|
|
48
48
|
const parsed = JSON.parse(raw);
|
|
49
49
|
// Promote to extended format if selector not stored
|
|
50
|
-
if (!parsed[
|
|
51
|
-
parsed[
|
|
50
|
+
if (!parsed["selector"]) {
|
|
51
|
+
parsed["selector"] = deriveSelector(parsed);
|
|
52
52
|
}
|
|
53
53
|
return parsed;
|
|
54
54
|
}
|
|
@@ -59,22 +59,25 @@ class DnaEditorService {
|
|
|
59
59
|
// 1. Read existing snapshot
|
|
60
60
|
const original = await this.read(key);
|
|
61
61
|
// 2. Shallow-merge patch (arrays and objects replaced, not deep-merged)
|
|
62
|
-
const merged = {
|
|
62
|
+
const merged = {
|
|
63
|
+
...original,
|
|
64
|
+
...patch,
|
|
65
|
+
};
|
|
63
66
|
// Any CLI write = human has reviewed the DNA → clear auto-heal status flag.
|
|
64
|
-
delete merged[
|
|
65
|
-
// 3. Derive selector only when absent
|
|
66
|
-
if (merged[
|
|
67
|
-
merged[
|
|
67
|
+
delete merged["status"];
|
|
68
|
+
// 3. Derive selector only when absent - never override an explicit (possibly invalid) value
|
|
69
|
+
if (merged["selector"] == null) {
|
|
70
|
+
merged["selector"] = deriveSelector(merged);
|
|
68
71
|
}
|
|
69
72
|
// 4. Validate before touching disk
|
|
70
73
|
const validation = this.validate(merged);
|
|
71
74
|
if (!validation.valid) {
|
|
72
|
-
throw dnaError(`Validation failed for ${key}: ${validation.errors.join(
|
|
75
|
+
throw dnaError(`Validation failed for ${key}: ${validation.errors.join("; ")}`, "VALIDATION_FAILED");
|
|
73
76
|
}
|
|
74
77
|
// 5. Backup current file (overwrite previous bak)
|
|
75
|
-
await promises_1.default.writeFile(bak, JSON.stringify(original, null, 2),
|
|
78
|
+
await promises_1.default.writeFile(bak, JSON.stringify(original, null, 2), "utf-8");
|
|
76
79
|
// 6. Write to tmp
|
|
77
|
-
await promises_1.default.writeFile(tmp, JSON.stringify(merged, null, 2),
|
|
80
|
+
await promises_1.default.writeFile(tmp, JSON.stringify(merged, null, 2), "utf-8");
|
|
78
81
|
// 7. Atomic rename tmp → json
|
|
79
82
|
await promises_1.default.rename(tmp, target);
|
|
80
83
|
return bak;
|
|
@@ -86,111 +89,127 @@ class DnaEditorService {
|
|
|
86
89
|
await promises_1.default.access(bak);
|
|
87
90
|
}
|
|
88
91
|
catch {
|
|
89
|
-
throw dnaError(`No backup found for: ${key}`,
|
|
92
|
+
throw dnaError(`No backup found for: ${key}`, "DNA_NOT_FOUND");
|
|
90
93
|
}
|
|
91
94
|
await promises_1.default.rename(bak, target);
|
|
92
95
|
}
|
|
93
96
|
validate(candidate) {
|
|
94
97
|
const errors = [];
|
|
95
|
-
if (typeof candidate !==
|
|
96
|
-
|
|
98
|
+
if (typeof candidate !== "object" ||
|
|
99
|
+
candidate === null ||
|
|
100
|
+
Array.isArray(candidate)) {
|
|
101
|
+
return { valid: false, errors: ["candidate must be a plain object"] };
|
|
97
102
|
}
|
|
98
103
|
const snap = candidate;
|
|
99
104
|
// ── Required top-level fields ─────────────────────────────────────────────
|
|
100
|
-
for (const field of [
|
|
105
|
+
for (const field of [
|
|
106
|
+
"tagName",
|
|
107
|
+
"selector",
|
|
108
|
+
"schemaVersion",
|
|
109
|
+
"timestamp",
|
|
110
|
+
]) {
|
|
101
111
|
if (!(field in snap) || snap[field] === undefined) {
|
|
102
112
|
errors.push(`Missing required field: ${field}`);
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
|
-
if (snap[
|
|
106
|
-
errors.push(`schemaVersion must be 2, got ${String(snap[
|
|
115
|
+
if (snap["schemaVersion"] !== 2) {
|
|
116
|
+
errors.push(`schemaVersion must be 2, got ${String(snap["schemaVersion"])}`);
|
|
107
117
|
}
|
|
108
118
|
// ── selector ─────────────────────────────────────────────────────────────
|
|
109
|
-
const sel = snap[
|
|
110
|
-
if (typeof sel ===
|
|
111
|
-
if (sel.trim() ===
|
|
112
|
-
errors.push(
|
|
113
|
-
if (sel ===
|
|
119
|
+
const sel = snap["selector"];
|
|
120
|
+
if (typeof sel === "string") {
|
|
121
|
+
if (sel.trim() === "")
|
|
122
|
+
errors.push("selector must not be empty");
|
|
123
|
+
if (sel === "undefined")
|
|
114
124
|
errors.push('selector must not be the string "undefined"');
|
|
115
125
|
if (/[\r\n]/.test(sel))
|
|
116
|
-
errors.push(
|
|
126
|
+
errors.push("selector must not contain raw newlines");
|
|
117
127
|
}
|
|
118
|
-
else if (
|
|
119
|
-
errors.push(
|
|
128
|
+
else if ("selector" in snap) {
|
|
129
|
+
errors.push("selector must be a string");
|
|
120
130
|
}
|
|
121
131
|
// ── tagName ───────────────────────────────────────────────────────────────
|
|
122
|
-
const tag = snap[
|
|
123
|
-
if (typeof tag ===
|
|
132
|
+
const tag = snap["tagName"];
|
|
133
|
+
if (typeof tag === "string") {
|
|
124
134
|
if (!/^[a-z][a-z0-9-]*$/.test(tag)) {
|
|
125
|
-
errors.push(
|
|
135
|
+
errors.push("tagName must match /^[a-z][a-z0-9-]*$/");
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
|
-
else if (
|
|
129
|
-
errors.push(
|
|
138
|
+
else if ("tagName" in snap) {
|
|
139
|
+
errors.push("tagName must be a string");
|
|
130
140
|
}
|
|
131
141
|
// ── text ─────────────────────────────────────────────────────────────────
|
|
132
|
-
const text = snap[
|
|
142
|
+
const text = snap["text"];
|
|
133
143
|
if (text !== null && text !== undefined) {
|
|
134
|
-
if (typeof text !==
|
|
135
|
-
errors.push(
|
|
144
|
+
if (typeof text !== "string") {
|
|
145
|
+
errors.push("text must be a string or null");
|
|
136
146
|
}
|
|
137
147
|
else if (text.length >= 2000) {
|
|
138
|
-
errors.push(
|
|
148
|
+
errors.push("text must be fewer than 2000 characters");
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
151
|
// ── attributes ───────────────────────────────────────────────────────────
|
|
142
|
-
const attrs = snap[
|
|
152
|
+
const attrs = snap["attributes"];
|
|
143
153
|
if (attrs !== undefined) {
|
|
144
|
-
if (typeof attrs !==
|
|
145
|
-
errors.push(
|
|
154
|
+
if (typeof attrs !== "object" || attrs === null || Array.isArray(attrs)) {
|
|
155
|
+
errors.push("attributes must be a plain object");
|
|
146
156
|
}
|
|
147
157
|
else {
|
|
148
158
|
for (const [k, v] of Object.entries(attrs)) {
|
|
149
159
|
if (!k)
|
|
150
|
-
errors.push(
|
|
151
|
-
if (typeof v !==
|
|
160
|
+
errors.push("attributes keys must not be empty");
|
|
161
|
+
if (typeof v !== "string")
|
|
152
162
|
errors.push(`attributes["${k}"] must be a string`);
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
165
|
}
|
|
156
166
|
// ── visibility (all 4 keys required) ─────────────────────────────────────
|
|
157
|
-
const vis = snap[
|
|
158
|
-
if (typeof vis ===
|
|
167
|
+
const vis = snap["visibility"];
|
|
168
|
+
if (typeof vis === "object" && vis !== null && !Array.isArray(vis)) {
|
|
159
169
|
const visObj = vis;
|
|
160
|
-
for (const key of [
|
|
170
|
+
for (const key of [
|
|
171
|
+
"isGhost",
|
|
172
|
+
"ghostReason",
|
|
173
|
+
"isOccluded",
|
|
174
|
+
"opacity",
|
|
175
|
+
]) {
|
|
161
176
|
if (!(key in visObj))
|
|
162
177
|
errors.push(`visibility.${key} is required`);
|
|
163
178
|
}
|
|
164
179
|
}
|
|
165
180
|
else {
|
|
166
|
-
errors.push(
|
|
181
|
+
errors.push("visibility block is required and must be an object");
|
|
167
182
|
}
|
|
168
183
|
// ── anchors (all 3 keys required) ────────────────────────────────────────
|
|
169
|
-
const anch = snap[
|
|
170
|
-
if (typeof anch ===
|
|
184
|
+
const anch = snap["anchors"];
|
|
185
|
+
if (typeof anch === "object" && anch !== null && !Array.isArray(anch)) {
|
|
171
186
|
const anchObj = anch;
|
|
172
|
-
for (const key of [
|
|
187
|
+
for (const key of [
|
|
188
|
+
"closestLabel",
|
|
189
|
+
"rowAnchor",
|
|
190
|
+
"neighborTexts",
|
|
191
|
+
]) {
|
|
173
192
|
if (!(key in anchObj))
|
|
174
193
|
errors.push(`anchors.${key} is required`);
|
|
175
194
|
}
|
|
176
|
-
const cl = anchObj[
|
|
177
|
-
if (cl !== null && typeof cl !==
|
|
178
|
-
errors.push(
|
|
195
|
+
const cl = anchObj["closestLabel"];
|
|
196
|
+
if (cl !== null && typeof cl !== "string") {
|
|
197
|
+
errors.push("anchors.closestLabel must be a string or null");
|
|
179
198
|
}
|
|
180
|
-
const nt = anchObj[
|
|
199
|
+
const nt = anchObj["neighborTexts"];
|
|
181
200
|
if (!Array.isArray(nt)) {
|
|
182
|
-
errors.push(
|
|
201
|
+
errors.push("anchors.neighborTexts must be an array");
|
|
183
202
|
}
|
|
184
203
|
else {
|
|
185
204
|
if (nt.length > 10)
|
|
186
|
-
errors.push(
|
|
187
|
-
if (!nt.every((x) => typeof x ===
|
|
188
|
-
errors.push(
|
|
205
|
+
errors.push("anchors.neighborTexts must have at most 10 elements");
|
|
206
|
+
if (!nt.every((x) => typeof x === "string")) {
|
|
207
|
+
errors.push("anchors.neighborTexts elements must all be strings");
|
|
189
208
|
}
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
211
|
else {
|
|
193
|
-
errors.push(
|
|
212
|
+
errors.push("anchors block is required and must be an object");
|
|
194
213
|
}
|
|
195
214
|
return { valid: errors.length === 0, errors };
|
|
196
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DnaIndexService.d.ts","sourceRoot":"","sources":["../../src/services/DnaIndexService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAatD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAC7C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IACzC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAChD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACxC,QAAQ,IAAI,QAAQ,CAAC;IACrB,OAAO,IAAI,IAAI,CAAC;CACjB;AAkED,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,WAAW,CAAyB;IAEtC,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"DnaIndexService.d.ts","sourceRoot":"","sources":["../../src/services/DnaIndexService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAatD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAC7C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IACzC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAChD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACxC,QAAQ,IAAI,QAAQ,CAAC;IACrB,OAAO,IAAI,IAAI,CAAC;CACjB;AAkED,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,WAAW,CAAyB;IAEtC,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAoCxD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE;IAK5C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE;IAOxC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE;IAO/C,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE;IAK7C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAIvC,QAAQ,IAAI,QAAQ;IAIpB,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,YAAY;CAQrB"}
|
|
@@ -12,7 +12,7 @@ function toRegExp(pattern) {
|
|
|
12
12
|
const m = pattern.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
13
13
|
if (m)
|
|
14
14
|
return new RegExp(m[1], m[2]);
|
|
15
|
-
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g,
|
|
15
|
+
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
16
16
|
}
|
|
17
17
|
function deriveSelector(snap) {
|
|
18
18
|
if (snap.selector)
|
|
@@ -20,19 +20,19 @@ function deriveSelector(snap) {
|
|
|
20
20
|
if (snap.id)
|
|
21
21
|
return `#${snap.id}`;
|
|
22
22
|
if (snap.classes.length > 0)
|
|
23
|
-
return `.${snap.classes.join(
|
|
23
|
+
return `.${snap.classes.join(".")}`;
|
|
24
24
|
return snap.tagName;
|
|
25
25
|
}
|
|
26
26
|
function isWithin7Days(iso) {
|
|
27
27
|
return new Date(iso).getTime() >= Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
28
28
|
}
|
|
29
29
|
function toRecord(filePath, raw) {
|
|
30
|
-
const key = path_1.default.basename(filePath,
|
|
31
|
-
const sourceFile = raw.sourceFile ??
|
|
30
|
+
const key = path_1.default.basename(filePath, ".json");
|
|
31
|
+
const sourceFile = raw.sourceFile ?? "";
|
|
32
32
|
return {
|
|
33
33
|
key,
|
|
34
34
|
filePath,
|
|
35
|
-
testTitle: raw.testTitle ??
|
|
35
|
+
testTitle: raw.testTitle ?? "",
|
|
36
36
|
sourceFile,
|
|
37
37
|
sourceLine: raw.sourceLine ?? 0,
|
|
38
38
|
selector: deriveSelector(raw),
|
|
@@ -40,8 +40,8 @@ function toRecord(filePath, raw) {
|
|
|
40
40
|
text: raw.text ?? null,
|
|
41
41
|
lastHealed: raw.lastHealed ?? null,
|
|
42
42
|
schemaVersion: raw.schemaVersion ?? 1,
|
|
43
|
-
// Only flag orphaned when sourceFile is known
|
|
44
|
-
isOrphaned: sourceFile !==
|
|
43
|
+
// Only flag orphaned when sourceFile is known - engine snapshots have no sourceFile
|
|
44
|
+
isOrphaned: sourceFile !== "" && !(0, fs_1.existsSync)(sourceFile),
|
|
45
45
|
status: raw.status,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
@@ -60,9 +60,9 @@ async function collectJsonFiles(dir) {
|
|
|
60
60
|
results.push(...(await collectJsonFiles(full)));
|
|
61
61
|
}
|
|
62
62
|
else if (entry.isFile() &&
|
|
63
|
-
entry.name.endsWith(
|
|
64
|
-
!entry.name.endsWith(
|
|
65
|
-
!entry.name.endsWith(
|
|
63
|
+
entry.name.endsWith(".json") &&
|
|
64
|
+
!entry.name.endsWith(".bak") &&
|
|
65
|
+
!entry.name.endsWith(".tmp")) {
|
|
66
66
|
results.push(full);
|
|
67
67
|
}
|
|
68
68
|
}));
|
|
@@ -73,11 +73,11 @@ class DnaIndexService {
|
|
|
73
73
|
async buildIndex(snapshotDir) {
|
|
74
74
|
const files = await collectJsonFiles(snapshotDir);
|
|
75
75
|
const settled = await Promise.allSettled(files.map(async (filePath) => {
|
|
76
|
-
const content = await promises_1.default.readFile(filePath,
|
|
76
|
+
const content = await promises_1.default.readFile(filePath, "utf-8");
|
|
77
77
|
return toRecord(filePath, JSON.parse(content));
|
|
78
78
|
}));
|
|
79
79
|
const records = settled
|
|
80
|
-
.filter((r) => r.status ===
|
|
80
|
+
.filter((r) => r.status === "fulfilled")
|
|
81
81
|
.map((r) => r.value);
|
|
82
82
|
const healedAllTime = records.filter((r) => r.lastHealed !== null).length;
|
|
83
83
|
const healedLast7Days = records.filter((r) => r.lastHealed !== null && isWithin7Days(r.lastHealed)).length;
|
|
@@ -121,8 +121,8 @@ class DnaIndexService {
|
|
|
121
121
|
}
|
|
122
122
|
requireIndex() {
|
|
123
123
|
if (!this.cachedIndex) {
|
|
124
|
-
const err = new Error(
|
|
125
|
-
err.code =
|
|
124
|
+
const err = new Error("Index not built - call buildIndex() first");
|
|
125
|
+
err.code = "SNAPSHOT_DIR_MISSING";
|
|
126
126
|
throw err;
|
|
127
127
|
}
|
|
128
128
|
return this.cachedIndex;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AuditVerdict, AuditVerdictValue } from "./IntentAuditor";
|
|
2
2
|
import type { ElementSnapshotV2 } from "../types";
|
|
3
|
+
import { type ReportData } from "./ReportGenerator";
|
|
3
4
|
export type HealEventKind = "HEALED" | "FAILED" | "PROTECTED";
|
|
4
5
|
export interface DnaSummary {
|
|
5
6
|
tagName: string;
|
|
@@ -17,10 +18,6 @@ export interface DnaSummary {
|
|
|
17
18
|
}
|
|
18
19
|
export interface AuditorBlock {
|
|
19
20
|
verdict: AuditVerdictValue;
|
|
20
|
-
/**
|
|
21
|
-
* Adjusted confidence 0-100. `undefined` when the auditor produced no score
|
|
22
|
-
* (e.g. SafetyGuard bypassed it) — reports MUST render this as "n/a".
|
|
23
|
-
*/
|
|
24
21
|
confidence?: number;
|
|
25
22
|
reason: string;
|
|
26
23
|
inversionType: AuditVerdict["inversionType"];
|
|
@@ -41,30 +38,13 @@ interface HealEventBase {
|
|
|
41
38
|
}
|
|
42
39
|
export interface HealedEvent extends HealEventBase {
|
|
43
40
|
kind: "HEALED";
|
|
44
|
-
/**
|
|
45
|
-
* Marks events produced during a `SELA_DRY_RUN=1` session — the in-memory
|
|
46
|
-
* heal completed (LLM call, SafetyGuard verdict, candidate diff captured)
|
|
47
|
-
* but no source file was modified and no commit was produced. Reports
|
|
48
|
-
* render these with an explicit "Dry Run · No Write" chip.
|
|
49
|
-
*/
|
|
50
41
|
dryRun?: boolean;
|
|
51
42
|
oldSelector: string;
|
|
52
43
|
newSelector: string;
|
|
53
|
-
/** First removed line from the native git diff (backward-compat fallback). */
|
|
54
44
|
oldCodeLine: string;
|
|
55
|
-
/** First added line from the native git diff (backward-compat fallback). */
|
|
56
45
|
newCodeLine: string;
|
|
57
46
|
newLineNumber: number;
|
|
58
|
-
/**
|
|
59
|
-
* Raw `git diff --unified=1` output captured immediately after the AST
|
|
60
|
-
* mutation hit disk and BEFORE any `git add`. Includes headers (---/+++)
|
|
61
|
-
* and one or more @@ hunks. Renderers strip headers; PR body emits the
|
|
62
|
-
* hunks verbatim inside a ```diff fence so cross-file healing, multi-line
|
|
63
|
-
* edits, and template-literal changes are all displayed natively without
|
|
64
|
-
* any in-process line-index math.
|
|
65
|
-
*/
|
|
66
47
|
gitUnifiedDiff?: string;
|
|
67
|
-
/** AI-reported confidence 0-100. `undefined` when the model omitted it. */
|
|
68
48
|
aiConfidence?: number;
|
|
69
49
|
aiExplanation: string;
|
|
70
50
|
aiAlternatives: string[];
|
|
@@ -90,15 +70,6 @@ export interface FailedEvent extends HealEventBase {
|
|
|
90
70
|
aiConfidence?: number;
|
|
91
71
|
aiExplanation?: string;
|
|
92
72
|
dnaBefore: DnaSummary | null;
|
|
93
|
-
/**
|
|
94
|
-
* Base64-encoded PNG screenshot captured by `SelaReporter` from the
|
|
95
|
-
* Playwright `TestResult.attachments` slot at the moment the test
|
|
96
|
-
* failed. NEVER prefixed with `data:image/png;base64,` — the HTML
|
|
97
|
-
* renderer prepends the data URL header at render time so the JSON
|
|
98
|
-
* payload stays compact and the bytes survive Playwright's temp
|
|
99
|
-
* folder cleanup. `undefined` when no screenshot was attached
|
|
100
|
-
* (worker crash, screenshot disabled, off-attachment failure).
|
|
101
|
-
*/
|
|
102
73
|
failureScreenshotPng?: string;
|
|
103
74
|
}
|
|
104
75
|
export interface ProtectedEvent extends HealEventBase {
|
|
@@ -108,7 +79,6 @@ export interface ProtectedEvent extends HealEventBase {
|
|
|
108
79
|
reason: string;
|
|
109
80
|
safetyLevel: string;
|
|
110
81
|
auditor: AuditorBlock | null;
|
|
111
|
-
/** AI-reported confidence 0-100. `undefined` when the model omitted it. */
|
|
112
82
|
aiConfidence?: number;
|
|
113
83
|
aiExplanation: string;
|
|
114
84
|
dnaBefore: DnaSummary | null;
|
|
@@ -127,42 +97,48 @@ export declare class HealReportService {
|
|
|
127
97
|
size(): number;
|
|
128
98
|
clear(): void;
|
|
129
99
|
hasContent(): boolean;
|
|
130
|
-
/** All HEALED events accumulated in this session. */
|
|
131
100
|
getHealedEvents(): HealedEvent[];
|
|
132
|
-
/** All PROTECTED events (SafetyGuard blocks) accumulated in this session. */
|
|
133
101
|
getProtectedEvents(): ProtectedEvent[];
|
|
134
|
-
/** All FAILED events accumulated in this session. */
|
|
135
102
|
getFailedEvents(): FailedEvent[];
|
|
136
103
|
/**
|
|
137
|
-
*
|
|
138
|
-
* onto the FAILED event(s) matching the supplied identity hints.
|
|
139
|
-
*
|
|
140
|
-
* Playwright-captured screenshot from `TestResult.attachments`.
|
|
141
|
-
*
|
|
142
|
-
* Match policy (all supplied hints must agree):
|
|
143
|
-
* • `sourceFile` — suffix-matched both directions to absorb
|
|
144
|
-
* project-relative vs absolute path mismatches between the engine
|
|
145
|
-
* (relative) and Playwright TestInfo.location.file (absolute).
|
|
146
|
-
* • `testTitle` — exact match.
|
|
147
|
-
* • `stableId` — exact match.
|
|
148
|
-
*
|
|
149
|
-
* Returns the number of events updated. Zero means the caller should
|
|
150
|
-
* keep the screenshot pending until the matching FailedEvent is
|
|
151
|
-
* recorded (worker-crash edge case where the heal pipeline did not
|
|
152
|
-
* run before the Reporter received the failure).
|
|
104
|
+
* Attach a Playwright failure screenshot (Base64 PNG, no data: prefix)
|
|
105
|
+
* onto the FAILED event(s) matching the supplied identity hints.
|
|
106
|
+
* Returns the number of events updated.
|
|
153
107
|
*/
|
|
154
108
|
attachFailureScreenshot(match: {
|
|
155
109
|
sourceFile?: string;
|
|
156
110
|
testTitle?: string;
|
|
157
111
|
stableId?: string;
|
|
158
112
|
}, base64Png: string): number;
|
|
113
|
+
private readHistory;
|
|
114
|
+
private writeHistory;
|
|
115
|
+
/**
|
|
116
|
+
* Generate and write `sela-report.html` + `sela-report.json` to targetDir.
|
|
117
|
+
* Also updates `.sela-history.json` with the current run's stats.
|
|
118
|
+
* Returns the absolute path of the HTML report, or null when there is
|
|
119
|
+
* nothing to write.
|
|
120
|
+
*/
|
|
121
|
+
flushToDisk(targetDir?: string, opts?: {
|
|
122
|
+
runId?: string;
|
|
123
|
+
}): string | null;
|
|
159
124
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
125
|
+
* Render and persist a pre-computed {@link ReportData} payload to `targetDir`:
|
|
126
|
+
* • `.sela-history.json` (the rolling window from `data.historical_runs`)
|
|
127
|
+
* • `sela-report.html` (the embedded dashboard)
|
|
128
|
+
* • `sela-report.json` (the raw payload for CI consumers)
|
|
129
|
+
*
|
|
130
|
+
* Extracted from {@link flushToDisk} so the CI merge path
|
|
131
|
+
* (`ReportMergeService`) writes a shard-aggregated report through the exact
|
|
132
|
+
* same rendering / serialisation logic — no duplication.
|
|
133
|
+
*
|
|
134
|
+
* Unlike `flushToDisk`, this writes unconditionally: an empty-event payload
|
|
135
|
+
* still produces a report and a `healed:0` history row. That is the desired
|
|
136
|
+
* CI behaviour — a clean, zero-heal pipeline is a healthy data point that
|
|
137
|
+
* must keep the trend chart's x-axis cadence intact.
|
|
138
|
+
*
|
|
139
|
+
* Returns the absolute path of the written HTML report.
|
|
162
140
|
*/
|
|
163
|
-
|
|
164
|
-
private renderHtml;
|
|
165
|
-
private computeStats;
|
|
141
|
+
writeReportData(targetDir: string, data: ReportData): string;
|
|
166
142
|
}
|
|
167
143
|
export declare const sharedHealReport: HealReportService;
|
|
168
144
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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"}
|