sela-core 1.0.6 → 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/README.md +1 -1
- 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 +3 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +23 -13
- 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 +26 -16
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +54 -22
- package/dist/engine/HealingRegistry.d.ts +1 -1
- package/dist/engine/HealingRegistry.js +3 -3
- package/dist/engine/SelaEngine.d.ts +13 -10
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +292 -101
- package/dist/errors/SelaError.d.ts +132 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +154 -0
- package/dist/fixtures/expectProxy.d.ts +1 -1
- package/dist/fixtures/expectProxy.d.ts.map +1 -1
- package/dist/fixtures/expectProxy.js +17 -10
- package/dist/fixtures/index.d.ts +7 -1
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +26 -11
- 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 +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/reporter/SelaReporter.d.ts +143 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +428 -0
- package/dist/services/ASTSourceUpdater.d.ts +20 -1
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +257 -89
- 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 +43 -1
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +108 -25
- package/dist/services/HealingAdvisory.js +1 -1
- 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/HealthReportService.d.ts +1 -1
- package/dist/services/HealthReportService.d.ts.map +1 -1
- package/dist/services/HealthReportService.js +25 -25
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +20 -1
- package/dist/services/IntentAuditor.d.ts +18 -1
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +50 -26
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +125 -0
- package/dist/services/LLMService.d.ts +18 -2
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +33 -23
- package/dist/services/PRAutomationService.d.ts +14 -1
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +287 -92
- 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 +30 -7
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +85 -116
- 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 +66 -34
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- package/package.json +71 -67
|
@@ -38,6 +38,23 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const child_process_1 = require("child_process");
|
|
40
40
|
const ASTSourceUpdater_1 = require("./ASTSourceUpdater");
|
|
41
|
+
const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
42
|
+
const WorkspaceSnapshotService_1 = require("./WorkspaceSnapshotService");
|
|
43
|
+
const SelaError_1 = require("../errors/SelaError");
|
|
44
|
+
// Centralised write gate - every disk-mutating `fs.writeFileSync` call in
|
|
45
|
+
// this module routes through `persistFile` so the SELA_DRY_RUN bypass is
|
|
46
|
+
// enforced in one place, and the Clean-Room WorkspaceSnapshotService
|
|
47
|
+
// records contentBefore/contentAfter for isolated in-memory diffing.
|
|
48
|
+
function persistFile(filePath, content) {
|
|
49
|
+
if (DryRunGuard_1.DryRunGuard.active()) {
|
|
50
|
+
DryRunGuard_1.DryRunGuard.logSkippedWrite("writeFileSync", filePath);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.preWrite(filePath);
|
|
54
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
55
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.postWrite(filePath);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
41
58
|
// ─────────────────────────────────────────────────────────────────
|
|
42
59
|
// QUOTE MANAGEMENT
|
|
43
60
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -293,21 +310,28 @@ function updateTemplateLiteralPreservingVars(line, oldSelector, newSelector, dom
|
|
|
293
310
|
// GIT HELPER (used by branch strategy and autoCommit)
|
|
294
311
|
//
|
|
295
312
|
// Design goals:
|
|
296
|
-
// • Lean
|
|
297
|
-
// • Commit-based
|
|
313
|
+
// • Lean - no stash, no branch-switching, no complex state management.
|
|
314
|
+
// • Commit-based - write first (inPlace), then commit. If git fails,
|
|
298
315
|
// the fix is still on disk (silent inPlace fallback).
|
|
299
|
-
// • Detached HEAD aware
|
|
316
|
+
// • Detached HEAD aware - skips silently in CI environments that check
|
|
300
317
|
// out a specific SHA without a branch ref.
|
|
301
318
|
// ─────────────────────────────────────────────────────────────────
|
|
302
319
|
function runGitOperations(absoluteFilePath, createBranch, relativeFilePath) {
|
|
303
320
|
const filename = path.basename(relativeFilePath);
|
|
321
|
+
// Dry-run gate - suppress branch creation, `git add`, and auto-commit.
|
|
322
|
+
// Mirrors the persistFile() skip so a dry run is fully observable in CI
|
|
323
|
+
// without ever producing a commit.
|
|
324
|
+
if (DryRunGuard_1.DryRunGuard.active()) {
|
|
325
|
+
console.log(`[SourceUpdater] 🌵 Dry Run - skip git operations for ${filename}`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
304
328
|
try {
|
|
305
329
|
// Bail out if HEAD is detached (CI SHA checkout, rebase mid-flight, etc.)
|
|
306
330
|
try {
|
|
307
331
|
(0, child_process_1.execSync)("git symbolic-ref HEAD", { stdio: "pipe" });
|
|
308
332
|
}
|
|
309
333
|
catch {
|
|
310
|
-
console.warn(`[SourceUpdater] ⚠️ Detached HEAD
|
|
334
|
+
console.warn(`[SourceUpdater] ⚠️ Detached HEAD - git operations skipped (fix is saved to disk)`);
|
|
311
335
|
return;
|
|
312
336
|
}
|
|
313
337
|
if (createBranch) {
|
|
@@ -322,7 +346,7 @@ function runGitOperations(absoluteFilePath, createBranch, relativeFilePath) {
|
|
|
322
346
|
console.log(`[SourceUpdater] ✅ Auto-committed fix in: ${filename}`);
|
|
323
347
|
}
|
|
324
348
|
catch (err) {
|
|
325
|
-
console.warn(`[SourceUpdater] ⚠️ Git operation failed: ${err.message}
|
|
349
|
+
console.warn(`[SourceUpdater] ⚠️ Git operation failed: ${err.message} - fix written to disk (inPlace fallback)`);
|
|
326
350
|
}
|
|
327
351
|
}
|
|
328
352
|
class SourceUpdater {
|
|
@@ -352,7 +376,7 @@ class SourceUpdater {
|
|
|
352
376
|
return result;
|
|
353
377
|
}
|
|
354
378
|
// ─────────────────────────────────────────────────────────────
|
|
355
|
-
// commentOnly helper
|
|
379
|
+
// commentOnly helper - inserts a TODO comment above the failing
|
|
356
380
|
// call site and returns success:false so the engine knows the
|
|
357
381
|
// source was NOT rewritten (no retry suppression needed).
|
|
358
382
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -367,7 +391,7 @@ class SourceUpdater {
|
|
|
367
391
|
const indent = lines[lineIdx].match(/^(\s*)/)?.[1] ?? "";
|
|
368
392
|
const comment = `${indent}// TODO [Sela]: replace "${oldSelector}" with "${newSelector}"`;
|
|
369
393
|
lines.splice(lineIdx, 0, comment);
|
|
370
|
-
|
|
394
|
+
persistFile(filePath, lines.join("\n"));
|
|
371
395
|
console.log(`[SourceUpdater] 💬 Comment-only: inserted TODO at line ${caller.line} in ${path.basename(filePath)}`);
|
|
372
396
|
return {
|
|
373
397
|
success: false,
|
|
@@ -378,12 +402,12 @@ class SourceUpdater {
|
|
|
378
402
|
catch (err) {
|
|
379
403
|
return {
|
|
380
404
|
success: false,
|
|
381
|
-
reason: `COMMENT_ONLY: write failed
|
|
405
|
+
reason: `COMMENT_ONLY: write failed - ${err.message}`,
|
|
382
406
|
};
|
|
383
407
|
}
|
|
384
408
|
}
|
|
385
409
|
// ─────────────────────────────────────────────────────────────
|
|
386
|
-
// CORE UPDATE
|
|
410
|
+
// CORE UPDATE - Layered Healing (AST primary, Regex fallback)
|
|
387
411
|
// Called by update(); never call directly from outside the class.
|
|
388
412
|
// ─────────────────────────────────────────────────────────────
|
|
389
413
|
static _coreUpdate(caller, oldSelector, newSelector, domContext, dnaAttrHint, fullSelectorContext, aiSegments, contentChange, smartChainSegments, originalChainHint) {
|
|
@@ -418,7 +442,7 @@ class SourceUpdater {
|
|
|
418
442
|
if (astResult.success) {
|
|
419
443
|
console.log(`[SourceUpdater] ✅ AST Strategy ${astResult.strategy} SUCCESS` +
|
|
420
444
|
(astResult.lineUpdated !== undefined
|
|
421
|
-
? `
|
|
445
|
+
? ` - line ${astResult.lineUpdated + 1}`
|
|
422
446
|
: ""));
|
|
423
447
|
return {
|
|
424
448
|
success: true,
|
|
@@ -434,13 +458,21 @@ class SourceUpdater {
|
|
|
434
458
|
// PROPERTY_ACCESS / TEMPLATE_LITERAL) but could not safely resolve the new
|
|
435
459
|
// value. Regex cannot do better and will corrupt the definition site.
|
|
436
460
|
if (astResult.strategy === "semantic-guard") {
|
|
437
|
-
console.warn(`[SourceUpdater] 🛑 Regex blocked
|
|
461
|
+
console.warn(`[SourceUpdater] 🛑 Regex blocked - semantic guard fired. ` +
|
|
438
462
|
`Source unchanged. Reason: ${astResult.reason}`);
|
|
439
463
|
return { success: false, reason: astResult.reason };
|
|
440
464
|
}
|
|
441
465
|
console.log(`[SourceUpdater] ⚠️ AST engine exhausted (${astResult.reason}), falling back to Regex`);
|
|
442
466
|
}
|
|
443
467
|
catch (astError) {
|
|
468
|
+
// Developer pinned the failing statement with `// sela-fail-fast` -
|
|
469
|
+
// propagate the directive instead of swallowing into the Regex
|
|
470
|
+
// fallback chain (which would defeat the whole point of the gate).
|
|
471
|
+
if ((0, SelaError_1.isSelaErrorFrom)(astError, "ASTUpdater") &&
|
|
472
|
+
astError.code ===
|
|
473
|
+
SelaError_1.AST_UPDATER_CODES.HEAL_DISABLED_BY_DIRECTIVE) {
|
|
474
|
+
throw astError;
|
|
475
|
+
}
|
|
444
476
|
console.warn(`[SourceUpdater] ⚠️ AST engine error: ${astError.message}. Falling back to Regex`);
|
|
445
477
|
}
|
|
446
478
|
// ── 2. Regex Fallback ──────────────────────────────────────────
|
|
@@ -448,11 +480,11 @@ class SourceUpdater {
|
|
|
448
480
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
449
481
|
const lines = raw.split(/\r?\n/);
|
|
450
482
|
if (caller.line <= 0) {
|
|
451
|
-
console.log(`[SourceUpdater] ⚠️ line ${caller.line} invalid
|
|
483
|
+
console.log(`[SourceUpdater] ⚠️ line ${caller.line} invalid - running Regex Strategy G only`);
|
|
452
484
|
if (contentChange) {
|
|
453
485
|
const assertResult = SourceUpdater.strategyAssertionHealing(lines, 1, contentChange, true);
|
|
454
486
|
if (assertResult.success) {
|
|
455
|
-
|
|
487
|
+
persistFile(filePath, lines.join("\n"));
|
|
456
488
|
console.log(`[SourceUpdater] ✅ Regex Strategy G (Assertion Healing) SUCCESS`);
|
|
457
489
|
return assertResult;
|
|
458
490
|
}
|
|
@@ -479,8 +511,8 @@ class SourceUpdater {
|
|
|
479
511
|
const layeredResult = SourceUpdater.strategyLayeredHealing(lines, callerLine, oldSelector, aiSegments, fullSelectorContext);
|
|
480
512
|
if (layeredResult.success) {
|
|
481
513
|
tryAssertionHealing();
|
|
482
|
-
|
|
483
|
-
console.log(`[SourceUpdater] ✅ Strategy 0 (Layered Healing) SUCCESS
|
|
514
|
+
persistFile(filePath, lines.join("\n"));
|
|
515
|
+
console.log(`[SourceUpdater] ✅ Strategy 0 (Layered Healing) SUCCESS - line ${layeredResult.lineUpdated + 1}`);
|
|
484
516
|
return layeredResult;
|
|
485
517
|
}
|
|
486
518
|
}
|
|
@@ -488,7 +520,7 @@ class SourceUpdater {
|
|
|
488
520
|
const chainCollapseResult = SourceUpdater.strategyChainCollapse_Regex(lines, callerLine, oldSelector, effectiveNewSelector);
|
|
489
521
|
if (chainCollapseResult.success) {
|
|
490
522
|
tryAssertionHealing();
|
|
491
|
-
|
|
523
|
+
persistFile(filePath, lines.join("\n"));
|
|
492
524
|
console.log(`[SourceUpdater] ✅ Strategy F (Chain Collapse) SUCCESS`);
|
|
493
525
|
return chainCollapseResult;
|
|
494
526
|
}
|
|
@@ -496,7 +528,7 @@ class SourceUpdater {
|
|
|
496
528
|
const chainResult = SourceUpdater.strategyChainCollapse(lines, callerLine, oldSelector, effectiveNewSelector);
|
|
497
529
|
if (chainResult.success) {
|
|
498
530
|
tryAssertionHealing();
|
|
499
|
-
|
|
531
|
+
persistFile(filePath, lines.join("\n"));
|
|
500
532
|
console.log(`[SourceUpdater] ✅ Strategy 1 (Chain Collapse) SUCCESS`);
|
|
501
533
|
return chainResult;
|
|
502
534
|
}
|
|
@@ -504,7 +536,7 @@ class SourceUpdater {
|
|
|
504
536
|
const directResult = SourceUpdater.strategyDirectLiteral(lines, callerLine, oldSelector, effectiveNewSelector);
|
|
505
537
|
if (directResult.success) {
|
|
506
538
|
tryAssertionHealing();
|
|
507
|
-
|
|
539
|
+
persistFile(filePath, lines.join("\n"));
|
|
508
540
|
console.log(`[SourceUpdater] ✅ Strategy 2 (Direct Literal) SUCCESS`);
|
|
509
541
|
return directResult;
|
|
510
542
|
}
|
|
@@ -512,7 +544,7 @@ class SourceUpdater {
|
|
|
512
544
|
const varResult = SourceUpdater.strategyUpstreamVariable(lines, callerLine, oldSelector, effectiveNewSelector);
|
|
513
545
|
if (varResult.success) {
|
|
514
546
|
tryAssertionHealing();
|
|
515
|
-
|
|
547
|
+
persistFile(filePath, lines.join("\n"));
|
|
516
548
|
console.log(`[SourceUpdater] ✅ Strategy 3 (Upstream Variable) SUCCESS`);
|
|
517
549
|
return varResult;
|
|
518
550
|
}
|
|
@@ -520,7 +552,7 @@ class SourceUpdater {
|
|
|
520
552
|
const chainDedupResult = SourceUpdater.strategyChainedLocator(lines, callerLine, oldSelector, effectiveNewSelector);
|
|
521
553
|
if (chainDedupResult.success) {
|
|
522
554
|
tryAssertionHealing();
|
|
523
|
-
|
|
555
|
+
persistFile(filePath, lines.join("\n"));
|
|
524
556
|
console.log(`[SourceUpdater] ✅ Strategy 4 (Chained Dedup) SUCCESS`);
|
|
525
557
|
return chainDedupResult;
|
|
526
558
|
}
|
|
@@ -528,7 +560,7 @@ class SourceUpdater {
|
|
|
528
560
|
const fnResult = SourceUpdater.strategyFunctionReturn(lines, callerLine, oldSelector, effectiveNewSelector, domContext, dnaAttrHint);
|
|
529
561
|
if (fnResult.success) {
|
|
530
562
|
tryAssertionHealing();
|
|
531
|
-
|
|
563
|
+
persistFile(filePath, lines.join("\n"));
|
|
532
564
|
console.log(`[SourceUpdater] ✅ Strategy 5 (Function Return) SUCCESS`);
|
|
533
565
|
return fnResult;
|
|
534
566
|
}
|
|
@@ -536,7 +568,7 @@ class SourceUpdater {
|
|
|
536
568
|
const scanResult = SourceUpdater.strategyGlobalScan(lines, oldSelector, effectiveNewSelector);
|
|
537
569
|
if (scanResult.success) {
|
|
538
570
|
tryAssertionHealing();
|
|
539
|
-
|
|
571
|
+
persistFile(filePath, lines.join("\n"));
|
|
540
572
|
console.log(`[SourceUpdater] ✅ Strategy 6 (Global Scan) SUCCESS`);
|
|
541
573
|
return scanResult;
|
|
542
574
|
}
|
|
@@ -544,7 +576,7 @@ class SourceUpdater {
|
|
|
544
576
|
if (contentChange) {
|
|
545
577
|
const assertResult = SourceUpdater.strategyAssertionHealing(lines, callerLine, contentChange);
|
|
546
578
|
if (assertResult.success) {
|
|
547
|
-
|
|
579
|
+
persistFile(filePath, lines.join("\n"));
|
|
548
580
|
console.log(`[SourceUpdater] ✅ Strategy G (Assertion Healing) SUCCESS`);
|
|
549
581
|
return assertResult;
|
|
550
582
|
}
|
|
@@ -556,7 +588,7 @@ class SourceUpdater {
|
|
|
556
588
|
};
|
|
557
589
|
}
|
|
558
590
|
// ─────────────────────────────────────────────────────────────
|
|
559
|
-
// STRATEGY F (Regex)
|
|
591
|
+
// STRATEGY F (Regex) - Full Chain Collapsing
|
|
560
592
|
// ─────────────────────────────────────────────────────────────
|
|
561
593
|
static strategyChainCollapse_Regex(lines, callerLine, oldSelector, newSelector) {
|
|
562
594
|
console.log(`[Strategy-F] ⛓️ Starting Regex Chain Collapse...`);
|
|
@@ -607,7 +639,7 @@ class SourceUpdater {
|
|
|
607
639
|
return { success: false, reason: "chain detected but could not replace" };
|
|
608
640
|
}
|
|
609
641
|
// ─────────────────────────────────────────────────────────────
|
|
610
|
-
// STRATEGY G (Regex)
|
|
642
|
+
// STRATEGY G (Regex) - Assertion Healing
|
|
611
643
|
// ─────────────────────────────────────────────────────────────
|
|
612
644
|
static strategyAssertionHealing(lines, callerLine, contentChange, wholeFile = false) {
|
|
613
645
|
const { oldText, newText } = contentChange;
|
|
@@ -662,7 +694,7 @@ class SourceUpdater {
|
|
|
662
694
|
return { success: false, reason: "no assertions to heal" };
|
|
663
695
|
}
|
|
664
696
|
// ─────────────────────────────────────────────────────────────
|
|
665
|
-
// STRATEGY 0
|
|
697
|
+
// STRATEGY 0 - Layered Healing (Upstream Variable Tracing)
|
|
666
698
|
// ─────────────────────────────────────────────────────────────
|
|
667
699
|
static strategyLayeredHealing(lines, callerLine, oldElementSelector, aiSegments, fullSelectorContext) {
|
|
668
700
|
console.log(`[SourceUpdater] 🧠 Strategy 0: Starting Layered Healing...`);
|
|
@@ -715,7 +747,7 @@ class SourceUpdater {
|
|
|
715
747
|
return null;
|
|
716
748
|
}
|
|
717
749
|
// ─────────────────────────────────────────────────────────────
|
|
718
|
-
// STRATEGY 1
|
|
750
|
+
// STRATEGY 1 - Chain Collapse (leaf-only precision replacement)
|
|
719
751
|
// ─────────────────────────────────────────────────────────────
|
|
720
752
|
static strategyChainCollapse(lines, callerLine, oldSelector, newSelector) {
|
|
721
753
|
const oldLeaf = oldSelector
|
|
@@ -757,7 +789,7 @@ class SourceUpdater {
|
|
|
757
789
|
};
|
|
758
790
|
}
|
|
759
791
|
// ─────────────────────────────────────────────────────────────
|
|
760
|
-
// STRATEGY 2
|
|
792
|
+
// STRATEGY 2 - Direct Literal
|
|
761
793
|
// ─────────────────────────────────────────────────────────────
|
|
762
794
|
static strategyDirectLiteral(lines, callerLine, oldSelector, newSelector) {
|
|
763
795
|
const windowSize = 10;
|
|
@@ -790,7 +822,7 @@ class SourceUpdater {
|
|
|
790
822
|
return { success: false, reason: "no direct literal" };
|
|
791
823
|
}
|
|
792
824
|
// ─────────────────────────────────────────────────────────────
|
|
793
|
-
// STRATEGY 3
|
|
825
|
+
// STRATEGY 3 - Upstream Variable/Const
|
|
794
826
|
// ─────────────────────────────────────────────────────────────
|
|
795
827
|
static strategyUpstreamVariable(lines, callerLine, oldSelector, newSelector) {
|
|
796
828
|
const callBlock = lines
|
|
@@ -827,7 +859,7 @@ class SourceUpdater {
|
|
|
827
859
|
return { success: false, reason: "no upstream variable" };
|
|
828
860
|
}
|
|
829
861
|
// ─────────────────────────────────────────────────────────────
|
|
830
|
-
// STRATEGY 4
|
|
862
|
+
// STRATEGY 4 - Chained Locator Dedup
|
|
831
863
|
// ─────────────────────────────────────────────────────────────
|
|
832
864
|
static strategyChainedLocator(lines, callerLine, oldSelector, newSelector) {
|
|
833
865
|
const blockStart = Math.max(0, callerLine - 10);
|
|
@@ -854,7 +886,7 @@ class SourceUpdater {
|
|
|
854
886
|
return { success: false, reason: "dedup found but literal not replaced" };
|
|
855
887
|
}
|
|
856
888
|
// ─────────────────────────────────────────────────────────────
|
|
857
|
-
// STRATEGY 5
|
|
889
|
+
// STRATEGY 5 - Function/Template with Variable Preservation
|
|
858
890
|
// ─────────────────────────────────────────────────────────────
|
|
859
891
|
static strategyFunctionReturn(lines, callerLine, oldSelector, newSelector, domContext, dnaAttrHint) {
|
|
860
892
|
const callBlock = lines
|
|
@@ -939,7 +971,7 @@ class SourceUpdater {
|
|
|
939
971
|
return { success: false, reason: "function strategy exhausted" };
|
|
940
972
|
}
|
|
941
973
|
// ─────────────────────────────────────────────────────────────
|
|
942
|
-
// STRATEGY 6
|
|
974
|
+
// STRATEGY 6 - Global Scan
|
|
943
975
|
// ─────────────────────────────────────────────────────────────
|
|
944
976
|
static strategyGlobalScan(lines, oldSelector, newSelector) {
|
|
945
977
|
const lastOldPart = oldSelector.split(">>").pop().trim();
|
|
@@ -1008,7 +1040,7 @@ class SourceUpdater {
|
|
|
1008
1040
|
if (pattern.test(lines[i])) {
|
|
1009
1041
|
const safeNew = sanitizeForInjection(newArgument, q);
|
|
1010
1042
|
lines[i] = lines[i].replace(pattern, `$1${safeNew}$2`);
|
|
1011
|
-
|
|
1043
|
+
persistFile(filePath, lines.join("\n"));
|
|
1012
1044
|
return { success: true, reason: "argument updated", lineUpdated: i };
|
|
1013
1045
|
}
|
|
1014
1046
|
}
|
|
@@ -1016,7 +1048,7 @@ class SourceUpdater {
|
|
|
1016
1048
|
if (pattern.test(lines[i])) {
|
|
1017
1049
|
const safeNew = sanitizeForInjection(newArgument, q);
|
|
1018
1050
|
lines[i] = lines[i].replace(pattern, `$1${safeNew}$2`);
|
|
1019
|
-
|
|
1051
|
+
persistFile(filePath, lines.join("\n"));
|
|
1020
1052
|
return { success: true, reason: "argument updated", lineUpdated: i };
|
|
1021
1053
|
}
|
|
1022
1054
|
}
|
|
@@ -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);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface FileSnapshot {
|
|
2
|
+
/** Absolute, normalised path of the tracked file. */
|
|
3
|
+
absolutePath: string;
|
|
4
|
+
/** Project-relative path (posix slashes) - for diff headers + PR commits. */
|
|
5
|
+
relativePath: string;
|
|
6
|
+
/**
|
|
7
|
+
* Disk content captured BEFORE Sela's first write to this file.
|
|
8
|
+
* May contain the developer's uncommitted edits - that's the point.
|
|
9
|
+
*/
|
|
10
|
+
contentBefore: string;
|
|
11
|
+
/** Disk content captured AFTER Sela's latest write. `null` until first postWrite(). */
|
|
12
|
+
contentAfter: string | null;
|
|
13
|
+
/** Content of the file at `HEAD` (last committed) - `null` when untracked / no git. */
|
|
14
|
+
headContent: string | null;
|
|
15
|
+
/** True when `contentBefore !== headContent`, i.e. developer had dirty edits. */
|
|
16
|
+
hadUncommittedChanges: boolean;
|
|
17
|
+
/** True when the file did not exist on disk before Sela touched it. */
|
|
18
|
+
createdBySela: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare class WorkspaceSnapshotService {
|
|
21
|
+
private snapshots;
|
|
22
|
+
private cwd;
|
|
23
|
+
constructor(cwd?: string);
|
|
24
|
+
/** Override the cwd used for relative-path + HEAD lookups (tests). */
|
|
25
|
+
setCwd(cwd: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Record the pre-mutation state of `filePath` if not already tracked.
|
|
28
|
+
* Idempotent - only the FIRST call per file matters.
|
|
29
|
+
*/
|
|
30
|
+
preWrite(filePath: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Refresh the post-mutation state. Safe to call multiple times - each
|
|
33
|
+
* call overwrites `contentAfter` with the latest disk state.
|
|
34
|
+
*/
|
|
35
|
+
postWrite(filePath: string): void;
|
|
36
|
+
/** Lookup by absolute or relative path. Returns `null` when untracked. */
|
|
37
|
+
getSnapshot(filePath: string): FileSnapshot | null;
|
|
38
|
+
/** Every tracked snapshot. Insertion order. */
|
|
39
|
+
getAllSnapshots(): FileSnapshot[];
|
|
40
|
+
/**
|
|
41
|
+
* Did Sela actually mutate this file? True when contentAfter exists and
|
|
42
|
+
* differs from contentBefore.
|
|
43
|
+
*/
|
|
44
|
+
isMutated(filePath: string): boolean;
|
|
45
|
+
/** All snapshots with a real mutation recorded. */
|
|
46
|
+
getMutatedSnapshots(): FileSnapshot[];
|
|
47
|
+
/**
|
|
48
|
+
* Rewind every tracked file on disk to `contentBefore` - restoring the
|
|
49
|
+
* developer's pre-Sela workspace state byte-for-byte.
|
|
50
|
+
*/
|
|
51
|
+
restoreOriginal(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Rewind a SINGLE tracked file on disk to `contentBefore`. Used by the
|
|
54
|
+
* Local DX interactive review flow when the developer rejects a fix.
|
|
55
|
+
* Returns true when the rewind happened, false when the file is untracked.
|
|
56
|
+
*/
|
|
57
|
+
restoreOriginalByPath(filePath: string): boolean;
|
|
58
|
+
private rewindSnapshot;
|
|
59
|
+
/**
|
|
60
|
+
* Forward every tracked file on disk to `contentAfter` (no-op for files
|
|
61
|
+
* that were never written). Used by PRAutomationService to reapply
|
|
62
|
+
* Sela's mutations after a `git stash pop` round-trip.
|
|
63
|
+
*/
|
|
64
|
+
restoreMutated(): void;
|
|
65
|
+
clear(): void;
|
|
66
|
+
private normalise;
|
|
67
|
+
private toRelative;
|
|
68
|
+
private readHeadContent;
|
|
69
|
+
}
|
|
70
|
+
export declare const sharedWorkspaceSnapshot: WorkspaceSnapshotService;
|
|
71
|
+
//# sourceMappingURL=WorkspaceSnapshotService.d.ts.map
|
|
@@ -0,0 +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;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"}
|