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
|
@@ -41,7 +41,7 @@ const ASTSourceUpdater_1 = require("./ASTSourceUpdater");
|
|
|
41
41
|
const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
42
42
|
const WorkspaceSnapshotService_1 = require("./WorkspaceSnapshotService");
|
|
43
43
|
const SelaError_1 = require("../errors/SelaError");
|
|
44
|
-
// Centralised write gate
|
|
44
|
+
// Centralised write gate - every disk-mutating `fs.writeFileSync` call in
|
|
45
45
|
// this module routes through `persistFile` so the SELA_DRY_RUN bypass is
|
|
46
46
|
// enforced in one place, and the Clean-Room WorkspaceSnapshotService
|
|
47
47
|
// records contentBefore/contentAfter for isolated in-memory diffing.
|
|
@@ -310,19 +310,19 @@ function updateTemplateLiteralPreservingVars(line, oldSelector, newSelector, dom
|
|
|
310
310
|
// GIT HELPER (used by branch strategy and autoCommit)
|
|
311
311
|
//
|
|
312
312
|
// Design goals:
|
|
313
|
-
// • Lean
|
|
314
|
-
// • Commit-based
|
|
313
|
+
// • Lean - no stash, no branch-switching, no complex state management.
|
|
314
|
+
// • Commit-based - write first (inPlace), then commit. If git fails,
|
|
315
315
|
// the fix is still on disk (silent inPlace fallback).
|
|
316
|
-
// • Detached HEAD aware
|
|
316
|
+
// • Detached HEAD aware - skips silently in CI environments that check
|
|
317
317
|
// out a specific SHA without a branch ref.
|
|
318
318
|
// ─────────────────────────────────────────────────────────────────
|
|
319
319
|
function runGitOperations(absoluteFilePath, createBranch, relativeFilePath) {
|
|
320
320
|
const filename = path.basename(relativeFilePath);
|
|
321
|
-
// Dry-run gate
|
|
321
|
+
// Dry-run gate - suppress branch creation, `git add`, and auto-commit.
|
|
322
322
|
// Mirrors the persistFile() skip so a dry run is fully observable in CI
|
|
323
323
|
// without ever producing a commit.
|
|
324
324
|
if (DryRunGuard_1.DryRunGuard.active()) {
|
|
325
|
-
console.log(`[SourceUpdater] 🌵 Dry Run
|
|
325
|
+
console.log(`[SourceUpdater] 🌵 Dry Run - skip git operations for ${filename}`);
|
|
326
326
|
return;
|
|
327
327
|
}
|
|
328
328
|
try {
|
|
@@ -331,7 +331,7 @@ function runGitOperations(absoluteFilePath, createBranch, relativeFilePath) {
|
|
|
331
331
|
(0, child_process_1.execSync)("git symbolic-ref HEAD", { stdio: "pipe" });
|
|
332
332
|
}
|
|
333
333
|
catch {
|
|
334
|
-
console.warn(`[SourceUpdater] ⚠️ Detached HEAD
|
|
334
|
+
console.warn(`[SourceUpdater] ⚠️ Detached HEAD - git operations skipped (fix is saved to disk)`);
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
337
|
if (createBranch) {
|
|
@@ -346,7 +346,7 @@ function runGitOperations(absoluteFilePath, createBranch, relativeFilePath) {
|
|
|
346
346
|
console.log(`[SourceUpdater] ✅ Auto-committed fix in: ${filename}`);
|
|
347
347
|
}
|
|
348
348
|
catch (err) {
|
|
349
|
-
console.warn(`[SourceUpdater] ⚠️ Git operation failed: ${err.message}
|
|
349
|
+
console.warn(`[SourceUpdater] ⚠️ Git operation failed: ${err.message} - fix written to disk (inPlace fallback)`);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
class SourceUpdater {
|
|
@@ -376,7 +376,7 @@ class SourceUpdater {
|
|
|
376
376
|
return result;
|
|
377
377
|
}
|
|
378
378
|
// ─────────────────────────────────────────────────────────────
|
|
379
|
-
// commentOnly helper
|
|
379
|
+
// commentOnly helper - inserts a TODO comment above the failing
|
|
380
380
|
// call site and returns success:false so the engine knows the
|
|
381
381
|
// source was NOT rewritten (no retry suppression needed).
|
|
382
382
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -402,12 +402,12 @@ class SourceUpdater {
|
|
|
402
402
|
catch (err) {
|
|
403
403
|
return {
|
|
404
404
|
success: false,
|
|
405
|
-
reason: `COMMENT_ONLY: write failed
|
|
405
|
+
reason: `COMMENT_ONLY: write failed - ${err.message}`,
|
|
406
406
|
};
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
// ─────────────────────────────────────────────────────────────
|
|
410
|
-
// CORE UPDATE
|
|
410
|
+
// CORE UPDATE - Layered Healing (AST primary, Regex fallback)
|
|
411
411
|
// Called by update(); never call directly from outside the class.
|
|
412
412
|
// ─────────────────────────────────────────────────────────────
|
|
413
413
|
static _coreUpdate(caller, oldSelector, newSelector, domContext, dnaAttrHint, fullSelectorContext, aiSegments, contentChange, smartChainSegments, originalChainHint) {
|
|
@@ -442,7 +442,7 @@ class SourceUpdater {
|
|
|
442
442
|
if (astResult.success) {
|
|
443
443
|
console.log(`[SourceUpdater] ✅ AST Strategy ${astResult.strategy} SUCCESS` +
|
|
444
444
|
(astResult.lineUpdated !== undefined
|
|
445
|
-
? `
|
|
445
|
+
? ` - line ${astResult.lineUpdated + 1}`
|
|
446
446
|
: ""));
|
|
447
447
|
return {
|
|
448
448
|
success: true,
|
|
@@ -458,18 +458,19 @@ class SourceUpdater {
|
|
|
458
458
|
// PROPERTY_ACCESS / TEMPLATE_LITERAL) but could not safely resolve the new
|
|
459
459
|
// value. Regex cannot do better and will corrupt the definition site.
|
|
460
460
|
if (astResult.strategy === "semantic-guard") {
|
|
461
|
-
console.warn(`[SourceUpdater] 🛑 Regex blocked
|
|
461
|
+
console.warn(`[SourceUpdater] 🛑 Regex blocked - semantic guard fired. ` +
|
|
462
462
|
`Source unchanged. Reason: ${astResult.reason}`);
|
|
463
463
|
return { success: false, reason: astResult.reason };
|
|
464
464
|
}
|
|
465
465
|
console.log(`[SourceUpdater] ⚠️ AST engine exhausted (${astResult.reason}), falling back to Regex`);
|
|
466
466
|
}
|
|
467
467
|
catch (astError) {
|
|
468
|
-
// Developer pinned the failing statement with `// sela-fail-fast`
|
|
468
|
+
// Developer pinned the failing statement with `// sela-fail-fast` -
|
|
469
469
|
// propagate the directive instead of swallowing into the Regex
|
|
470
470
|
// fallback chain (which would defeat the whole point of the gate).
|
|
471
471
|
if ((0, SelaError_1.isSelaErrorFrom)(astError, "ASTUpdater") &&
|
|
472
|
-
astError.code ===
|
|
472
|
+
astError.code ===
|
|
473
|
+
SelaError_1.AST_UPDATER_CODES.HEAL_DISABLED_BY_DIRECTIVE) {
|
|
473
474
|
throw astError;
|
|
474
475
|
}
|
|
475
476
|
console.warn(`[SourceUpdater] ⚠️ AST engine error: ${astError.message}. Falling back to Regex`);
|
|
@@ -479,7 +480,7 @@ class SourceUpdater {
|
|
|
479
480
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
480
481
|
const lines = raw.split(/\r?\n/);
|
|
481
482
|
if (caller.line <= 0) {
|
|
482
|
-
console.log(`[SourceUpdater] ⚠️ line ${caller.line} invalid
|
|
483
|
+
console.log(`[SourceUpdater] ⚠️ line ${caller.line} invalid - running Regex Strategy G only`);
|
|
483
484
|
if (contentChange) {
|
|
484
485
|
const assertResult = SourceUpdater.strategyAssertionHealing(lines, 1, contentChange, true);
|
|
485
486
|
if (assertResult.success) {
|
|
@@ -511,7 +512,7 @@ class SourceUpdater {
|
|
|
511
512
|
if (layeredResult.success) {
|
|
512
513
|
tryAssertionHealing();
|
|
513
514
|
persistFile(filePath, lines.join("\n"));
|
|
514
|
-
console.log(`[SourceUpdater] ✅ Strategy 0 (Layered Healing) SUCCESS
|
|
515
|
+
console.log(`[SourceUpdater] ✅ Strategy 0 (Layered Healing) SUCCESS - line ${layeredResult.lineUpdated + 1}`);
|
|
515
516
|
return layeredResult;
|
|
516
517
|
}
|
|
517
518
|
}
|
|
@@ -587,7 +588,7 @@ class SourceUpdater {
|
|
|
587
588
|
};
|
|
588
589
|
}
|
|
589
590
|
// ─────────────────────────────────────────────────────────────
|
|
590
|
-
// STRATEGY F (Regex)
|
|
591
|
+
// STRATEGY F (Regex) - Full Chain Collapsing
|
|
591
592
|
// ─────────────────────────────────────────────────────────────
|
|
592
593
|
static strategyChainCollapse_Regex(lines, callerLine, oldSelector, newSelector) {
|
|
593
594
|
console.log(`[Strategy-F] ⛓️ Starting Regex Chain Collapse...`);
|
|
@@ -638,7 +639,7 @@ class SourceUpdater {
|
|
|
638
639
|
return { success: false, reason: "chain detected but could not replace" };
|
|
639
640
|
}
|
|
640
641
|
// ─────────────────────────────────────────────────────────────
|
|
641
|
-
// STRATEGY G (Regex)
|
|
642
|
+
// STRATEGY G (Regex) - Assertion Healing
|
|
642
643
|
// ─────────────────────────────────────────────────────────────
|
|
643
644
|
static strategyAssertionHealing(lines, callerLine, contentChange, wholeFile = false) {
|
|
644
645
|
const { oldText, newText } = contentChange;
|
|
@@ -693,7 +694,7 @@ class SourceUpdater {
|
|
|
693
694
|
return { success: false, reason: "no assertions to heal" };
|
|
694
695
|
}
|
|
695
696
|
// ─────────────────────────────────────────────────────────────
|
|
696
|
-
// STRATEGY 0
|
|
697
|
+
// STRATEGY 0 - Layered Healing (Upstream Variable Tracing)
|
|
697
698
|
// ─────────────────────────────────────────────────────────────
|
|
698
699
|
static strategyLayeredHealing(lines, callerLine, oldElementSelector, aiSegments, fullSelectorContext) {
|
|
699
700
|
console.log(`[SourceUpdater] 🧠 Strategy 0: Starting Layered Healing...`);
|
|
@@ -746,7 +747,7 @@ class SourceUpdater {
|
|
|
746
747
|
return null;
|
|
747
748
|
}
|
|
748
749
|
// ─────────────────────────────────────────────────────────────
|
|
749
|
-
// STRATEGY 1
|
|
750
|
+
// STRATEGY 1 - Chain Collapse (leaf-only precision replacement)
|
|
750
751
|
// ─────────────────────────────────────────────────────────────
|
|
751
752
|
static strategyChainCollapse(lines, callerLine, oldSelector, newSelector) {
|
|
752
753
|
const oldLeaf = oldSelector
|
|
@@ -788,7 +789,7 @@ class SourceUpdater {
|
|
|
788
789
|
};
|
|
789
790
|
}
|
|
790
791
|
// ─────────────────────────────────────────────────────────────
|
|
791
|
-
// STRATEGY 2
|
|
792
|
+
// STRATEGY 2 - Direct Literal
|
|
792
793
|
// ─────────────────────────────────────────────────────────────
|
|
793
794
|
static strategyDirectLiteral(lines, callerLine, oldSelector, newSelector) {
|
|
794
795
|
const windowSize = 10;
|
|
@@ -821,7 +822,7 @@ class SourceUpdater {
|
|
|
821
822
|
return { success: false, reason: "no direct literal" };
|
|
822
823
|
}
|
|
823
824
|
// ─────────────────────────────────────────────────────────────
|
|
824
|
-
// STRATEGY 3
|
|
825
|
+
// STRATEGY 3 - Upstream Variable/Const
|
|
825
826
|
// ─────────────────────────────────────────────────────────────
|
|
826
827
|
static strategyUpstreamVariable(lines, callerLine, oldSelector, newSelector) {
|
|
827
828
|
const callBlock = lines
|
|
@@ -858,7 +859,7 @@ class SourceUpdater {
|
|
|
858
859
|
return { success: false, reason: "no upstream variable" };
|
|
859
860
|
}
|
|
860
861
|
// ─────────────────────────────────────────────────────────────
|
|
861
|
-
// STRATEGY 4
|
|
862
|
+
// STRATEGY 4 - Chained Locator Dedup
|
|
862
863
|
// ─────────────────────────────────────────────────────────────
|
|
863
864
|
static strategyChainedLocator(lines, callerLine, oldSelector, newSelector) {
|
|
864
865
|
const blockStart = Math.max(0, callerLine - 10);
|
|
@@ -885,7 +886,7 @@ class SourceUpdater {
|
|
|
885
886
|
return { success: false, reason: "dedup found but literal not replaced" };
|
|
886
887
|
}
|
|
887
888
|
// ─────────────────────────────────────────────────────────────
|
|
888
|
-
// STRATEGY 5
|
|
889
|
+
// STRATEGY 5 - Function/Template with Variable Preservation
|
|
889
890
|
// ─────────────────────────────────────────────────────────────
|
|
890
891
|
static strategyFunctionReturn(lines, callerLine, oldSelector, newSelector, domContext, dnaAttrHint) {
|
|
891
892
|
const callBlock = lines
|
|
@@ -970,7 +971,7 @@ class SourceUpdater {
|
|
|
970
971
|
return { success: false, reason: "function strategy exhausted" };
|
|
971
972
|
}
|
|
972
973
|
// ─────────────────────────────────────────────────────────────
|
|
973
|
-
// STRATEGY 6
|
|
974
|
+
// STRATEGY 6 - Global Scan
|
|
974
975
|
// ─────────────────────────────────────────────────────────────
|
|
975
976
|
static strategyGlobalScan(lines, oldSelector, newSelector) {
|
|
976
977
|
const lastOldPart = oldSelector.split(">>").pop().trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplateDiffService.d.ts","sourceRoot":"","sources":["../../src/services/TemplateDiffService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,kBAAkB,EAAc,MAAM,UAAU,CAAC;AAMhE,MAAM,MAAM,eAAe,GACvB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,mFAAmF;IACnF,eAAe,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AA6BN,qBAAa,mBAAmB;IAW9B,MAAM,CAAC,YAAY,CACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,eAAe;
|
|
1
|
+
{"version":3,"file":"TemplateDiffService.d.ts","sourceRoot":"","sources":["../../src/services/TemplateDiffService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,kBAAkB,EAAc,MAAM,UAAU,CAAC;AAMhE,MAAM,MAAM,eAAe,GACvB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,mFAAmF;IACnF,eAAe,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AA6BN,qBAAa,mBAAmB;IAW9B,MAAM,CAAC,YAAY,CACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,eAAe;IAiHlB,MAAM,CAAC,gBAAgB,CACrB,YAAY,EAAE,kBAAkB,EAChC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,eAAe;IAoFlB,MAAM,CAAC,qBAAqB,CAC1B,YAAY,EAAE,kBAAkB,EAChC,QAAQ,EAAE,MAAM,GACf,MAAM,EAAE,GAAG,IAAI;CASnB;AAgJD,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD"}
|
|
@@ -5,13 +5,13 @@ exports.escapeForTemplate = escapeForTemplate;
|
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════════
|
|
6
6
|
// TemplateDiffService
|
|
7
7
|
//
|
|
8
|
-
// T-8: String-based API
|
|
8
|
+
// T-8: String-based API - diffAndPatch()
|
|
9
9
|
// Given a template source like `input[data-role="${role}_legacy"]`,
|
|
10
10
|
// its resolved old string, and an AI-proposed new string, compute
|
|
11
11
|
// the patched template that preserves ${...} spans while updating
|
|
12
12
|
// only the changed static segments.
|
|
13
13
|
//
|
|
14
|
-
// T-8.1: Node-based API
|
|
14
|
+
// T-8.1: Node-based API - computeNodePatch()
|
|
15
15
|
// Wraps diffAndPatch() using the TemplateExpression AST node as the
|
|
16
16
|
// source of truth (avoids text-parsing ambiguity). Returns the full
|
|
17
17
|
// patched template text for use with templateNode.replaceWithText().
|
|
@@ -34,7 +34,7 @@ class TemplateDiffService {
|
|
|
34
34
|
const exprSegs = segments
|
|
35
35
|
.filter((s) => s.type === "expr")
|
|
36
36
|
.map((s) => s.text);
|
|
37
|
-
// No spans
|
|
37
|
+
// No spans - the "template" is just a plain string.
|
|
38
38
|
if (exprSegs.length === 0) {
|
|
39
39
|
if (resolvedOld !== resolvedNew) {
|
|
40
40
|
return {
|
|
@@ -43,18 +43,21 @@ class TemplateDiffService {
|
|
|
43
43
|
changedSegmentIndices: [0],
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "resolvedOld === resolvedNew - no change needed",
|
|
49
|
+
};
|
|
47
50
|
}
|
|
48
51
|
// Extract span values from resolvedOld using static segments as anchors.
|
|
49
52
|
const spanValues = extractSpanValues(staticSegs, resolvedOld);
|
|
50
53
|
if (!spanValues) {
|
|
51
54
|
return {
|
|
52
55
|
ok: false,
|
|
53
|
-
reason: "template static segments don't align with resolvedOld
|
|
56
|
+
reason: "template static segments don't align with resolvedOld - possible desync. " +
|
|
54
57
|
`template="${templateText}", resolvedOld="${resolvedOld}"`,
|
|
55
58
|
};
|
|
56
59
|
}
|
|
57
|
-
// Verify reconstruction
|
|
60
|
+
// Verify reconstruction - extractSpanValues already guarantees this
|
|
58
61
|
// implicitly, but an explicit check catches any edge-case bugs early.
|
|
59
62
|
{
|
|
60
63
|
let check = "";
|
|
@@ -76,13 +79,13 @@ class TemplateDiffService {
|
|
|
76
79
|
return {
|
|
77
80
|
ok: false,
|
|
78
81
|
reason: `dynamic span value(s) [${spanValues.map((v) => `"${v}"`).join(", ")}] ` +
|
|
79
|
-
`not found in resolvedNew="${resolvedNew}"
|
|
82
|
+
`not found in resolvedNew="${resolvedNew}" - structural change, manual review required`,
|
|
80
83
|
};
|
|
81
84
|
}
|
|
82
85
|
if (newStaticSegs.length !== staticSegs.length) {
|
|
83
86
|
return {
|
|
84
87
|
ok: false,
|
|
85
|
-
reason: "static segment count mismatch after alignment
|
|
88
|
+
reason: "static segment count mismatch after alignment - structural change",
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
91
|
// Diff: find which static segments changed.
|
|
@@ -94,14 +97,14 @@ class TemplateDiffService {
|
|
|
94
97
|
if (changedIndices.length === 0) {
|
|
95
98
|
return {
|
|
96
99
|
ok: false,
|
|
97
|
-
reason: "no static segment changed
|
|
100
|
+
reason: "no static segment changed - the difference is in a dynamic span. " +
|
|
98
101
|
"Update the argument passed to the function instead.",
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
104
|
if (changedIndices.length > 1) {
|
|
102
105
|
return {
|
|
103
106
|
ok: false,
|
|
104
|
-
reason: `${changedIndices.length} static segments changed simultaneously
|
|
107
|
+
reason: `${changedIndices.length} static segments changed simultaneously - ` +
|
|
105
108
|
"ambiguous, manual review required",
|
|
106
109
|
};
|
|
107
110
|
}
|
|
@@ -119,7 +122,7 @@ class TemplateDiffService {
|
|
|
119
122
|
//
|
|
120
123
|
// Uses the TemplateExpression AST node as the authoritative source
|
|
121
124
|
// of static/expr segments (avoids text-parsing edge cases).
|
|
122
|
-
// Returns `patchedText`
|
|
125
|
+
// Returns `patchedText` - pass directly to templateNode.replaceWithText().
|
|
123
126
|
// ─────────────────────────────────────────────────────────────────
|
|
124
127
|
static computeNodePatch(templateNode, resolvedOld, resolvedNew) {
|
|
125
128
|
// Extract static segments and expr texts directly from the AST.
|
|
@@ -172,13 +175,13 @@ class TemplateDiffService {
|
|
|
172
175
|
if (changedIndices.length === 0) {
|
|
173
176
|
return {
|
|
174
177
|
ok: false,
|
|
175
|
-
reason: "difference is in dynamic span
|
|
178
|
+
reason: "difference is in dynamic span - update call-site argument",
|
|
176
179
|
};
|
|
177
180
|
}
|
|
178
181
|
if (changedIndices.length > 1) {
|
|
179
182
|
return {
|
|
180
183
|
ok: false,
|
|
181
|
-
reason: `${changedIndices.length} static segments changed
|
|
184
|
+
reason: `${changedIndices.length} static segments changed - ambiguous`,
|
|
182
185
|
};
|
|
183
186
|
}
|
|
184
187
|
const patchedStatics = [...staticSegs];
|
|
@@ -238,7 +241,7 @@ function parseTemplateSource(text) {
|
|
|
238
241
|
*
|
|
239
242
|
* Algorithm: iterate over all static segments in order. After consuming
|
|
240
243
|
* each static segment, find the next static as the delimiter for the
|
|
241
|
-
* span value
|
|
244
|
+
* span value - then advance pos to the start of that next static so
|
|
242
245
|
* the following iteration can consume it. This ensures pos is always
|
|
243
246
|
* consistent at the start of each static segment.
|
|
244
247
|
*
|
|
@@ -265,7 +268,7 @@ function extractSpanValues(staticSegments, resolved) {
|
|
|
265
268
|
break;
|
|
266
269
|
}
|
|
267
270
|
if (nextStatic === "") {
|
|
268
|
-
// Empty non-trailing static: adjacent spans
|
|
271
|
+
// Empty non-trailing static: adjacent spans - boundary is ambiguous.
|
|
269
272
|
return null;
|
|
270
273
|
}
|
|
271
274
|
const delimIdx = resolved.indexOf(nextStatic, pos);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export interface FileSnapshot {
|
|
2
2
|
/** Absolute, normalised path of the tracked file. */
|
|
3
3
|
absolutePath: string;
|
|
4
|
-
/** Project-relative path (posix slashes)
|
|
4
|
+
/** Project-relative path (posix slashes) - for diff headers + PR commits. */
|
|
5
5
|
relativePath: string;
|
|
6
6
|
/**
|
|
7
7
|
* Disk content captured BEFORE Sela's first write to this file.
|
|
8
|
-
* May contain the developer's uncommitted edits
|
|
8
|
+
* May contain the developer's uncommitted edits - that's the point.
|
|
9
9
|
*/
|
|
10
10
|
contentBefore: string;
|
|
11
11
|
/** Disk content captured AFTER Sela's latest write. `null` until first postWrite(). */
|
|
12
12
|
contentAfter: string | null;
|
|
13
|
-
/** Content of the file at `HEAD` (last committed)
|
|
13
|
+
/** Content of the file at `HEAD` (last committed) - `null` when untracked / no git. */
|
|
14
14
|
headContent: string | null;
|
|
15
15
|
/** True when `contentBefore !== headContent`, i.e. developer had dirty edits. */
|
|
16
16
|
hadUncommittedChanges: boolean;
|
|
@@ -25,11 +25,11 @@ export declare class WorkspaceSnapshotService {
|
|
|
25
25
|
setCwd(cwd: string): void;
|
|
26
26
|
/**
|
|
27
27
|
* Record the pre-mutation state of `filePath` if not already tracked.
|
|
28
|
-
* Idempotent
|
|
28
|
+
* Idempotent - only the FIRST call per file matters.
|
|
29
29
|
*/
|
|
30
30
|
preWrite(filePath: string): void;
|
|
31
31
|
/**
|
|
32
|
-
* Refresh the post-mutation state. Safe to call multiple times
|
|
32
|
+
* Refresh the post-mutation state. Safe to call multiple times - each
|
|
33
33
|
* call overwrites `contentAfter` with the latest disk state.
|
|
34
34
|
*/
|
|
35
35
|
postWrite(filePath: string): void;
|
|
@@ -45,7 +45,7 @@ export declare class WorkspaceSnapshotService {
|
|
|
45
45
|
/** All snapshots with a real mutation recorded. */
|
|
46
46
|
getMutatedSnapshots(): FileSnapshot[];
|
|
47
47
|
/**
|
|
48
|
-
* Rewind every tracked file on disk to `contentBefore`
|
|
48
|
+
* Rewind every tracked file on disk to `contentBefore` - restoring the
|
|
49
49
|
* developer's pre-Sela workspace state byte-for-byte.
|
|
50
50
|
*/
|
|
51
51
|
restoreOriginal(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceSnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/WorkspaceSnapshotService.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uFAAuF;IACvF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iFAAiF;IACjF,qBAAqB,EAAE,OAAO,CAAC;IAC/B,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,GAAG,CAAS;gBAER,GAAG,GAAE,MAAsB;IAIvC,sEAAsE;IACtE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAqBhC;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"WorkspaceSnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/WorkspaceSnapshotService.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uFAAuF;IACvF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iFAAiF;IACjF,qBAAqB,EAAE,OAAO,CAAC;IAC/B,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,GAAG,CAAS;gBAER,GAAG,GAAE,MAAsB;IAIvC,sEAAsE;IACtE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAqBhC;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOjC,0EAA0E;IAC1E,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAIlD,+CAA+C;IAC/C,eAAe,IAAI,YAAY,EAAE;IAIjC;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAMpC,mDAAmD;IACnD,mBAAmB,IAAI,YAAY,EAAE;IAMrC;;;OAGG;IACH,eAAe,IAAI,IAAI;IAMvB;;;;OAIG;IACH,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAOhD,OAAO,CAAC,cAAc;IAUtB;;;;OAIG;IACH,cAAc,IAAI,IAAI;IAOtB,KAAK,IAAI,IAAI;IAQb,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,eAAe;CAcxB;AAED,eAAO,MAAM,uBAAuB,0BAAiC,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/services/WorkspaceSnapshotService.ts
|
|
3
3
|
//
|
|
4
|
-
// Clean-Room Architecture
|
|
4
|
+
// Clean-Room Architecture - file 1 of 2.
|
|
5
5
|
//
|
|
6
6
|
// Tracks the EXACT content of every file Sela mutates during a session, so:
|
|
7
7
|
// • The HTML report diff can be generated from an isolated in-memory diff
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
// byte-for-byte after a PR is opened on an isolated branch.
|
|
11
11
|
//
|
|
12
12
|
// Lifecycle:
|
|
13
|
-
// preWrite(path)
|
|
13
|
+
// preWrite(path) - first call per file records `contentBefore` (disk
|
|
14
14
|
// snapshot right before Sela touches it) + HEAD blob.
|
|
15
|
-
// postWrite(path)
|
|
16
|
-
// restoreOriginal()
|
|
17
|
-
// restoreMutated()
|
|
18
|
-
// clear()
|
|
15
|
+
// postWrite(path) - refreshes `contentAfter` (latest disk state).
|
|
16
|
+
// restoreOriginal()- writes contentBefore back to every tracked file.
|
|
17
|
+
// restoreMutated() - writes contentAfter back to every tracked file.
|
|
18
|
+
// clear() - empties the ledger.
|
|
19
19
|
//
|
|
20
20
|
// Singleton `sharedWorkspaceSnapshot` is the canonical instance the
|
|
21
21
|
// SourceUpdater / ASTSourceUpdater write hooks talk to.
|
|
@@ -69,7 +69,7 @@ class WorkspaceSnapshotService {
|
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Record the pre-mutation state of `filePath` if not already tracked.
|
|
72
|
-
* Idempotent
|
|
72
|
+
* Idempotent - only the FIRST call per file matters.
|
|
73
73
|
*/
|
|
74
74
|
preWrite(filePath) {
|
|
75
75
|
const abs = this.normalise(filePath);
|
|
@@ -90,7 +90,7 @@ class WorkspaceSnapshotService {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
* Refresh the post-mutation state. Safe to call multiple times
|
|
93
|
+
* Refresh the post-mutation state. Safe to call multiple times - each
|
|
94
94
|
* call overwrites `contentAfter` with the latest disk state.
|
|
95
95
|
*/
|
|
96
96
|
postWrite(filePath) {
|
|
@@ -98,9 +98,7 @@ class WorkspaceSnapshotService {
|
|
|
98
98
|
const snap = this.snapshots.get(abs);
|
|
99
99
|
if (!snap)
|
|
100
100
|
return;
|
|
101
|
-
snap.contentAfter = fs.existsSync(abs)
|
|
102
|
-
? fs.readFileSync(abs, "utf8")
|
|
103
|
-
: "";
|
|
101
|
+
snap.contentAfter = fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : "";
|
|
104
102
|
}
|
|
105
103
|
/** Lookup by absolute or relative path. Returns `null` when untracked. */
|
|
106
104
|
getSnapshot(filePath) {
|
|
@@ -125,7 +123,7 @@ class WorkspaceSnapshotService {
|
|
|
125
123
|
return this.getAllSnapshots().filter((s) => s.contentAfter !== null && s.contentAfter !== s.contentBefore);
|
|
126
124
|
}
|
|
127
125
|
/**
|
|
128
|
-
* Rewind every tracked file on disk to `contentBefore`
|
|
126
|
+
* Rewind every tracked file on disk to `contentBefore` - restoring the
|
|
129
127
|
* developer's pre-Sela workspace state byte-for-byte.
|
|
130
128
|
*/
|
|
131
129
|
restoreOriginal() {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export interface AncestorNode {
|
|
|
2
2
|
tag: string;
|
|
3
3
|
id: string | null;
|
|
4
4
|
classes: string[];
|
|
5
|
-
/** "{tag}#{id}[{classes_sorted_asc}]"
|
|
5
|
+
/** "{tag}#{id}[{classes_sorted_asc}]" - stable fingerprint for drift comparison */
|
|
6
6
|
fingerprint: string;
|
|
7
7
|
}
|
|
8
8
|
export type GhostCondition = "G1_DISPLAY_NONE" | "G2_VISIBILITY_HIDDEN" | "G3_OPACITY_ZERO" | "G4_POINTER_EVENTS_NONE" | "G5_ZERO_AREA" | "G6_CONTENT_VISIBILITY_HIDDEN";
|
|
@@ -32,13 +32,13 @@ export declare function extractChangeBlocks(before: string, after: string): Chan
|
|
|
32
32
|
export interface ApplyBlocksResult {
|
|
33
33
|
ok: boolean;
|
|
34
34
|
result?: string;
|
|
35
|
-
/** Human-readable failure reason
|
|
35
|
+
/** Human-readable failure reason - surfaced in the PR-automation log. */
|
|
36
36
|
reason?: string;
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Replay a list of change blocks against a fresh base buffer (typically
|
|
40
40
|
* the file's `HEAD` blob). Each block is applied via UNIQUE substring
|
|
41
|
-
* replacement
|
|
41
|
+
* replacement - when the old block is missing or ambiguous in the base,
|
|
42
42
|
* we fail loudly rather than silently mutating the wrong spot.
|
|
43
43
|
*/
|
|
44
44
|
export declare function applyChangeBlocks(base: string, blocks: ChangeBlock[]): ApplyBlocksResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IsolatedDiff.d.ts","sourceRoot":"","sources":["../../src/utils/IsolatedDiff.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,mBAAwB,GAC7B,MAAM,CAsBR;
|
|
1
|
+
{"version":3,"file":"IsolatedDiff.d.ts","sourceRoot":"","sources":["../../src/utils/IsolatedDiff.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,mBAAwB,GAC7B,MAAM,CAsBR;AA6PD,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,WAAW,EAAE,CAoDf;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EAAE,GACpB,iBAAiB,CAuDnB"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// src/utils/IsolatedDiff.ts
|
|
3
3
|
//
|
|
4
4
|
// Pure-JS unified-diff generator. No external `diff` package, no shell-out to
|
|
5
|
-
// `git diff`
|
|
5
|
+
// `git diff` - the result is guaranteed to be a function ONLY of the two
|
|
6
6
|
// in-memory buffers passed in, never of any global working-tree state.
|
|
7
7
|
//
|
|
8
8
|
// This is the foundation of the Clean-Room Architecture: the HTML report
|
|
@@ -47,7 +47,7 @@ function generateUnifiedDiff(before, after, opts = {}) {
|
|
|
47
47
|
return out.join("\n") + "\n";
|
|
48
48
|
}
|
|
49
49
|
// ────────────────────────────────────────────────────────────────────
|
|
50
|
-
// LINE SPLITTER
|
|
50
|
+
// LINE SPLITTER - preserves trailing newline / CRLF so we can emit the
|
|
51
51
|
// canonical `` marker when needed.
|
|
52
52
|
// ────────────────────────────────────────────────────────────────────
|
|
53
53
|
function splitKeepEol(text) {
|
|
@@ -200,7 +200,7 @@ function groupHunks(ops, ctx) {
|
|
|
200
200
|
// Anchor on the corresponding `b` position. For pure-deletions at
|
|
201
201
|
// the top of file `op.a === 0` ⇒ newStart should be 0 (special
|
|
202
202
|
// header form `+0,0`). For mid-file deletions use the running b
|
|
203
|
-
// cursor
|
|
203
|
+
// cursor - derive it from prior ops.
|
|
204
204
|
newStartIdx = computeBAnchor(ops, k);
|
|
205
205
|
}
|
|
206
206
|
const line = ctx.a[op.a];
|
|
@@ -296,7 +296,7 @@ function extractChangeBlocks(before, after) {
|
|
|
296
296
|
}
|
|
297
297
|
let anchorBefore = null;
|
|
298
298
|
if (oldLines.length === 0) {
|
|
299
|
-
// Pure addition
|
|
299
|
+
// Pure addition - find preceding unchanged anchor line.
|
|
300
300
|
for (let k = start - 1; k >= 0; k--) {
|
|
301
301
|
const op = ops[k];
|
|
302
302
|
if (op.kind === "eq") {
|
|
@@ -318,7 +318,7 @@ function extractChangeBlocks(before, after) {
|
|
|
318
318
|
/**
|
|
319
319
|
* Replay a list of change blocks against a fresh base buffer (typically
|
|
320
320
|
* the file's `HEAD` blob). Each block is applied via UNIQUE substring
|
|
321
|
-
* replacement
|
|
321
|
+
* replacement - when the old block is missing or ambiguous in the base,
|
|
322
322
|
* we fail loudly rather than silently mutating the wrong spot.
|
|
323
323
|
*/
|
|
324
324
|
function applyChangeBlocks(base, blocks) {
|
|
@@ -328,7 +328,7 @@ function applyChangeBlocks(base, blocks) {
|
|
|
328
328
|
// Pure addition.
|
|
329
329
|
if (block.anchorBefore === null) {
|
|
330
330
|
if (working.length === 0) {
|
|
331
|
-
// Empty file
|
|
331
|
+
// Empty file - prepend.
|
|
332
332
|
working = block.newBlock + working;
|
|
333
333
|
continue;
|
|
334
334
|
}
|
|
@@ -351,25 +351,28 @@ function applyChangeBlocks(base, blocks) {
|
|
|
351
351
|
};
|
|
352
352
|
}
|
|
353
353
|
const insertAt = idx + block.anchorBefore.length;
|
|
354
|
-
working =
|
|
354
|
+
working =
|
|
355
|
+
working.slice(0, insertAt) + block.newBlock + working.slice(insertAt);
|
|
355
356
|
continue;
|
|
356
357
|
}
|
|
357
358
|
const idx = working.indexOf(block.oldBlock);
|
|
358
359
|
if (idx === -1) {
|
|
359
360
|
return {
|
|
360
361
|
ok: false,
|
|
361
|
-
reason: `block not found in clean base
|
|
362
|
+
reason: `block not found in clean base - developer's dirty state likely overlaps Sela's mutation site: "${snippet(block.oldBlock)}"`,
|
|
362
363
|
};
|
|
363
364
|
}
|
|
364
365
|
const lastIdx = working.lastIndexOf(block.oldBlock);
|
|
365
366
|
if (idx !== lastIdx) {
|
|
366
367
|
return {
|
|
367
368
|
ok: false,
|
|
368
|
-
reason: `block matches multiple locations in clean base
|
|
369
|
+
reason: `block matches multiple locations in clean base - refusing to guess: "${snippet(block.oldBlock)}"`,
|
|
369
370
|
};
|
|
370
371
|
}
|
|
371
372
|
working =
|
|
372
|
-
working.slice(0, idx) +
|
|
373
|
+
working.slice(0, idx) +
|
|
374
|
+
block.newBlock +
|
|
375
|
+
working.slice(idx + block.oldBlock.length);
|
|
373
376
|
}
|
|
374
377
|
return { ok: true, result: working };
|
|
375
378
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sela-core",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "AI self-healing Playwright wrapper
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "AI self-healing Playwright wrapper - drop-in replacement for @playwright/test",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"exports": {
|