sela-core 1.0.7 → 1.0.8
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/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/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 +9 -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 +12 -16
- 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 +63 -49
- package/dist/errors/SelaError.d.ts +0 -1
- package/dist/errors/SelaError.d.ts.map +1 -1
- package/dist/errors/SelaError.js +6 -7
- package/dist/fixtures/expectProxy.d.ts +1 -1
- package/dist/fixtures/expectProxy.js +11 -11
- package/dist/fixtures/index.js +14 -14
- package/dist/fixtures/proxyTag.d.ts +1 -1
- package/dist/fixtures/proxyTag.js +3 -3
- 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 +3 -3
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +92 -68
- 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/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 +6 -6
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +33 -29
- package/dist/services/HealingAdvisory.js +1 -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.js +2 -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 +31 -124
- package/dist/services/LLMService.d.ts +1 -1
- package/dist/services/LLMService.js +16 -16
- 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 +2 -2
- package/dist/services/PendingPromptLedger.js +6 -6
- package/dist/services/ReportGenerator.d.ts +2 -2
- package/dist/services/ReportGenerator.d.ts.map +1 -1
- package/dist/services/ReportGenerator.js +5 -5
- package/dist/services/SafetyGuard.d.ts +3 -6
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +29 -106
- package/dist/services/SnapshotService.js +5 -5
- 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 +27 -26
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- 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/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
|
@@ -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;
|
|
@@ -19,7 +19,7 @@ export interface AuditorBlock {
|
|
|
19
19
|
verdict: AuditVerdictValue;
|
|
20
20
|
/**
|
|
21
21
|
* Adjusted confidence 0-100. `undefined` when the auditor produced no score
|
|
22
|
-
* (e.g. SafetyGuard bypassed it)
|
|
22
|
+
* (e.g. SafetyGuard bypassed it) - reports MUST render this as "n/a".
|
|
23
23
|
*/
|
|
24
24
|
confidence?: number;
|
|
25
25
|
reason: string;
|
|
@@ -42,7 +42,7 @@ interface HealEventBase {
|
|
|
42
42
|
export interface HealedEvent extends HealEventBase {
|
|
43
43
|
kind: "HEALED";
|
|
44
44
|
/**
|
|
45
|
-
* Marks events produced during a `SELA_DRY_RUN=1` session
|
|
45
|
+
* Marks events produced during a `SELA_DRY_RUN=1` session - the in-memory
|
|
46
46
|
* heal completed (LLM call, SafetyGuard verdict, candidate diff captured)
|
|
47
47
|
* but no source file was modified and no commit was produced. Reports
|
|
48
48
|
* render these with an explicit "Dry Run · No Write" chip.
|
|
@@ -93,7 +93,7 @@ export interface FailedEvent extends HealEventBase {
|
|
|
93
93
|
/**
|
|
94
94
|
* Base64-encoded PNG screenshot captured by `SelaReporter` from the
|
|
95
95
|
* Playwright `TestResult.attachments` slot at the moment the test
|
|
96
|
-
* failed. NEVER prefixed with `data:image/png;base64,`
|
|
96
|
+
* failed. NEVER prefixed with `data:image/png;base64,` - the HTML
|
|
97
97
|
* renderer prepends the data URL header at render time so the JSON
|
|
98
98
|
* payload stays compact and the bytes survive Playwright's temp
|
|
99
99
|
* folder cleanup. `undefined` when no screenshot was attached
|
|
@@ -140,11 +140,11 @@ export declare class HealReportService {
|
|
|
140
140
|
* Playwright-captured screenshot from `TestResult.attachments`.
|
|
141
141
|
*
|
|
142
142
|
* Match policy (all supplied hints must agree):
|
|
143
|
-
* • `sourceFile`
|
|
143
|
+
* • `sourceFile` - suffix-matched both directions to absorb
|
|
144
144
|
* project-relative vs absolute path mismatches between the engine
|
|
145
145
|
* (relative) and Playwright TestInfo.location.file (absolute).
|
|
146
|
-
* • `testTitle`
|
|
147
|
-
* • `stableId`
|
|
146
|
+
* • `testTitle` - exact match.
|
|
147
|
+
* • `stableId` - exact match.
|
|
148
148
|
*
|
|
149
149
|
* Returns the number of events updated. Zero means the caller should
|
|
150
150
|
* keep the screenshot pending until the matching FailedEvent is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/services/HealReportService.ts
|
|
3
3
|
//
|
|
4
|
-
// Sela Insights
|
|
4
|
+
// Sela Insights - Transparent Healing Report.
|
|
5
5
|
// Buffers a HealEvent per heal attempt (HEALED, FAILED, PROTECTED), then renders
|
|
6
6
|
// a single self-contained HTML file (sela-report.html) at commitUpdates() time.
|
|
7
7
|
//
|
|
@@ -47,9 +47,9 @@ const path = __importStar(require("path"));
|
|
|
47
47
|
// Time-saved heuristics (minutes). Tuned for "fair midpoint" engineer estimate.
|
|
48
48
|
const TIME_PER_HEAL = 12;
|
|
49
49
|
const TIME_PER_PROTECT = 18; // catching a masked bug is worth more
|
|
50
|
-
const TIME_PER_FAILED = 4; // partial value
|
|
50
|
+
const TIME_PER_FAILED = 4; // partial value - narrows debug scope
|
|
51
51
|
// ═══════════════════════════════════════════════════════════════════
|
|
52
|
-
// SUMMARISERS
|
|
52
|
+
// SUMMARISERS - ElementSnapshotV2 → DnaSummary
|
|
53
53
|
// ═══════════════════════════════════════════════════════════════════
|
|
54
54
|
function summariseSnapshot(s) {
|
|
55
55
|
if (!s)
|
|
@@ -107,11 +107,11 @@ class HealReportService {
|
|
|
107
107
|
* Playwright-captured screenshot from `TestResult.attachments`.
|
|
108
108
|
*
|
|
109
109
|
* Match policy (all supplied hints must agree):
|
|
110
|
-
* • `sourceFile`
|
|
110
|
+
* • `sourceFile` - suffix-matched both directions to absorb
|
|
111
111
|
* project-relative vs absolute path mismatches between the engine
|
|
112
112
|
* (relative) and Playwright TestInfo.location.file (absolute).
|
|
113
|
-
* • `testTitle`
|
|
114
|
-
* • `stableId`
|
|
113
|
+
* • `testTitle` - exact match.
|
|
114
|
+
* • `stableId` - exact match.
|
|
115
115
|
*
|
|
116
116
|
* Returns the number of events updated. Zero means the caller should
|
|
117
117
|
* keep the screenshot pending until the matching FailedEvent is
|
|
@@ -153,7 +153,11 @@ class HealReportService {
|
|
|
153
153
|
const htmlPath = path.join(targetDir, "sela-report.html");
|
|
154
154
|
const jsonPath = path.join(targetDir, "sela-report.json");
|
|
155
155
|
fs.writeFileSync(htmlPath, html, "utf8");
|
|
156
|
-
fs.writeFileSync(jsonPath, JSON.stringify({
|
|
156
|
+
fs.writeFileSync(jsonPath, JSON.stringify({
|
|
157
|
+
generatedAt: new Date().toISOString(),
|
|
158
|
+
startedAt: this.startedAt,
|
|
159
|
+
events: this.events,
|
|
160
|
+
}, null, 2), "utf8");
|
|
157
161
|
return htmlPath;
|
|
158
162
|
}
|
|
159
163
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -216,14 +220,14 @@ function htmlEscape(s) {
|
|
|
216
220
|
}
|
|
217
221
|
function renderHtmlTemplate(input) {
|
|
218
222
|
// Embed structured data into the page so the front-end script renders it
|
|
219
|
-
//
|
|
223
|
+
// - keeps the template compact and avoids server-side template branching.
|
|
220
224
|
const payload = JSON.stringify(input).replace(/</g, "\\u003c");
|
|
221
225
|
return `<!DOCTYPE html>
|
|
222
226
|
<html lang="en" data-theme="dark">
|
|
223
227
|
<head>
|
|
224
228
|
<meta charset="UTF-8" />
|
|
225
229
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
226
|
-
<title>Sela Insights
|
|
230
|
+
<title>Sela Insights - Healing Report</title>
|
|
227
231
|
<style>
|
|
228
232
|
${REPORT_CSS}
|
|
229
233
|
</style>
|
|
@@ -312,7 +316,7 @@ ${REPORT_JS}
|
|
|
312
316
|
</html>`;
|
|
313
317
|
}
|
|
314
318
|
// ═══════════════════════════════════════════════════════════════════
|
|
315
|
-
// CSS
|
|
319
|
+
// CSS - dark, immersive, animated
|
|
316
320
|
// ═══════════════════════════════════════════════════════════════════
|
|
317
321
|
const REPORT_CSS = `
|
|
318
322
|
:root {
|
|
@@ -730,7 +734,7 @@ a:hover { text-decoration: underline; }
|
|
|
730
734
|
.tag.ghost { color: var(--warn); border-color: rgba(251,191,36,0.3); }
|
|
731
735
|
.tag.occlude { color: var(--bad); border-color: rgba(248,113,113,0.3); }
|
|
732
736
|
|
|
733
|
-
/* Failure context
|
|
737
|
+
/* Failure context - Playwright screenshot rendered inline on FAILED cards */
|
|
734
738
|
.failure-img {
|
|
735
739
|
background: var(--bg-1); border: 1px solid var(--line);
|
|
736
740
|
border-radius: var(--radius-sm); padding: 8px;
|
|
@@ -797,7 +801,7 @@ a:hover { text-decoration: underline; }
|
|
|
797
801
|
}
|
|
798
802
|
`;
|
|
799
803
|
// ═══════════════════════════════════════════════════════════════════
|
|
800
|
-
// FRONT-END JS
|
|
804
|
+
// FRONT-END JS - renders the embedded payload
|
|
801
805
|
// ═══════════════════════════════════════════════════════════════════
|
|
802
806
|
const REPORT_JS = String.raw `
|
|
803
807
|
(function() {
|
|
@@ -815,7 +819,7 @@ const REPORT_JS = String.raw `
|
|
|
815
819
|
try { return new Date(iso).toLocaleString(); } catch { return iso; }
|
|
816
820
|
}
|
|
817
821
|
function pluralize(n, one, many) { return n === 1 ? one : (many || one + "s"); }
|
|
818
|
-
// Graceful percentage formatter
|
|
822
|
+
// Graceful percentage formatter - mirrors the server-side helper in
|
|
819
823
|
// PRAutomationService. Anything that is not a finite number renders "n/a"
|
|
820
824
|
// instead of being coerced to 0%. Used in every spot that previously
|
|
821
825
|
// hard-coded a plain "x + %" concatenation.
|
|
@@ -840,7 +844,7 @@ const REPORT_JS = String.raw `
|
|
|
840
844
|
if (s.protectedCnt > 0 && s.healed === 0) {
|
|
841
845
|
return [
|
|
842
846
|
"Sela protected your suite from " + s.protectedCnt + " masked " + pluralize(s.protectedCnt, "regression") + ".",
|
|
843
|
-
"These changes looked safe
|
|
847
|
+
"These changes looked safe - but the Intent Auditor caught semantic mismatch and blocked the auto-fix.",
|
|
844
848
|
"Bugs caught"
|
|
845
849
|
];
|
|
846
850
|
}
|
|
@@ -860,7 +864,7 @@ const REPORT_JS = String.raw `
|
|
|
860
864
|
}
|
|
861
865
|
return [
|
|
862
866
|
"Sela couldn't heal " + s.failed + " " + pluralize(s.failed, "selector") + ".",
|
|
863
|
-
"These need a human hand
|
|
867
|
+
"These need a human hand - but you've got context, candidates, and direct links ready below.",
|
|
864
868
|
"Needs attention"
|
|
865
869
|
];
|
|
866
870
|
})();
|
|
@@ -926,15 +930,15 @@ const REPORT_JS = String.raw `
|
|
|
926
930
|
const same = oldStr === newStr;
|
|
927
931
|
const hasAfter = !!after;
|
|
928
932
|
let cls, icon;
|
|
929
|
-
if (!hasAfter) { cls = "missing"; icon = "
|
|
933
|
+
if (!hasAfter) { cls = "missing"; icon = "-"; }
|
|
930
934
|
else if (same) { cls = "matched"; icon = "✅"; }
|
|
931
935
|
else { cls = "changed"; icon = "⚠️"; }
|
|
932
936
|
const valCell = !hasAfter
|
|
933
|
-
? '<td class="val">' + esc(oldStr || "
|
|
937
|
+
? '<td class="val">' + esc(oldStr || "-") + '</td>'
|
|
934
938
|
: same
|
|
935
|
-
? '<td class="val" colspan="1">' + esc(oldStr || "
|
|
936
|
-
: '<td class="val"><span class="val old">' + esc(oldStr || "
|
|
937
|
-
'<br><span class="val new">' + esc(newStr || "
|
|
939
|
+
? '<td class="val" colspan="1">' + esc(oldStr || "-") + '</td>'
|
|
940
|
+
: '<td class="val"><span class="val old">' + esc(oldStr || "-") + '</span>' +
|
|
941
|
+
'<br><span class="val new">' + esc(newStr || "-") + '</span></td>';
|
|
938
942
|
rows.push(
|
|
939
943
|
'<tr class="' + cls + '">' +
|
|
940
944
|
'<td class="field">' + esc(field) + '</td>' +
|
|
@@ -1065,10 +1069,10 @@ const REPORT_JS = String.raw `
|
|
|
1065
1069
|
if (ev.kind !== "HEALED") {
|
|
1066
1070
|
if (ev.kind === "PROTECTED") {
|
|
1067
1071
|
return (
|
|
1068
|
-
'<div class="muted-block">No source code was modified. Sela blocked the AI-proposed change because the Intent Auditor flagged a semantic mismatch
|
|
1072
|
+
'<div class="muted-block">No source code was modified. Sela blocked the AI-proposed change because the Intent Auditor flagged a semantic mismatch - your test file is untouched and the underlying behaviour change is still surfacing in CI.</div>'
|
|
1069
1073
|
);
|
|
1070
1074
|
}
|
|
1071
|
-
return '<div class="muted-block">No source code was modified
|
|
1075
|
+
return '<div class="muted-block">No source code was modified - the AI could not propose a confident fix for this selector.</div>';
|
|
1072
1076
|
}
|
|
1073
1077
|
|
|
1074
1078
|
const raw = ev.gitUnifiedDiff || "";
|
|
@@ -1151,7 +1155,7 @@ const REPORT_JS = String.raw `
|
|
|
1151
1155
|
|
|
1152
1156
|
function renderActions(ev) {
|
|
1153
1157
|
const issueUrl = "https://github.com/anthropics/sela-core/issues/new?title=" +
|
|
1154
|
-
encodeURIComponent("[Sela Report] " + ev.kind + "
|
|
1158
|
+
encodeURIComponent("[Sela Report] " + ev.kind + " - " + ev.stableId) +
|
|
1155
1159
|
"&body=" + encodeURIComponent(
|
|
1156
1160
|
"Stable ID: " + ev.stableId + "\n" +
|
|
1157
1161
|
"File: " + ev.sourceFile + ":" + ev.sourceLine + "\n" +
|
|
@@ -1171,7 +1175,7 @@ const REPORT_JS = String.raw `
|
|
|
1171
1175
|
if (ev.kind === "PROTECTED") {
|
|
1172
1176
|
return (
|
|
1173
1177
|
'<div class="actions">' +
|
|
1174
|
-
'<button class="btn primary" data-action="acknowledge" data-id="' + ev.stableId + '">✓ Acknowledge
|
|
1178
|
+
'<button class="btn primary" data-action="acknowledge" data-id="' + ev.stableId + '">✓ Acknowledge - I will investigate</button>' +
|
|
1175
1179
|
'<a class="btn ghost" target="_blank" rel="noopener" href="' + issueUrl + '">⚑ File regression</a>' +
|
|
1176
1180
|
'</div>'
|
|
1177
1181
|
);
|
|
@@ -1226,7 +1230,7 @@ const REPORT_JS = String.raw `
|
|
|
1226
1230
|
'</div>'
|
|
1227
1231
|
);
|
|
1228
1232
|
|
|
1229
|
-
// 5. Failure Context
|
|
1233
|
+
// 5. Failure Context - Playwright screenshot (FAILED only)
|
|
1230
1234
|
blocks.push(renderFailureContext(ev));
|
|
1231
1235
|
|
|
1232
1236
|
// 6. Alternatives
|
|
@@ -1242,14 +1246,14 @@ const REPORT_JS = String.raw `
|
|
|
1242
1246
|
const steps = [];
|
|
1243
1247
|
steps.push("AI proposed a replacement selector for the broken locator.");
|
|
1244
1248
|
if (ev.aiExplanation) steps.push("Reasoning given: " + ev.aiExplanation);
|
|
1245
|
-
if (ev.candidateNewSelector) steps.push("Candidate selector resolved live
|
|
1249
|
+
if (ev.candidateNewSelector) steps.push("Candidate selector resolved live - but its semantics changed.");
|
|
1246
1250
|
if (ev.auditor) steps.push("Intent Auditor verdict: " + ev.auditor.verdict + " · " + ev.auditor.reason);
|
|
1247
1251
|
steps.push("Sela blocked the auto-fix. Source code untouched. Investigate this as a real regression.");
|
|
1248
1252
|
return steps;
|
|
1249
1253
|
}
|
|
1250
1254
|
function buildFailedSteps(ev) {
|
|
1251
1255
|
const steps = [];
|
|
1252
|
-
steps.push("Locator failed to resolve at runtime
|
|
1256
|
+
steps.push("Locator failed to resolve at runtime - Sela attempted to heal it.");
|
|
1253
1257
|
if (ev.aiExplanation) steps.push("AI response: " + ev.aiExplanation);
|
|
1254
1258
|
steps.push("No confident replacement was produced. Reason: " + (ev.reason || "unknown"));
|
|
1255
1259
|
steps.push("Open the file directly in your editor to inspect the original locator.");
|
|
@@ -1282,7 +1286,7 @@ const REPORT_JS = String.raw `
|
|
|
1282
1286
|
// ── Mount ─────────────────────────────────────────────────────
|
|
1283
1287
|
const $events = $("#events");
|
|
1284
1288
|
if (DATA.events.length === 0) {
|
|
1285
|
-
$events.innerHTML = '<div class="muted-block">Sela was idle on this run
|
|
1289
|
+
$events.innerHTML = '<div class="muted-block">Sela was idle on this run - no broken selectors encountered.</div>';
|
|
1286
1290
|
} else {
|
|
1287
1291
|
$events.innerHTML = DATA.events.map(eventCard).join("");
|
|
1288
1292
|
|
|
@@ -1318,7 +1322,7 @@ const REPORT_JS = String.raw `
|
|
|
1318
1322
|
if (action === "keep" || action === "acknowledge") {
|
|
1319
1323
|
btn.dataset.state = "approved";
|
|
1320
1324
|
btn.textContent = action === "keep" ? "✓ Approved" : "✓ Acknowledged";
|
|
1321
|
-
showToast("Saved locally
|
|
1325
|
+
showToast("Saved locally - Sela noted your decision for " + id);
|
|
1322
1326
|
} else if (action === "rollback") {
|
|
1323
1327
|
const cmd = "npx sela dna refactor --rollback " + id;
|
|
1324
1328
|
navigator.clipboard?.writeText(cmd).catch(() => {});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Typed advisory system for definition-site mutations.
|
|
6
6
|
// Advisories are buffered during the AST phase and flushed AFTER
|
|
7
|
-
// Playwright prints its test result
|
|
7
|
+
// Playwright prints its test result - prevents interleaving.
|
|
8
8
|
// ═══════════════════════════════════════════════════════════════════
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.AdvisoryBuffer = exports.AdvisoryCode = void 0;
|
|
@@ -3,11 +3,11 @@ export type HealCacheMode = "action" | "assertion";
|
|
|
3
3
|
export interface CacheKeyInput {
|
|
4
4
|
/** Absolute or project-relative spec path containing the failing locator. */
|
|
5
5
|
filePath: string;
|
|
6
|
-
/** 1-based line of the failing call site
|
|
6
|
+
/** 1-based line of the failing call site - paired with filePath as a fingerprint. */
|
|
7
7
|
line: number;
|
|
8
8
|
/**
|
|
9
9
|
* Stable content hash of the DNA snapshot used to drive the heal. When the
|
|
10
|
-
* DNA rewrites, this changes
|
|
10
|
+
* DNA rewrites, this changes - every cached entry for the old DNA
|
|
11
11
|
* silently stops matching and ages out via TTL.
|
|
12
12
|
*/
|
|
13
13
|
dnaContentHash: string;
|
|
@@ -66,7 +66,7 @@ export interface HealingCacheSetInput extends CacheKeyInput {
|
|
|
66
66
|
}
|
|
67
67
|
export declare function computeCacheKey(input: CacheKeyInput): string;
|
|
68
68
|
/**
|
|
69
|
-
* Convenience helper
|
|
69
|
+
* Convenience helper - fingerprint the JSON form of a DNA snapshot so
|
|
70
70
|
* the cache key changes whenever the DNA is rewritten.
|
|
71
71
|
*/
|
|
72
72
|
export declare function hashDnaContent(dna: unknown): string;
|
|
@@ -83,7 +83,7 @@ export declare class HealingCacheService {
|
|
|
83
83
|
});
|
|
84
84
|
/**
|
|
85
85
|
* Read-only lookup. Bumps `hitCount` / `lastHitAt` IN-MEMORY only on
|
|
86
|
-
* the returned entry reference
|
|
86
|
+
* the returned entry reference - never persisted on the get() path.
|
|
87
87
|
* This eliminates the read-write race where parallel workers each
|
|
88
88
|
* call get() and serialise full entries-map snapshots back to disk,
|
|
89
89
|
* overwriting peer set() operations.
|
|
@@ -96,7 +96,7 @@ export declare class HealingCacheService {
|
|
|
96
96
|
* deadlocks downstream callers.
|
|
97
97
|
*/
|
|
98
98
|
set(input: HealingCacheSetInput): Promise<void>;
|
|
99
|
-
/** Synchronous variant
|
|
99
|
+
/** Synchronous variant - used by teardown / CLI paths. */
|
|
100
100
|
setSync(input: HealingCacheSetInput): void;
|
|
101
101
|
/** Wipe every entry. Used by tests and the (forthcoming) `sela cache clear` CLI. */
|
|
102
102
|
clear(): void;
|
|
@@ -105,7 +105,7 @@ export declare class HealingCacheService {
|
|
|
105
105
|
/** Absolute path of the on-disk cache file. */
|
|
106
106
|
filePath(): string;
|
|
107
107
|
/**
|
|
108
|
-
* Prunes expired entries. Cheap O(n) scan
|
|
108
|
+
* Prunes expired entries. Cheap O(n) scan - intended for engine
|
|
109
109
|
* startup. Returns the number of entries removed.
|
|
110
110
|
*/
|
|
111
111
|
pruneExpired(): number;
|
|
@@ -115,13 +115,13 @@ export declare class HealingCacheService {
|
|
|
115
115
|
/**
|
|
116
116
|
* Reads + parses the cache file. Tolerates every realistic failure
|
|
117
117
|
* (missing, empty, malformed, wrong schema version) by returning a
|
|
118
|
-
* fresh empty shell
|
|
118
|
+
* fresh empty shell - the contract is "cache misses are silent".
|
|
119
119
|
*/
|
|
120
120
|
private safeRead;
|
|
121
121
|
/**
|
|
122
122
|
* Atomic write: write to `<file>.tmp`, copy current to `<file>.bak`,
|
|
123
123
|
* then rename `<file>.tmp` → `<file>`. The rename is the single
|
|
124
|
-
* commit point
|
|
124
|
+
* commit point - a crash between bak and rename leaves the bak file
|
|
125
125
|
* intact for future recovery.
|
|
126
126
|
*/
|
|
127
127
|
private atomicWrite;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealingCacheService.d.ts","sourceRoot":"","sources":["../../src/services/HealingCacheService.ts"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAMvE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,aAAa,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAc5D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"HealingCacheService.d.ts","sourceRoot":"","sources":["../../src/services/HealingCacheService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/services/HealingCacheService.ts
|
|
3
3
|
//
|
|
4
|
-
// Local healing cache
|
|
4
|
+
// Local healing cache - short-circuits the LLM round-trip when an
|
|
5
5
|
// identical heal-context has been resolved successfully in a prior
|
|
6
6
|
// session.
|
|
7
7
|
//
|
|
8
8
|
// Cache key (matches DX MVP Phase 6 plan):
|
|
9
9
|
// SHA1(filePath + ":" + line + "::" + dnaContentHash + "::" + brokenSelector + "::" + mode)
|
|
10
10
|
// Including `dnaContentHash` means a DNA rewrite automatically
|
|
11
|
-
// invalidates every entry keyed against that fingerprint
|
|
11
|
+
// invalidates every entry keyed against that fingerprint - old
|
|
12
12
|
// entries simply stop matching and age out via TTL.
|
|
13
13
|
//
|
|
14
14
|
// Cache value:
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// callers.
|
|
32
32
|
//
|
|
33
33
|
// 2. Cross-process file lock
|
|
34
|
-
// Playwright workers run as separate processes
|
|
34
|
+
// Playwright workers run as separate processes - atomic
|
|
35
35
|
// `mkdir <cacheDir>/.sela-cache.lock` is used as the mutex. Second
|
|
36
36
|
// process gets `EEXIST`, polls with exponential backoff
|
|
37
37
|
// (50ms → 800ms, max 6 retries ≈ 3.15 s ceiling). Lock is held
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
//
|
|
40
40
|
// 3. Atomic write via temp + rename
|
|
41
41
|
// Write to `.sela-cache.json.tmp`, copy the previous file to
|
|
42
|
-
// `.sela-cache.json.bak`, then `fs.renameSync(tmp, target)`
|
|
42
|
+
// `.sela-cache.json.bak`, then `fs.renameSync(tmp, target)` -
|
|
43
43
|
// atomic on POSIX and on Windows (Node ≥14) when source and
|
|
44
44
|
// target share a volume (always true here).
|
|
45
45
|
//
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
// acquires the LLM via the normal path; cache writes happen post-success.
|
|
49
49
|
//
|
|
50
50
|
// `get()` is strictly read-only on disk: hit-count / lastHitAt are
|
|
51
|
-
// bumped IN-MEMORY only
|
|
51
|
+
// bumped IN-MEMORY only - they never trigger a disk write. This avoids
|
|
52
52
|
// the documented bug where concurrent worker `get()` calls would
|
|
53
53
|
// overwrite valid `set()` operations from peer workers.
|
|
54
54
|
//
|
|
55
55
|
// Failure tolerance: any read failure (missing, malformed JSON, wrong
|
|
56
56
|
// schema, EACCES, …) returns "no entries" instead of throwing. The
|
|
57
|
-
// cache is a latency optimisation
|
|
57
|
+
// cache is a latency optimisation - never a hard dependency of the
|
|
58
58
|
// heal pipeline.
|
|
59
59
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
60
60
|
if (k2 === undefined) k2 = k;
|
|
@@ -126,7 +126,7 @@ function computeCacheKey(input) {
|
|
|
126
126
|
.digest("hex");
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
|
-
* Convenience helper
|
|
129
|
+
* Convenience helper - fingerprint the JSON form of a DNA snapshot so
|
|
130
130
|
* the cache key changes whenever the DNA is rewritten.
|
|
131
131
|
*/
|
|
132
132
|
function hashDnaContent(dna) {
|
|
@@ -148,7 +148,7 @@ class HealingCacheService {
|
|
|
148
148
|
tmpSuffix = ".tmp";
|
|
149
149
|
bakSuffix = ".bak";
|
|
150
150
|
// In-process serialisation. Every new set() chains onto the prior
|
|
151
|
-
// tail Promise. The chain is fault-tolerant
|
|
151
|
+
// tail Promise. The chain is fault-tolerant - see `set()` below.
|
|
152
152
|
inFlight = Promise.resolve();
|
|
153
153
|
constructor(opts) {
|
|
154
154
|
this.cacheDir = opts?.cacheDir ?? process.cwd();
|
|
@@ -160,7 +160,7 @@ class HealingCacheService {
|
|
|
160
160
|
// ─────────────────────────────────────────────────────────────
|
|
161
161
|
/**
|
|
162
162
|
* Read-only lookup. Bumps `hitCount` / `lastHitAt` IN-MEMORY only on
|
|
163
|
-
* the returned entry reference
|
|
163
|
+
* the returned entry reference - never persisted on the get() path.
|
|
164
164
|
* This eliminates the read-write race where parallel workers each
|
|
165
165
|
* call get() and serialise full entries-map snapshots back to disk,
|
|
166
166
|
* overwriting peer set() operations.
|
|
@@ -175,7 +175,7 @@ class HealingCacheService {
|
|
|
175
175
|
return null;
|
|
176
176
|
if (this.isExpired(entry))
|
|
177
177
|
return null;
|
|
178
|
-
// In-memory only
|
|
178
|
+
// In-memory only - DO NOT write back. Hit-count drift across workers
|
|
179
179
|
// is acceptable; corrupting peer writes is not.
|
|
180
180
|
entry.hitCount = (entry.hitCount ?? 0) + 1;
|
|
181
181
|
entry.lastHitAt = new Date().toISOString();
|
|
@@ -195,11 +195,11 @@ class HealingCacheService {
|
|
|
195
195
|
// Chain onto the previous tail, but swallow rejections via a
|
|
196
196
|
// `.catch(noop)` adapter. Without this, an earlier rejected set()
|
|
197
197
|
// would propagate through every subsequent `await prev` and either
|
|
198
|
-
// throw at the wrong caller or
|
|
198
|
+
// throw at the wrong caller or - worse - leak through the finally
|
|
199
199
|
// block without releasing the queue. The wrapper guarantees the
|
|
200
200
|
// queue is FIFO and rejection-safe.
|
|
201
201
|
const prev = this.inFlight.catch(() => {
|
|
202
|
-
/* swallow
|
|
202
|
+
/* swallow - upstream failure handled by its own caller */
|
|
203
203
|
});
|
|
204
204
|
let release;
|
|
205
205
|
const myTurn = new Promise((res) => {
|
|
@@ -211,12 +211,12 @@ class HealingCacheService {
|
|
|
211
211
|
await this.lockedWrite(input);
|
|
212
212
|
}
|
|
213
213
|
finally {
|
|
214
|
-
// Always release
|
|
214
|
+
// Always release - even on throw - so the next queued caller
|
|
215
215
|
// unblocks. Errors still propagate up to *this* caller normally.
|
|
216
216
|
release();
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
/** Synchronous variant
|
|
219
|
+
/** Synchronous variant - used by teardown / CLI paths. */
|
|
220
220
|
setSync(input) {
|
|
221
221
|
if (DryRunGuard_1.DryRunGuard.active())
|
|
222
222
|
return;
|
|
@@ -253,7 +253,7 @@ class HealingCacheService {
|
|
|
253
253
|
return path.join(this.cacheDir, this.filename);
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
|
-
* Prunes expired entries. Cheap O(n) scan
|
|
256
|
+
* Prunes expired entries. Cheap O(n) scan - intended for engine
|
|
257
257
|
* startup. Returns the number of entries removed.
|
|
258
258
|
*/
|
|
259
259
|
pruneExpired() {
|
|
@@ -272,7 +272,7 @@ class HealingCacheService {
|
|
|
272
272
|
return removed;
|
|
273
273
|
}
|
|
274
274
|
// ─────────────────────────────────────────────────────────────
|
|
275
|
-
// INTERNAL
|
|
275
|
+
// INTERNAL - read / write / lock
|
|
276
276
|
// ─────────────────────────────────────────────────────────────
|
|
277
277
|
isExpired(entry) {
|
|
278
278
|
if (!entry.ttlExpiresAt)
|
|
@@ -285,7 +285,7 @@ class HealingCacheService {
|
|
|
285
285
|
async lockedWrite(input) {
|
|
286
286
|
const acquired = await this.acquireLockWithBackoff();
|
|
287
287
|
if (!acquired) {
|
|
288
|
-
console.warn(`[HealingCache] ⚠️ Could not acquire lock at ${this.lockDir}
|
|
288
|
+
console.warn(`[HealingCache] ⚠️ Could not acquire lock at ${this.lockDir} - write skipped`);
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
291
|
try {
|
|
@@ -326,7 +326,7 @@ class HealingCacheService {
|
|
|
326
326
|
/**
|
|
327
327
|
* Reads + parses the cache file. Tolerates every realistic failure
|
|
328
328
|
* (missing, empty, malformed, wrong schema version) by returning a
|
|
329
|
-
* fresh empty shell
|
|
329
|
+
* fresh empty shell - the contract is "cache misses are silent".
|
|
330
330
|
*/
|
|
331
331
|
safeRead() {
|
|
332
332
|
const target = path.join(this.cacheDir, this.filename);
|
|
@@ -345,7 +345,7 @@ class HealingCacheService {
|
|
|
345
345
|
parsed = JSON.parse(raw);
|
|
346
346
|
}
|
|
347
347
|
catch (err) {
|
|
348
|
-
console.warn(`[HealingCache] ⚠️ Corrupt cache file at ${target}
|
|
348
|
+
console.warn(`[HealingCache] ⚠️ Corrupt cache file at ${target} - starting fresh (${err.message})`);
|
|
349
349
|
return { schemaVersion: 1, entries: {} };
|
|
350
350
|
}
|
|
351
351
|
if (!parsed ||
|
|
@@ -355,7 +355,7 @@ class HealingCacheService {
|
|
|
355
355
|
typeof parsed.entries !== "object" ||
|
|
356
356
|
parsed.entries === null ||
|
|
357
357
|
Array.isArray(parsed.entries)) {
|
|
358
|
-
console.warn(`[HealingCache] ⚠️ Unknown cache schema at ${target}
|
|
358
|
+
console.warn(`[HealingCache] ⚠️ Unknown cache schema at ${target} - starting fresh`);
|
|
359
359
|
return { schemaVersion: 1, entries: {} };
|
|
360
360
|
}
|
|
361
361
|
return parsed;
|
|
@@ -363,7 +363,7 @@ class HealingCacheService {
|
|
|
363
363
|
/**
|
|
364
364
|
* Atomic write: write to `<file>.tmp`, copy current to `<file>.bak`,
|
|
365
365
|
* then rename `<file>.tmp` → `<file>`. The rename is the single
|
|
366
|
-
* commit point
|
|
366
|
+
* commit point - a crash between bak and rename leaves the bak file
|
|
367
367
|
* intact for future recovery.
|
|
368
368
|
*/
|
|
369
369
|
atomicWrite(file) {
|
|
@@ -386,7 +386,7 @@ class HealingCacheService {
|
|
|
386
386
|
fs.renameSync(tmp, target);
|
|
387
387
|
}
|
|
388
388
|
// ─────────────────────────────────────────────────────────────
|
|
389
|
-
// FILE LOCK
|
|
389
|
+
// FILE LOCK - atomic mkdir as a cross-process mutex
|
|
390
390
|
// ─────────────────────────────────────────────────────────────
|
|
391
391
|
async acquireLockWithBackoff() {
|
|
392
392
|
const delaysMs = [50, 100, 200, 400, 800, 800];
|
|
@@ -430,7 +430,7 @@ class HealingCacheService {
|
|
|
430
430
|
break;
|
|
431
431
|
sleepBusy(delaysMs[attempt]);
|
|
432
432
|
}
|
|
433
|
-
console.warn(`[HealingCache] ⚠️ Lock acquire failed (sync)
|
|
433
|
+
console.warn(`[HealingCache] ⚠️ Lock acquire failed (sync) - operation skipped`);
|
|
434
434
|
}
|
|
435
435
|
releaseLock() {
|
|
436
436
|
try {
|
|
@@ -455,6 +455,6 @@ function sleepBusy(ms) {
|
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
// ═══════════════════════════════════════════════════════════════════
|
|
458
|
-
// Module-level singleton
|
|
458
|
+
// Module-level singleton - engines reuse the same cache file by default
|
|
459
459
|
// ═══════════════════════════════════════════════════════════════════
|
|
460
460
|
exports.sharedHealingCache = new HealingCacheService();
|