sela-core 1.0.5 → 1.0.7
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 +1 -1
- package/dist/config/ConfigLoader.d.ts +1 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +10 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +79 -0
- package/dist/config/SelaConfig.d.ts +15 -1
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +27 -1
- package/dist/engine/SelaEngine.d.ts +26 -6
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +329 -77
- package/dist/errors/SelaError.d.ts +133 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +155 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -1
- package/dist/fixtures/expectProxy.js +7 -0
- package/dist/fixtures/index.d.ts +7 -1
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +15 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/reporter/SelaReporter.d.ts +110 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +328 -0
- package/dist/services/ASTSourceUpdater.d.ts +19 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +173 -29
- package/dist/services/HealReportService.d.ts +62 -3
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +184 -14
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +460 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +20 -1
- package/dist/services/IntentAuditor.d.ts +18 -1
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +19 -6
- package/dist/services/InteractiveReview.d.ts +24 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +218 -0
- package/dist/services/LLMService.d.ts +23 -2
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +48 -12
- package/dist/services/PRAutomationService.d.ts +23 -3
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +325 -79
- package/dist/services/PendingPromptLedger.d.ts +44 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +180 -0
- package/dist/services/ReportGenerator.d.ts +70 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +191 -0
- package/dist/services/SafetyGuard.d.ts +34 -2
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +65 -13
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +44 -13
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +202 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +379 -0
- package/package.json +71 -67
|
@@ -37,6 +37,9 @@ exports.ASTSourceUpdater = void 0;
|
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const ts_morph_1 = require("ts-morph");
|
|
40
|
+
const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
41
|
+
const WorkspaceSnapshotService_1 = require("./WorkspaceSnapshotService");
|
|
42
|
+
const SelaError_1 = require("../errors/SelaError");
|
|
40
43
|
const HealingRegistry_1 = require("../engine/HealingRegistry");
|
|
41
44
|
const ArgumentTypeAnalyzer_1 = require("./ArgumentTypeAnalyzer");
|
|
42
45
|
const BlastRadiusAnalyzer_1 = require("./BlastRadiusAnalyzer");
|
|
@@ -723,6 +726,33 @@ function resolveVariableToSegments(sourceFile, varName, beforeLine, depth = 0) {
|
|
|
723
726
|
};
|
|
724
727
|
}
|
|
725
728
|
// ═══════════════════════════════════════════════════════════════════
|
|
729
|
+
// SHARED PERSISTENCE HOOK
|
|
730
|
+
//
|
|
731
|
+
// Module-level write gate shared between `ASTSourceUpdater` and the
|
|
732
|
+
// internal `NodeTransformationStrategy`. Centralises three concerns
|
|
733
|
+
// every `saveSync` call site needs:
|
|
734
|
+
// 1. `DryRunGuard.active()` short-circuit — keeps the mutation in
|
|
735
|
+
// memory but suppresses the on-disk write under SELA_DRY_RUN.
|
|
736
|
+
// 2. `sharedWorkspaceSnapshot.preWrite()` — captures `contentBefore`
|
|
737
|
+
// so the Clean-Room ledger / Local DX rollback can rewind.
|
|
738
|
+
// 3. `sharedWorkspaceSnapshot.postWrite()` — refreshes `contentAfter`
|
|
739
|
+
// so `getMutatedSnapshots()` (driving the pending-prompts ledger
|
|
740
|
+
// and the interactive CLI prompt) sees the new disk state.
|
|
741
|
+
//
|
|
742
|
+
// Strategies do NOT receive the persistence service via DI — they call
|
|
743
|
+
// this helper directly. Same singleton, zero coupling, one code path.
|
|
744
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
745
|
+
function persistSourceFile(sourceFile) {
|
|
746
|
+
if (DryRunGuard_1.DryRunGuard.active()) {
|
|
747
|
+
DryRunGuard_1.DryRunGuard.logSkippedWrite("saveSync", sourceFile.getFilePath());
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const fp = sourceFile.getFilePath();
|
|
751
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.preWrite(fp);
|
|
752
|
+
sourceFile.saveSync();
|
|
753
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.postWrite(fp);
|
|
754
|
+
}
|
|
755
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
726
756
|
// NODE TRANSFORMATION STRATEGY — Scope-Aware, Zero-Redundancy
|
|
727
757
|
// ═══════════════════════════════════════════════════════════════════
|
|
728
758
|
class NodeTransformationStrategy {
|
|
@@ -959,7 +989,7 @@ class NodeTransformationStrategy {
|
|
|
959
989
|
` ${newCode.split("\n")[0]}`);
|
|
960
990
|
try {
|
|
961
991
|
chainTop.replaceWithText(newCode);
|
|
962
|
-
sourceFile
|
|
992
|
+
persistSourceFile(sourceFile);
|
|
963
993
|
return {
|
|
964
994
|
success: true,
|
|
965
995
|
reason: `scope-aware transform [${effectiveReceiver}]: ${resolvedSegments.length} segments`,
|
|
@@ -1146,7 +1176,7 @@ class NodeTransformationStrategy {
|
|
|
1146
1176
|
console.log(`[NodeTransform] 🔬 Partial: segment[${changedIdx}] .${currentMethodName}() → ${newCallText}`);
|
|
1147
1177
|
try {
|
|
1148
1178
|
targetCall.replaceWithText(newCallText);
|
|
1149
|
-
sourceFile
|
|
1179
|
+
persistSourceFile(sourceFile);
|
|
1150
1180
|
return {
|
|
1151
1181
|
success: true,
|
|
1152
1182
|
reason: `partial: seg[${changedIdx}] .${currentMethodName}() → .${newSeg.type}()`,
|
|
@@ -1201,7 +1231,7 @@ class NodeTransformationStrategy {
|
|
|
1201
1231
|
if (!ts_morph_1.Node.isStringLiteral(firstArg))
|
|
1202
1232
|
continue;
|
|
1203
1233
|
firstArg.replaceWithText(`"${newSelector}"`);
|
|
1204
|
-
sourceFile
|
|
1234
|
+
persistSourceFile(sourceFile);
|
|
1205
1235
|
return true;
|
|
1206
1236
|
}
|
|
1207
1237
|
return false;
|
|
@@ -1529,6 +1559,112 @@ class ASTSourceUpdater {
|
|
|
1529
1559
|
flushAdvisories() {
|
|
1530
1560
|
this.advisoryBuffer.flush();
|
|
1531
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Centralised write hook for every ts-morph `saveSync` call in this class.
|
|
1564
|
+
* When `SELA_DRY_RUN` is active the AST mutation stays in memory but the
|
|
1565
|
+
* on-disk file is left untouched — the heal pipeline can still compute a
|
|
1566
|
+
* candidate diff via `sourceFile.getFullText()` and the report layer
|
|
1567
|
+
* tags the event with `dryRun: true`.
|
|
1568
|
+
*/
|
|
1569
|
+
_persistChanges(sourceFile) {
|
|
1570
|
+
persistSourceFile(sourceFile);
|
|
1571
|
+
}
|
|
1572
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1573
|
+
// FAIL-FAST DIRECTIVE SCAN
|
|
1574
|
+
//
|
|
1575
|
+
// Walks the leading comment ranges of the failing statement (and the
|
|
1576
|
+
// CallExpression node when it differs) looking for the directive
|
|
1577
|
+
// `// sela-fail-fast`. When found, throws `SelaError(ASTUpdater,
|
|
1578
|
+
// AST_HEAL_DISABLED_BY_DIRECTIVE)` so SelaEngine can short-circuit
|
|
1579
|
+
// BEFORE the LLM call and record a FailedEvent in the "Skipped by
|
|
1580
|
+
// Developer" category.
|
|
1581
|
+
//
|
|
1582
|
+
// Implementation note — ts-morph's `Node.getLeadingCommentRanges()`
|
|
1583
|
+
// returns `ts.CommentRange[]` (offsets, not text). We slice the full
|
|
1584
|
+
// source text by `(pos, end)` to recover the raw comment for each
|
|
1585
|
+
// range, matching both `//` line comments and `/* */` block comments
|
|
1586
|
+
// with either `sela-fail-fast` or `@sela-fail-fast` spelling.
|
|
1587
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1588
|
+
scanFailFastDirective(caller) {
|
|
1589
|
+
const filePath = this.resolveFilePath(caller.filePath);
|
|
1590
|
+
if (!filePath)
|
|
1591
|
+
return { failFast: false };
|
|
1592
|
+
let sourceFile = this.project.getSourceFile(filePath);
|
|
1593
|
+
if (sourceFile) {
|
|
1594
|
+
sourceFile.refreshFromFileSystemSync();
|
|
1595
|
+
}
|
|
1596
|
+
else {
|
|
1597
|
+
sourceFile = this.project.addSourceFileAtPath(filePath);
|
|
1598
|
+
}
|
|
1599
|
+
if (caller.line <= 0)
|
|
1600
|
+
return { failFast: false };
|
|
1601
|
+
const failureNode = this.findNodeAtLine(sourceFile, caller.line);
|
|
1602
|
+
if (!failureNode)
|
|
1603
|
+
return { failFast: false };
|
|
1604
|
+
const statement = this.getEnclosingStatement(failureNode);
|
|
1605
|
+
// Collect candidate comment-owning nodes: enclosing statement (catches
|
|
1606
|
+
// pre-statement comments) AND the failure node itself (catches inline
|
|
1607
|
+
// pre-CallExpression comments inside multi-line chains). De-dup by
|
|
1608
|
+
// offset to avoid double-matching.
|
|
1609
|
+
const fullText = sourceFile.getFullText();
|
|
1610
|
+
const seen = new Set();
|
|
1611
|
+
const nodes = [];
|
|
1612
|
+
if (statement)
|
|
1613
|
+
nodes.push(statement);
|
|
1614
|
+
if (failureNode !== statement)
|
|
1615
|
+
nodes.push(failureNode);
|
|
1616
|
+
const DIRECTIVE_RE = /\/[\/*]\s*@?sela-fail-fast\b/i;
|
|
1617
|
+
for (const node of nodes) {
|
|
1618
|
+
let ranges = [];
|
|
1619
|
+
try {
|
|
1620
|
+
ranges = node.getLeadingCommentRanges() ?? [];
|
|
1621
|
+
}
|
|
1622
|
+
catch {
|
|
1623
|
+
ranges = [];
|
|
1624
|
+
}
|
|
1625
|
+
for (const r of ranges) {
|
|
1626
|
+
const pos = r.getPos();
|
|
1627
|
+
const end = r.getEnd();
|
|
1628
|
+
const key = `${pos}:${end}`;
|
|
1629
|
+
if (seen.has(key))
|
|
1630
|
+
continue;
|
|
1631
|
+
seen.add(key);
|
|
1632
|
+
const text = fullText.slice(pos, end);
|
|
1633
|
+
if (DIRECTIVE_RE.test(text)) {
|
|
1634
|
+
const lineNum = sourceFile.getLineAndColumnAtPos(pos).line;
|
|
1635
|
+
return {
|
|
1636
|
+
failFast: true,
|
|
1637
|
+
matchedComment: text.trim(),
|
|
1638
|
+
matchedLine: lineNum,
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return { failFast: false };
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Convenience helper — runs `scanFailFastDirective` and throws the
|
|
1647
|
+
* canonical SelaError when the directive is present. Used by SelaEngine
|
|
1648
|
+
* as the very first gate inside `heal()`.
|
|
1649
|
+
*/
|
|
1650
|
+
enforceFailFastDirective(caller) {
|
|
1651
|
+
const result = this.scanFailFastDirective(caller);
|
|
1652
|
+
if (!result.failFast)
|
|
1653
|
+
return;
|
|
1654
|
+
throw new SelaError_1.SelaError({
|
|
1655
|
+
subsystem: "ASTUpdater",
|
|
1656
|
+
code: SelaError_1.AST_UPDATER_CODES.HEAL_DISABLED_BY_DIRECTIVE,
|
|
1657
|
+
reason: `Heal disabled by developer directive at ${path.basename(caller.filePath)}` +
|
|
1658
|
+
(result.matchedLine ? `:${result.matchedLine}` : ""),
|
|
1659
|
+
context: {
|
|
1660
|
+
filePath: caller.filePath,
|
|
1661
|
+
line: caller.line,
|
|
1662
|
+
directiveLine: result.matchedLine,
|
|
1663
|
+
directiveText: result.matchedComment,
|
|
1664
|
+
category: "Skipped by Developer",
|
|
1665
|
+
},
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1532
1668
|
constructor() {
|
|
1533
1669
|
this.project = new ts_morph_1.Project({
|
|
1534
1670
|
useInMemoryFileSystem: false,
|
|
@@ -1673,6 +1809,14 @@ class ASTSourceUpdater {
|
|
|
1673
1809
|
const statement = failureNode
|
|
1674
1810
|
? this.getEnclosingStatement(failureNode)
|
|
1675
1811
|
: undefined;
|
|
1812
|
+
// ─── Developer fail-fast directive ─────────────────────────────
|
|
1813
|
+
// Scan leading comments on the enclosing statement (and the failure
|
|
1814
|
+
// node when they differ) for `// sela-fail-fast`. When present, abort
|
|
1815
|
+
// all heal work with SelaError(AST_HEAL_DISABLED_BY_DIRECTIVE) so the
|
|
1816
|
+
// engine records a FailedEvent in the "Skipped by Developer" bucket.
|
|
1817
|
+
// Defense-in-depth — SelaEngine also calls this at the very start of
|
|
1818
|
+
// heal() to skip the LLM round-trip entirely.
|
|
1819
|
+
this.enforceFailFastDirective(caller);
|
|
1676
1820
|
const suspectIdentifier = statement
|
|
1677
1821
|
? this.identifySuspectIdentifier(statement, caller.line)
|
|
1678
1822
|
: null;
|
|
@@ -1850,7 +1994,7 @@ class ASTSourceUpdater {
|
|
|
1850
1994
|
console.log(`[ASTUpdater] 🔗 H: full replace (depth=${chainDepth}) →\n${newCode}`);
|
|
1851
1995
|
try {
|
|
1852
1996
|
chainTop.replaceWithText(newCode);
|
|
1853
|
-
|
|
1997
|
+
this._persistChanges(sourceFile);
|
|
1854
1998
|
return {
|
|
1855
1999
|
success: true,
|
|
1856
2000
|
reason: `smart chain applied (${resolvedSegments.length} segments, depth=${chainDepth})`,
|
|
@@ -1914,7 +2058,7 @@ class ASTSourceUpdater {
|
|
|
1914
2058
|
console.log(`[ASTUpdater] 🔗 H partial: .${currentMethodName}() → ${newCallText}`);
|
|
1915
2059
|
try {
|
|
1916
2060
|
targetCall.replaceWithText(newCallText);
|
|
1917
|
-
|
|
2061
|
+
this._persistChanges(sourceFile);
|
|
1918
2062
|
return {
|
|
1919
2063
|
success: true,
|
|
1920
2064
|
reason: `partial chain heal: segment[${changedIdx}] .${currentMethodName}() → .${newSeg.type}()`,
|
|
@@ -1975,7 +2119,7 @@ class ASTSourceUpdater {
|
|
|
1975
2119
|
const newCallText = buildCallText(rootReceiver, newSem);
|
|
1976
2120
|
try {
|
|
1977
2121
|
chainTop.replaceWithText(newCallText);
|
|
1978
|
-
|
|
2122
|
+
this._persistChanges(sourceFile);
|
|
1979
2123
|
return {
|
|
1980
2124
|
success: true,
|
|
1981
2125
|
reason: `chain collapsed atomically (depth=${chainDepth})`,
|
|
@@ -2059,7 +2203,7 @@ class ASTSourceUpdater {
|
|
|
2059
2203
|
}
|
|
2060
2204
|
}
|
|
2061
2205
|
if (anyFixed) {
|
|
2062
|
-
|
|
2206
|
+
this._persistChanges(sourceFile);
|
|
2063
2207
|
return {
|
|
2064
2208
|
success: true,
|
|
2065
2209
|
reason: `upstream variable '${varName}' fixed`,
|
|
@@ -2070,7 +2214,7 @@ class ASTSourceUpdater {
|
|
|
2070
2214
|
for (const change of changedFrames) {
|
|
2071
2215
|
const result = this.replaceStringLiteralInRange(sourceFile, change.old, change.new, declLine, Math.min(sourceFile.getEndLineNumber(), declLine + 8));
|
|
2072
2216
|
if (result !== null) {
|
|
2073
|
-
|
|
2217
|
+
this._persistChanges(sourceFile);
|
|
2074
2218
|
return {
|
|
2075
2219
|
success: true,
|
|
2076
2220
|
reason: "multi-line frame fixed",
|
|
@@ -2085,7 +2229,7 @@ class ASTSourceUpdater {
|
|
|
2085
2229
|
for (const change of changedFrames) {
|
|
2086
2230
|
const result = this.replaceStringLiteralInRange(sourceFile, change.old, change.new, Math.max(1, caller.line - 15), caller.line);
|
|
2087
2231
|
if (result !== null) {
|
|
2088
|
-
|
|
2232
|
+
this._persistChanges(sourceFile);
|
|
2089
2233
|
return {
|
|
2090
2234
|
success: true,
|
|
2091
2235
|
reason: "inline frame fixed",
|
|
@@ -2166,10 +2310,10 @@ class ASTSourceUpdater {
|
|
|
2166
2310
|
const newCallText = buildCallText(effectiveReceiver, newSem);
|
|
2167
2311
|
try {
|
|
2168
2312
|
chainTop.replaceWithText(newCallText);
|
|
2169
|
-
|
|
2313
|
+
this._persistChanges(sourceFile);
|
|
2170
2314
|
const cascadePairs = this.performCascadeHeal(sourceFile, oldSelector, newSelector, oldSem, newSem);
|
|
2171
2315
|
if (cascadePairs.length > 0) {
|
|
2172
|
-
|
|
2316
|
+
this._persistChanges(sourceFile);
|
|
2173
2317
|
HealingRegistry_1.HealingRegistry.getInstance().broadcastCascade({ filePath: caller.filePath, line: caller.line }, cascadePairs);
|
|
2174
2318
|
}
|
|
2175
2319
|
return {
|
|
@@ -2208,10 +2352,10 @@ class ASTSourceUpdater {
|
|
|
2208
2352
|
const { chainTop: tailChainTop, rootReceiver } = climbToChainTop(tailCall);
|
|
2209
2353
|
try {
|
|
2210
2354
|
tailChainTop.replaceWithText(buildCallText(rootReceiver, newSem));
|
|
2211
|
-
|
|
2355
|
+
this._persistChanges(sourceFile);
|
|
2212
2356
|
const cascadePairs = this.performCascadeHeal(sourceFile, oldSelector, newSelector, oldSem, newSem);
|
|
2213
2357
|
if (cascadePairs.length > 0) {
|
|
2214
|
-
|
|
2358
|
+
this._persistChanges(sourceFile);
|
|
2215
2359
|
HealingRegistry_1.HealingRegistry.getInstance().broadcastCascade({ filePath: caller.filePath, line: caller.line }, cascadePairs);
|
|
2216
2360
|
}
|
|
2217
2361
|
return {
|
|
@@ -2228,10 +2372,10 @@ class ASTSourceUpdater {
|
|
|
2228
2372
|
}
|
|
2229
2373
|
const fixed = this.replaceChainTailOnly(tailCall, newSem, newSelector);
|
|
2230
2374
|
if (fixed) {
|
|
2231
|
-
|
|
2375
|
+
this._persistChanges(sourceFile);
|
|
2232
2376
|
const cascadePairs = this.performCascadeHeal(sourceFile, oldSelector, newSelector, oldSem, newSem);
|
|
2233
2377
|
if (cascadePairs.length > 0) {
|
|
2234
|
-
|
|
2378
|
+
this._persistChanges(sourceFile);
|
|
2235
2379
|
HealingRegistry_1.HealingRegistry.getInstance().broadcastCascade({ filePath: caller.filePath, line: caller.line }, cascadePairs);
|
|
2236
2380
|
}
|
|
2237
2381
|
return {
|
|
@@ -2253,7 +2397,7 @@ class ASTSourceUpdater {
|
|
|
2253
2397
|
if (chainDepth > 0 || isLeafOfChain(call)) {
|
|
2254
2398
|
try {
|
|
2255
2399
|
stmtChainTop.replaceWithText(buildCallText(rootReceiver, newSem));
|
|
2256
|
-
|
|
2400
|
+
this._persistChanges(sourceFile);
|
|
2257
2401
|
return {
|
|
2258
2402
|
success: true,
|
|
2259
2403
|
reason: "direct statement chain collapse",
|
|
@@ -2264,10 +2408,10 @@ class ASTSourceUpdater {
|
|
|
2264
2408
|
catch { }
|
|
2265
2409
|
}
|
|
2266
2410
|
if (this.replaceChainTailOnly(call, newSem, newSelector)) {
|
|
2267
|
-
|
|
2411
|
+
this._persistChanges(sourceFile);
|
|
2268
2412
|
const cascadePairs = this.performCascadeHeal(sourceFile, oldSelector, newSelector, oldSem, newSem);
|
|
2269
2413
|
if (cascadePairs.length > 0) {
|
|
2270
|
-
|
|
2414
|
+
this._persistChanges(sourceFile);
|
|
2271
2415
|
HealingRegistry_1.HealingRegistry.getInstance().broadcastCascade({ filePath: caller.filePath, line: caller.line }, cascadePairs);
|
|
2272
2416
|
}
|
|
2273
2417
|
return {
|
|
@@ -2301,10 +2445,10 @@ class ASTSourceUpdater {
|
|
|
2301
2445
|
try {
|
|
2302
2446
|
chainTop.replaceWithText(buildCallText(rootReceiver, newSem));
|
|
2303
2447
|
const fixLine = chainTop.getStartLineNumber();
|
|
2304
|
-
|
|
2448
|
+
this._persistChanges(sourceFile);
|
|
2305
2449
|
const cascadePairs = this.performCascadeHeal(sourceFile, oldSelector, newSelector, oldSem, newSem);
|
|
2306
2450
|
if (cascadePairs.length > 0) {
|
|
2307
|
-
|
|
2451
|
+
this._persistChanges(sourceFile);
|
|
2308
2452
|
HealingRegistry_1.HealingRegistry.getInstance().broadcastCascade({ filePath: caller.filePath, line: caller.line }, cascadePairs);
|
|
2309
2453
|
}
|
|
2310
2454
|
return {
|
|
@@ -2319,10 +2463,10 @@ class ASTSourceUpdater {
|
|
|
2319
2463
|
}
|
|
2320
2464
|
if (this.applySemanticFix(call, newSem, newSelector)) {
|
|
2321
2465
|
const fixLine = call.getStartLineNumber();
|
|
2322
|
-
|
|
2466
|
+
this._persistChanges(sourceFile);
|
|
2323
2467
|
const cascadePairs = this.performCascadeHeal(sourceFile, oldSelector, newSelector, oldSem, newSem);
|
|
2324
2468
|
if (cascadePairs.length > 0) {
|
|
2325
|
-
|
|
2469
|
+
this._persistChanges(sourceFile);
|
|
2326
2470
|
HealingRegistry_1.HealingRegistry.getInstance().broadcastCascade({ filePath: caller.filePath, line: caller.line }, cascadePairs);
|
|
2327
2471
|
}
|
|
2328
2472
|
return {
|
|
@@ -2385,7 +2529,7 @@ class ASTSourceUpdater {
|
|
|
2385
2529
|
if (newText !== fullText) {
|
|
2386
2530
|
tmpl.replaceWithText(newText.trim());
|
|
2387
2531
|
const line = tmpl.getStartLineNumber();
|
|
2388
|
-
|
|
2532
|
+
this._persistChanges(sourceFile);
|
|
2389
2533
|
return {
|
|
2390
2534
|
success: true,
|
|
2391
2535
|
reason: "template literal suffix updated",
|
|
@@ -2430,7 +2574,7 @@ class ASTSourceUpdater {
|
|
|
2430
2574
|
if (chainDepth > 0 || isLeafOfChain(call)) {
|
|
2431
2575
|
try {
|
|
2432
2576
|
chainTop.replaceWithText(buildCallText(rootReceiver, newSem));
|
|
2433
|
-
|
|
2577
|
+
this._persistChanges(sourceFile);
|
|
2434
2578
|
return {
|
|
2435
2579
|
success: true,
|
|
2436
2580
|
reason: `fragment scan chain collapse .${callSem.method}() @ line ${call.getStartLineNumber()}`,
|
|
@@ -2441,7 +2585,7 @@ class ASTSourceUpdater {
|
|
|
2441
2585
|
catch { }
|
|
2442
2586
|
}
|
|
2443
2587
|
if (this.replaceChainTailOnly(call, newSem, newSelector)) {
|
|
2444
|
-
|
|
2588
|
+
this._persistChanges(sourceFile);
|
|
2445
2589
|
return {
|
|
2446
2590
|
success: true,
|
|
2447
2591
|
reason: `fragment scan fixed .${callSem.method}() @ line ${call.getStartLineNumber()}`,
|
|
@@ -2618,7 +2762,7 @@ class ASTSourceUpdater {
|
|
|
2618
2762
|
const propName = firstArg.getName();
|
|
2619
2763
|
const result = this.updateObjectProperty(sourceFile, objName, propName, oldSem.primaryArg, newSem.primaryArg);
|
|
2620
2764
|
if (result !== null) {
|
|
2621
|
-
|
|
2765
|
+
this._persistChanges(sourceFile);
|
|
2622
2766
|
return {
|
|
2623
2767
|
success: true,
|
|
2624
2768
|
reason: `'${objName}.${propName}' updated`,
|
|
@@ -3032,7 +3176,7 @@ class ASTSourceUpdater {
|
|
|
3032
3176
|
}
|
|
3033
3177
|
const lineUpdated = node.getStartLineNumber() - 1;
|
|
3034
3178
|
node.replaceWithText(patchResult.patchedTemplate);
|
|
3035
|
-
|
|
3179
|
+
this._persistChanges(sourceFile);
|
|
3036
3180
|
if (!this.validateFileSyntax(sourceFile)) {
|
|
3037
3181
|
return {
|
|
3038
3182
|
success: false,
|
|
@@ -3068,7 +3212,7 @@ class ASTSourceUpdater {
|
|
|
3068
3212
|
const result = InitializerUpdater_1.InitializerUpdater.apply(init, sourceFile, newSpanVal);
|
|
3069
3213
|
if (!result)
|
|
3070
3214
|
return null;
|
|
3071
|
-
|
|
3215
|
+
this._persistChanges(sourceFile);
|
|
3072
3216
|
return {
|
|
3073
3217
|
success: true,
|
|
3074
3218
|
reason: `span literal updated via trace`,
|
|
@@ -3132,7 +3276,7 @@ class ASTSourceUpdater {
|
|
|
3132
3276
|
}
|
|
3133
3277
|
const lineUpdated = fnAnalysis.templateNode.getStartLineNumber() - 1;
|
|
3134
3278
|
fnAnalysis.templateNode.replaceWithText(patchResult.patchedTemplate);
|
|
3135
|
-
fnAnalysis.functionFile
|
|
3279
|
+
this._persistChanges(fnAnalysis.functionFile);
|
|
3136
3280
|
if (!this.validateFileSyntax(fnAnalysis.functionFile)) {
|
|
3137
3281
|
return {
|
|
3138
3282
|
success: false,
|
|
@@ -17,7 +17,11 @@ export interface DnaSummary {
|
|
|
17
17
|
}
|
|
18
18
|
export interface AuditorBlock {
|
|
19
19
|
verdict: AuditVerdictValue;
|
|
20
|
-
|
|
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
|
+
confidence?: number;
|
|
21
25
|
reason: string;
|
|
22
26
|
inversionType: AuditVerdict["inversionType"];
|
|
23
27
|
rawConfidence?: number;
|
|
@@ -37,12 +41,31 @@ interface HealEventBase {
|
|
|
37
41
|
}
|
|
38
42
|
export interface HealedEvent extends HealEventBase {
|
|
39
43
|
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
|
+
dryRun?: boolean;
|
|
40
51
|
oldSelector: string;
|
|
41
52
|
newSelector: string;
|
|
53
|
+
/** First removed line from the native git diff (backward-compat fallback). */
|
|
42
54
|
oldCodeLine: string;
|
|
55
|
+
/** First added line from the native git diff (backward-compat fallback). */
|
|
43
56
|
newCodeLine: string;
|
|
44
57
|
newLineNumber: number;
|
|
45
|
-
|
|
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
|
+
gitUnifiedDiff?: string;
|
|
67
|
+
/** AI-reported confidence 0-100. `undefined` when the model omitted it. */
|
|
68
|
+
aiConfidence?: number;
|
|
46
69
|
aiExplanation: string;
|
|
47
70
|
aiAlternatives: string[];
|
|
48
71
|
reasoningSteps: string[];
|
|
@@ -67,6 +90,16 @@ export interface FailedEvent extends HealEventBase {
|
|
|
67
90
|
aiConfidence?: number;
|
|
68
91
|
aiExplanation?: string;
|
|
69
92
|
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
|
+
failureScreenshotPng?: string;
|
|
70
103
|
}
|
|
71
104
|
export interface ProtectedEvent extends HealEventBase {
|
|
72
105
|
kind: "PROTECTED";
|
|
@@ -75,7 +108,8 @@ export interface ProtectedEvent extends HealEventBase {
|
|
|
75
108
|
reason: string;
|
|
76
109
|
safetyLevel: string;
|
|
77
110
|
auditor: AuditorBlock | null;
|
|
78
|
-
|
|
111
|
+
/** AI-reported confidence 0-100. `undefined` when the model omitted it. */
|
|
112
|
+
aiConfidence?: number;
|
|
79
113
|
aiExplanation: string;
|
|
80
114
|
dnaBefore: DnaSummary | null;
|
|
81
115
|
dnaAfter: DnaSummary | null;
|
|
@@ -97,6 +131,31 @@ export declare class HealReportService {
|
|
|
97
131
|
getHealedEvents(): HealedEvent[];
|
|
98
132
|
/** All PROTECTED events (SafetyGuard blocks) accumulated in this session. */
|
|
99
133
|
getProtectedEvents(): ProtectedEvent[];
|
|
134
|
+
/** All FAILED events accumulated in this session. */
|
|
135
|
+
getFailedEvents(): FailedEvent[];
|
|
136
|
+
/**
|
|
137
|
+
* Inject a Playwright failure screenshot (Base64 PNG, no data: prefix)
|
|
138
|
+
* onto the FAILED event(s) matching the supplied identity hints. Used
|
|
139
|
+
* by `SelaReporter` to enrich engine-emitted FAILED events with the
|
|
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).
|
|
153
|
+
*/
|
|
154
|
+
attachFailureScreenshot(match: {
|
|
155
|
+
sourceFile?: string;
|
|
156
|
+
testTitle?: string;
|
|
157
|
+
stableId?: string;
|
|
158
|
+
}, base64Png: string): number;
|
|
100
159
|
/**
|
|
101
160
|
* Write `sela-report.html` (+ `sela-report.json` debug dump) to cwd.
|
|
102
161
|
* Returns the absolute path of the HTML report, or null if nothing to write.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAMlD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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"}
|