sela-core 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +27 -17
  2. package/dist/cli/ErrorHandler.d.ts +1 -1
  3. package/dist/cli/ErrorHandler.js +14 -14
  4. package/dist/cli/commands/bulk.d.ts +1 -1
  5. package/dist/cli/commands/bulk.d.ts.map +1 -1
  6. package/dist/cli/commands/bulk.js +51 -33
  7. package/dist/cli/commands/init.d.ts +1 -1
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +26 -18
  10. package/dist/cli/commands/merge.d.ts +13 -0
  11. package/dist/cli/commands/merge.d.ts.map +1 -0
  12. package/dist/cli/commands/merge.js +119 -0
  13. package/dist/cli/commands/showReport.d.ts +1 -1
  14. package/dist/cli/commands/showReport.d.ts.map +1 -1
  15. package/dist/cli/commands/showReport.js +12 -10
  16. package/dist/cli/commands/status.d.ts +1 -1
  17. package/dist/cli/commands/status.d.ts.map +1 -1
  18. package/dist/cli/commands/status.js +8 -8
  19. package/dist/cli/commands/sync.d.ts +1 -1
  20. package/dist/cli/commands/sync.d.ts.map +1 -1
  21. package/dist/cli/commands/sync.js +38 -30
  22. package/dist/cli/index.js +12 -9
  23. package/dist/cli/ui/DnaTable.d.ts +1 -1
  24. package/dist/cli/ui/DnaTable.d.ts.map +1 -1
  25. package/dist/cli/ui/DnaTable.js +23 -16
  26. package/dist/cli/ui/RefactorWizard.d.ts +3 -3
  27. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
  28. package/dist/cli/ui/RefactorWizard.js +108 -82
  29. package/dist/config/ConfigLoader.d.ts +2 -2
  30. package/dist/config/ConfigLoader.d.ts.map +1 -1
  31. package/dist/config/ConfigLoader.js +15 -15
  32. package/dist/config/DryRunGuard.d.ts +1 -1
  33. package/dist/config/DryRunGuard.js +7 -7
  34. package/dist/config/SelaConfig.d.ts +13 -17
  35. package/dist/config/SelaConfig.d.ts.map +1 -1
  36. package/dist/config/SelaConfig.js +32 -26
  37. package/dist/engine/HealingRegistry.d.ts +1 -1
  38. package/dist/engine/HealingRegistry.js +3 -3
  39. package/dist/engine/SelaEngine.d.ts +1 -1
  40. package/dist/engine/SelaEngine.d.ts.map +1 -1
  41. package/dist/engine/SelaEngine.js +117 -51
  42. package/dist/errors/SelaError.d.ts +8 -1
  43. package/dist/errors/SelaError.d.ts.map +1 -1
  44. package/dist/errors/SelaError.js +14 -7
  45. package/dist/fixtures/expectProxy.d.ts +2 -2
  46. package/dist/fixtures/expectProxy.js +16 -16
  47. package/dist/fixtures/index.d.ts +18 -2
  48. package/dist/fixtures/index.d.ts.map +1 -1
  49. package/dist/fixtures/index.js +47 -27
  50. package/dist/fixtures/moduleExpect.js +7 -7
  51. package/dist/fixtures/proxyTag.d.ts +1 -1
  52. package/dist/fixtures/proxyTag.js +4 -4
  53. package/dist/index.d.ts +8 -8
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +4 -4
  56. package/dist/reporter/SelaReporter.d.ts +45 -12
  57. package/dist/reporter/SelaReporter.d.ts.map +1 -1
  58. package/dist/reporter/SelaReporter.js +139 -39
  59. package/dist/services/ASTSourceUpdater.d.ts +48 -3
  60. package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
  61. package/dist/services/ASTSourceUpdater.js +283 -105
  62. package/dist/services/AnchorResolver.d.ts +157 -0
  63. package/dist/services/AnchorResolver.d.ts.map +1 -0
  64. package/dist/services/AnchorResolver.js +289 -0
  65. package/dist/services/ArgumentTypeAnalyzer.js +7 -7
  66. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
  67. package/dist/services/BlastRadiusAnalyzer.js +5 -2
  68. package/dist/services/ChainValidator.js +22 -22
  69. package/dist/services/CrossFileHealer.js +7 -7
  70. package/dist/services/DecisionEngine.d.ts +51 -0
  71. package/dist/services/DecisionEngine.d.ts.map +1 -0
  72. package/dist/services/DecisionEngine.js +260 -0
  73. package/dist/services/DefinitionTracer.d.ts +1 -1
  74. package/dist/services/DefinitionTracer.d.ts.map +1 -1
  75. package/dist/services/DefinitionTracer.js +28 -13
  76. package/dist/services/DnaEditorService.d.ts +2 -2
  77. package/dist/services/DnaEditorService.d.ts.map +1 -1
  78. package/dist/services/DnaEditorService.js +81 -62
  79. package/dist/services/DnaIndexService.d.ts +1 -1
  80. package/dist/services/DnaIndexService.d.ts.map +1 -1
  81. package/dist/services/DnaIndexService.js +14 -14
  82. package/dist/services/HealReportService.d.ts +31 -55
  83. package/dist/services/HealReportService.d.ts.map +1 -1
  84. package/dist/services/HealReportService.js +864 -1240
  85. package/dist/services/HealingAdvisory.d.ts +9 -1
  86. package/dist/services/HealingAdvisory.d.ts.map +1 -1
  87. package/dist/services/HealingAdvisory.js +9 -1
  88. package/dist/services/HealingCacheService.d.ts +8 -8
  89. package/dist/services/HealingCacheService.d.ts.map +1 -1
  90. package/dist/services/HealingCacheService.js +24 -24
  91. package/dist/services/HealthReportService.d.ts +1 -1
  92. package/dist/services/HealthReportService.d.ts.map +1 -1
  93. package/dist/services/HealthReportService.js +25 -25
  94. package/dist/services/InitializerUpdater.d.ts.map +1 -1
  95. package/dist/services/InitializerUpdater.js +12 -2
  96. package/dist/services/IntentAuditor.d.ts.map +1 -1
  97. package/dist/services/IntentAuditor.js +32 -21
  98. package/dist/services/InteractiveReview.d.ts +8 -17
  99. package/dist/services/InteractiveReview.d.ts.map +1 -1
  100. package/dist/services/InteractiveReview.js +34 -124
  101. package/dist/services/LLMService.d.ts +1 -1
  102. package/dist/services/LLMService.js +16 -16
  103. package/dist/services/MutationApplier.d.ts +55 -0
  104. package/dist/services/MutationApplier.d.ts.map +1 -0
  105. package/dist/services/MutationApplier.js +322 -0
  106. package/dist/services/PRAutomationService.d.ts +2 -2
  107. package/dist/services/PRAutomationService.d.ts.map +1 -1
  108. package/dist/services/PRAutomationService.js +43 -39
  109. package/dist/services/PendingPromptLedger.d.ts +9 -2
  110. package/dist/services/PendingPromptLedger.d.ts.map +1 -1
  111. package/dist/services/PendingPromptLedger.js +31 -6
  112. package/dist/services/ReportGenerator.d.ts +116 -30
  113. package/dist/services/ReportGenerator.d.ts.map +1 -1
  114. package/dist/services/ReportGenerator.js +149 -62
  115. package/dist/services/ReportMergeService.d.ts +95 -0
  116. package/dist/services/ReportMergeService.d.ts.map +1 -0
  117. package/dist/services/ReportMergeService.js +0 -0
  118. package/dist/services/SafetyGuard.d.ts +4 -7
  119. package/dist/services/SafetyGuard.d.ts.map +1 -1
  120. package/dist/services/SafetyGuard.js +29 -106
  121. package/dist/services/SelectorSanitizer.d.ts +52 -0
  122. package/dist/services/SelectorSanitizer.d.ts.map +1 -0
  123. package/dist/services/SelectorSanitizer.js +318 -0
  124. package/dist/services/SnapshotService.js +6 -6
  125. package/dist/services/SourceLinkService.d.ts +2 -2
  126. package/dist/services/SourceLinkService.d.ts.map +1 -1
  127. package/dist/services/SourceLinkService.js +31 -22
  128. package/dist/services/SourceUpdater.d.ts +1 -1
  129. package/dist/services/SourceUpdater.d.ts.map +1 -1
  130. package/dist/services/SourceUpdater.js +44 -26
  131. package/dist/services/TemplateDiffService.d.ts.map +1 -1
  132. package/dist/services/TemplateDiffService.js +18 -15
  133. package/dist/services/TraceBackEngine.d.ts +67 -0
  134. package/dist/services/TraceBackEngine.d.ts.map +1 -0
  135. package/dist/services/TraceBackEngine.js +672 -0
  136. package/dist/services/WorkspaceSnapshotService.d.ts +6 -6
  137. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -1
  138. package/dist/services/WorkspaceSnapshotService.js +10 -12
  139. package/dist/types/index.d.ts +1 -1
  140. package/dist/utils/DOMUtils.d.ts +18 -2
  141. package/dist/utils/DOMUtils.d.ts.map +1 -1
  142. package/dist/utils/DOMUtils.js +335 -49
  143. package/dist/utils/IsolatedDiff.d.ts +2 -2
  144. package/dist/utils/IsolatedDiff.d.ts.map +1 -1
  145. package/dist/utils/IsolatedDiff.js +13 -10
  146. package/package.json +2 -2
@@ -49,8 +49,12 @@ const InitializerUpdater_1 = require("./InitializerUpdater");
49
49
  const CrossFileHealer_1 = require("./CrossFileHealer");
50
50
  const HealingAdvisory_1 = require("./HealingAdvisory");
51
51
  const ChainValidator_1 = require("./ChainValidator");
52
+ const TraceBackEngine_1 = require("./TraceBackEngine");
53
+ const DecisionEngine_1 = require("./DecisionEngine");
54
+ const MutationApplier_1 = require("./MutationApplier");
55
+ const SelectorSanitizer_1 = require("./SelectorSanitizer");
52
56
  // ═══════════════════════════════════════════════════════════════════
53
- // FIX #1 normalizeSelector utility
57
+ // FIX #1 - normalizeSelector utility
54
58
  // ═══════════════════════════════════════════════════════════════════
55
59
  function normalizeSelector(s) {
56
60
  return s
@@ -70,7 +74,7 @@ function normalizeSelector(s) {
70
74
  // of "varName", or null otherwise.
71
75
  //
72
76
  // Why this matters:
73
- // const shadowBtn = shadowBtn.getByRole(…) ← CIRCULAR must be caught
77
+ // const shadowBtn = shadowBtn.getByRole(…) ← CIRCULAR - must be caught
74
78
  // const shadowBtn = page.getByRole(…) ← CORRECT
75
79
  //
76
80
  // We walk UP the ancestor chain from the call node looking for a
@@ -83,7 +87,7 @@ function getEnclosingVariableDeclaration(node) {
83
87
  if (ts_morph_1.Node.isVariableDeclaration(current)) {
84
88
  return current;
85
89
  }
86
- // Stop climbing past statement boundaries we only want the immediate
90
+ // Stop climbing past statement boundaries - we only want the immediate
87
91
  // enclosing declaration, not a declaration somewhere further up the tree.
88
92
  if (ts_morph_1.Node.isBlock(current) ||
89
93
  ts_morph_1.Node.isSourceFile(current) ||
@@ -121,7 +125,7 @@ function isInsideOwnInitializer(callNode, varName) {
121
125
  function resolveSafeReceiver(proposedReceiver, rootReceiver, callNode) {
122
126
  if (isInsideOwnInitializer(callNode, proposedReceiver)) {
123
127
  console.log(`[ScopeGuard] 🔒 Circular reference prevented: ` +
124
- `"${proposedReceiver}" is being initialized ` +
128
+ `"${proposedReceiver}" is being initialized - ` +
125
129
  `falling back to root receiver "${rootReceiver}"`);
126
130
  return rootReceiver;
127
131
  }
@@ -554,7 +558,7 @@ class MethodMapper {
554
558
  for (const seg of segments) {
555
559
  const callText = MethodMapper.build(currentReceiver, seg);
556
560
  if (callText === null) {
557
- console.warn(`[MethodMapper] Cannot build segment type "${seg.type}" aborting chain`);
561
+ console.warn(`[MethodMapper] Cannot build segment type "${seg.type}" - aborting chain`);
558
562
  return null;
559
563
  }
560
564
  const methodPart = callText.slice(currentReceiver.length);
@@ -617,7 +621,7 @@ function deduplicateChainSegments(aiSegments, contextSegments) {
617
621
  if (overlap === 0)
618
622
  return { deduped: aiSegments, stripped: 0 };
619
623
  console.log(`[ChainDedup] ✂️ Stripping ${overlap}/${contextSegments.length} duplicate leading ` +
620
- `segment(s) context already encodes them`);
624
+ `segment(s) - context already encodes them`);
621
625
  return { deduped: aiSegments.slice(overlap), stripped: overlap };
622
626
  }
623
627
  const PAGE_ROOT_NAMES = new Set([
@@ -731,15 +735,15 @@ function resolveVariableToSegments(sourceFile, varName, beforeLine, depth = 0) {
731
735
  // Module-level write gate shared between `ASTSourceUpdater` and the
732
736
  // internal `NodeTransformationStrategy`. Centralises three concerns
733
737
  // every `saveSync` call site needs:
734
- // 1. `DryRunGuard.active()` short-circuit keeps the mutation in
738
+ // 1. `DryRunGuard.active()` short-circuit - keeps the mutation in
735
739
  // memory but suppresses the on-disk write under SELA_DRY_RUN.
736
- // 2. `sharedWorkspaceSnapshot.preWrite()` captures `contentBefore`
740
+ // 2. `sharedWorkspaceSnapshot.preWrite()` - captures `contentBefore`
737
741
  // so the Clean-Room ledger / Local DX rollback can rewind.
738
- // 3. `sharedWorkspaceSnapshot.postWrite()` refreshes `contentAfter`
742
+ // 3. `sharedWorkspaceSnapshot.postWrite()` - refreshes `contentAfter`
739
743
  // so `getMutatedSnapshots()` (driving the pending-prompts ledger
740
744
  // and the interactive CLI prompt) sees the new disk state.
741
745
  //
742
- // Strategies do NOT receive the persistence service via DI they call
746
+ // Strategies do NOT receive the persistence service via DI - they call
743
747
  // this helper directly. Same singleton, zero coupling, one code path.
744
748
  // ═══════════════════════════════════════════════════════════════════
745
749
  function persistSourceFile(sourceFile) {
@@ -753,7 +757,7 @@ function persistSourceFile(sourceFile) {
753
757
  WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.postWrite(fp);
754
758
  }
755
759
  // ═══════════════════════════════════════════════════════════════════
756
- // NODE TRANSFORMATION STRATEGY Scope-Aware, Zero-Redundancy
760
+ // NODE TRANSFORMATION STRATEGY - Scope-Aware, Zero-Redundancy
757
761
  // ═══════════════════════════════════════════════════════════════════
758
762
  class NodeTransformationStrategy {
759
763
  chainValidator;
@@ -823,8 +827,10 @@ class NodeTransformationStrategy {
823
827
  // When AI's first segment TYPE matches the receiver variable's defining type
824
828
  // but with a different selector, the AI is replacing the receiver context.
825
829
  // Strategy: update the receiver variable's definition, then use remaining
826
- // segments with the receiver var preserving developer's variable structure.
827
- if (!forcedReceiver && discoveredAstRoot && !PAGE_ROOT_NAMES.has(discoveredAstRoot)) {
830
+ // segments with the receiver var - preserving developer's variable structure.
831
+ if (!forcedReceiver &&
832
+ discoveredAstRoot &&
833
+ !PAGE_ROOT_NAMES.has(discoveredAstRoot)) {
828
834
  const typeCheckRes = resolveVariableToSegments(sourceFile, discoveredAstRoot, caller.line);
829
835
  if (typeCheckRes.segments.length > 0 && workingSegments.length > 0) {
830
836
  const receiverDefType = typeCheckRes.segments[typeCheckRes.segments.length - 1].type;
@@ -923,8 +929,9 @@ class NodeTransformationStrategy {
923
929
  // to prevent. Skip the candidate; the semantic gate owns this case.
924
930
  const firstArg = call.getArguments()[0];
925
931
  if (firstArg &&
926
- (ts_morph_1.Node.isIdentifier(firstArg) || ts_morph_1.Node.isPropertyAccessExpression(firstArg))) {
927
- console.log(`[NodeTransform] 🛑 skip — first arg is ${firstArg.getKindName()} ` +
932
+ (ts_morph_1.Node.isIdentifier(firstArg) ||
933
+ ts_morph_1.Node.isPropertyAccessExpression(firstArg))) {
934
+ console.log(`[NodeTransform] 🛑 skip - first arg is ${firstArg.getKindName()} ` +
928
935
  `"${firstArg.getText()}" (semantic dispatch owns definition-site mutations)`);
929
936
  continue;
930
937
  }
@@ -936,12 +943,12 @@ class NodeTransformationStrategy {
936
943
  // Before we build code, we must verify that the proposed receiver
937
944
  // does not create a circular self-reference.
938
945
  //
939
- // Case A forcedReceiver is set (strip-and-force path):
946
+ // Case A - forcedReceiver is set (strip-and-force path):
940
947
  // Check if the chainTop lives inside the initializer of that var.
941
948
  // If yes, fall back to the actual AST root (e.g. "page").
942
949
  //
943
- // Case B no forced receiver:
944
- // Use astRoot directly it was walked up from the call node and
950
+ // Case B - no forced receiver:
951
+ // Use astRoot directly - it was walked up from the call node and
945
952
  // is always either "page" or a stable variable reference.
946
953
  // ══════════════════════════════════════════════════════════════
947
954
  let effectiveReceiver;
@@ -1191,7 +1198,7 @@ class NodeTransformationStrategy {
1191
1198
  }
1192
1199
  updateReceiverDefinition(sourceFile, varName, segType, newSelector, callerLine) {
1193
1200
  // Collect all matching declarations, then pick the closest one that
1194
- // appears strictly BEFORE callerLine prevents accidentally updating
1201
+ // appears strictly BEFORE callerLine - prevents accidentally updating
1195
1202
  // a same-named variable in a different test block that happens to be
1196
1203
  // within the 100-line distance window.
1197
1204
  const declarations = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.VariableDeclaration);
@@ -1302,7 +1309,7 @@ class NodeTransformationStrategy {
1302
1309
  .map((d) => `[TS${d.getCode()}] ${d.getMessageText()}`)
1303
1310
  .join(", "));
1304
1311
  if (/\.(getBy\w+|frameLocator)\s*\(/.test(code)) {
1305
- console.warn(`[NodeTransform] ⚠️ Semantic method detected bypassing validation`);
1312
+ console.warn(`[NodeTransform] ⚠️ Semantic method detected - bypassing validation`);
1306
1313
  return true;
1307
1314
  }
1308
1315
  return false;
@@ -1550,19 +1557,145 @@ function findAssertionsInWindow(sourceFile, locatorVarName, callerLine, windowLi
1550
1557
  }
1551
1558
  return results;
1552
1559
  }
1553
- // ═══════════════════════════════════════════════════════════════════
1554
- // MAIN CLASS
1555
- // ═══════════════════════════════════════════════════════════════════
1560
+ /**
1561
+ * Build a locator EXPRESSION from a raw healed selector + chain root.
1562
+ * Internal engine selectors become their idiomatic method form
1563
+ * (`page.getByRole(...)`); otherwise a `root.locator("selector")` call.
1564
+ */
1565
+ function buildLocatorExpr(rawSelector, rootReceiver) {
1566
+ const root = rootReceiver || "page";
1567
+ if (!rawSelector)
1568
+ return `${root}.locator("")`;
1569
+ if ((0, SelectorSanitizer_1.isInternalEngineSelector)(rawSelector)) {
1570
+ const method = (0, SelectorSanitizer_1.internalToMethodExpression)(rawSelector, root);
1571
+ if (method)
1572
+ return method;
1573
+ const publicStr = (0, SelectorSanitizer_1.internalToPublicSelectorString)(rawSelector);
1574
+ return `${root}.locator(${JSON.stringify(publicStr ?? rawSelector)})`;
1575
+ }
1576
+ return `${root}.locator(${JSON.stringify(rawSelector)})`;
1577
+ }
1578
+ /** 0-based column of the first non-whitespace char on a 1-based line. */
1579
+ function firstNonWhitespaceColumn(text, line1Based) {
1580
+ const lines = text.split(/\r?\n/);
1581
+ const idx = line1Based - 1;
1582
+ if (idx < 0 || idx >= lines.length)
1583
+ return 0;
1584
+ const m = lines[idx].match(/^(\s*)/);
1585
+ return m ? m[1].length : 0;
1586
+ }
1556
1587
  class ASTSourceUpdater {
1557
1588
  project;
1558
1589
  advisoryBuffer = new HealingAdvisory_1.AdvisoryBuffer();
1559
1590
  flushAdvisories() {
1560
1591
  this.advisoryBuffer.flush();
1561
1592
  }
1593
+ // ═══════════════════════════════════════════════════════════════════
1594
+ // STAGE 4 — end-to-end trace → decide → apply (Stages 1-3 wired in)
1595
+ //
1596
+ // Additive entry point. The legacy heal paths below are untouched; this
1597
+ // is the new write-disciplined pipeline that composes AnchorResolver,
1598
+ // TraceBackEngine, DecisionEngine and MutationApplier.
1599
+ // ═══════════════════════════════════════════════════════════════════
1600
+ /**
1601
+ * Trace the failing call-site back to its source declaration, decide the
1602
+ * mutation strategy (in-place / fork / prompt), and — when `apply` is set —
1603
+ * write it to disk through the type-safety gate.
1604
+ */
1605
+ static composeHeal(input) {
1606
+ const original = fs.readFileSync(input.filePath, "utf8");
1607
+ const project = new ts_morph_1.Project({
1608
+ useInMemoryFileSystem: true,
1609
+ compilerOptions: {
1610
+ allowJs: true,
1611
+ noEmit: true,
1612
+ skipLibCheck: true,
1613
+ noResolve: true,
1614
+ },
1615
+ });
1616
+ const sf = project.createSourceFile(input.filePath, original, {
1617
+ overwrite: true,
1618
+ });
1619
+ // Resolve a robust position: when no explicit column is given, use the
1620
+ // first non-whitespace char of the line (avoids landing on indentation).
1621
+ const column = input.column ?? firstNonWhitespaceColumn(original, input.line);
1622
+ const trace = TraceBackEngine_1.TraceBackEngine.traceFromPosition(sf, input.line, column);
1623
+ if (trace.status !== "target") {
1624
+ return { kind: "abort", trace };
1625
+ }
1626
+ // Re-resolve the node at the failing position for fork test-block lookup.
1627
+ let failingNode;
1628
+ try {
1629
+ failingNode = sf.getDescendantAtPos(sf.compilerNode.getPositionOfLineAndCharacter(input.line - 1, column));
1630
+ }
1631
+ catch {
1632
+ failingNode = undefined;
1633
+ }
1634
+ if (!failingNode)
1635
+ failingNode = trace.target.targetNode;
1636
+ // Build the replacement EXPRESSION. Explicit newLocatorExpr wins; else
1637
+ // synthesise it from the raw healed selector + the traced root receiver,
1638
+ // translating internal engine selectors to their idiomatic method form.
1639
+ const newLocatorExpr = input.newLocatorExpr ??
1640
+ buildLocatorExpr(input.newSelector ?? "", trace.target.rootReceiver);
1641
+ const decision = DecisionEngine_1.DecisionEngine.decide({
1642
+ target: trace.target,
1643
+ failingCallSite: failingNode,
1644
+ newLocatorExpr,
1645
+ contextSpecific: input.contextSpecific ?? false,
1646
+ globalCorrect: input.globalCorrect ?? false,
1647
+ });
1648
+ const batch = ASTSourceUpdater.decisionToBatch(input.filePath, decision);
1649
+ const result = {
1650
+ kind: "decision",
1651
+ trace,
1652
+ decision,
1653
+ batch,
1654
+ writtenLine: trace.target.targetNode.getStartLineNumber(),
1655
+ };
1656
+ if (input.apply && batch.edits.length > 0) {
1657
+ result.outcome = MutationApplier_1.MutationApplier.applyFileBatch(batch, input.applyOptions ?? {});
1658
+ }
1659
+ return result;
1660
+ }
1661
+ /** Drain-loop entry: apply pre-built batches through the write gate. */
1662
+ static applyMutationBatches(batches, opts = {}) {
1663
+ return MutationApplier_1.MutationApplier.applyBatches(batches, opts);
1664
+ }
1665
+ /** Convert a DecisionEngine result into an anchored MutationApplier batch. */
1666
+ static decisionToBatch(filePath, decision) {
1667
+ const edits = decision.edits.map((e) => {
1668
+ if (e.kind === "insert") {
1669
+ return {
1670
+ kind: "insert",
1671
+ replacement: e.replacement,
1672
+ span: e.span,
1673
+ requireLocator: false,
1674
+ };
1675
+ }
1676
+ // MUTATE_IN_PLACE replace → anchor-resolved + Locator-type-gated.
1677
+ if (decision.strategy === "MUTATE_IN_PLACE") {
1678
+ return {
1679
+ kind: "replace",
1680
+ replacement: e.replacement,
1681
+ anchor: decision.anchor,
1682
+ requireLocator: true,
1683
+ };
1684
+ }
1685
+ // FORK call-site receiver rewrite → identifier, not a locator.
1686
+ return {
1687
+ kind: "replace",
1688
+ replacement: e.replacement,
1689
+ span: e.span,
1690
+ requireLocator: false,
1691
+ };
1692
+ });
1693
+ return { filePath, edits };
1694
+ }
1562
1695
  /**
1563
1696
  * Centralised write hook for every ts-morph `saveSync` call in this class.
1564
1697
  * 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
1698
+ * on-disk file is left untouched - the heal pipeline can still compute a
1566
1699
  * candidate diff via `sourceFile.getFullText()` and the report layer
1567
1700
  * tags the event with `dryRun: true`.
1568
1701
  */
@@ -1579,7 +1712,7 @@ class ASTSourceUpdater {
1579
1712
  // BEFORE the LLM call and record a FailedEvent in the "Skipped by
1580
1713
  // Developer" category.
1581
1714
  //
1582
- // Implementation note ts-morph's `Node.getLeadingCommentRanges()`
1715
+ // Implementation note - ts-morph's `Node.getLeadingCommentRanges()`
1583
1716
  // returns `ts.CommentRange[]` (offsets, not text). We slice the full
1584
1717
  // source text by `(pos, end)` to recover the raw comment for each
1585
1718
  // range, matching both `//` line comments and `/* */` block comments
@@ -1643,7 +1776,7 @@ class ASTSourceUpdater {
1643
1776
  return { failFast: false };
1644
1777
  }
1645
1778
  /**
1646
- * Convenience helper runs `scanFailFastDirective` and throws the
1779
+ * Convenience helper - runs `scanFailFastDirective` and throws the
1647
1780
  * canonical SelaError when the directive is present. Used by SelaEngine
1648
1781
  * as the very first gate inside `heal()`.
1649
1782
  */
@@ -1790,7 +1923,7 @@ class ASTSourceUpdater {
1790
1923
  sourceFile = this.project.addSourceFileAtPath(filePath);
1791
1924
  }
1792
1925
  if (caller.line <= 0) {
1793
- console.log(`[ASTUpdater] ⚠️ line ${caller.line} invalid Attempting Global Template Strategy`);
1926
+ console.log(`[ASTUpdater] ⚠️ line ${caller.line} invalid - Attempting Global Template Strategy`);
1794
1927
  const globalResult = this.strategyTemplateLiteral(sourceFile, caller, oldSelector, newSelector);
1795
1928
  if (globalResult && globalResult.success) {
1796
1929
  if (contentChange)
@@ -1814,7 +1947,7 @@ class ASTSourceUpdater {
1814
1947
  // node when they differ) for `// sela-fail-fast`. When present, abort
1815
1948
  // all heal work with SelaError(AST_HEAL_DISABLED_BY_DIRECTIVE) so the
1816
1949
  // engine records a FailedEvent in the "Skipped by Developer" bucket.
1817
- // Defense-in-depth SelaEngine also calls this at the very start of
1950
+ // Defense-in-depth - SelaEngine also calls this at the very start of
1818
1951
  // heal() to skip the LLM round-trip entirely.
1819
1952
  this.enforceFailFastDirective(caller);
1820
1953
  const suspectIdentifier = statement
@@ -1827,7 +1960,7 @@ class ASTSourceUpdater {
1827
1960
  // HARD EXIT: any non-null result (success OR failure) stops all
1828
1961
  // subsequent AST strategies. When the locator argument is an
1829
1962
  // Identifier, PropertyAccess, or TemplateLiteral, the gate owns
1830
- // the mutation NT/H must never override the symbolic reference.
1963
+ // the mutation - NT/H must never override the symbolic reference.
1831
1964
  const semanticResult = this.semanticDispatch(sourceFile, caller, oldSelector, newSelector);
1832
1965
  if (semanticResult !== null) {
1833
1966
  if (semanticResult.success) {
@@ -1836,7 +1969,7 @@ class ASTSourceUpdater {
1836
1969
  }
1837
1970
  return this.broadcastAndReturn(semanticResult, oldSelector, newSelector, caller);
1838
1971
  }
1839
- // Failure (blast-radius block, trace failed, syntax error) hard stop.
1972
+ // Failure (blast-radius block, trace failed, syntax error) - hard stop.
1840
1973
  console.log(`[SemanticDispatch] 🚫 Hard stop (${semanticResult.strategy}): ${semanticResult.reason}`);
1841
1974
  return semanticResult;
1842
1975
  }
@@ -1885,7 +2018,7 @@ class ASTSourceUpdater {
1885
2018
  };
1886
2019
  }
1887
2020
  // ═══════════════════════════════════════════════════════════════
1888
- // STRATEGY H Smart Chain (Legacy fallback)
2021
+ // STRATEGY H - Smart Chain (Legacy fallback)
1889
2022
  // ═══════════════════════════════════════════════════════════════
1890
2023
  strategySmartChain(sourceFile, caller, oldSelector, oldSem, smartSegments, originalChainHint, suspectIdentifier) {
1891
2024
  console.log(`[ASTUpdater] 🔗 Strategy H v2: Smart Chain (${smartSegments.length} segments)`);
@@ -1959,13 +2092,14 @@ class ASTSourceUpdater {
1959
2092
  return b.chainDepth - a.chainDepth;
1960
2093
  });
1961
2094
  for (const { leafCall, chainTop, rootReceiver, chainDepth, originalText, } of candidates) {
1962
- // Identifier/PropertyAccess guard mirrors the NT guard.
2095
+ // Identifier/PropertyAccess guard - mirrors the NT guard.
1963
2096
  // Strategy H must never substitute a hardcoded string for a symbolic
1964
2097
  // reference; the semantic dispatch gate owns definition-site mutations.
1965
2098
  const firstArgH = leafCall.getArguments()[0];
1966
2099
  if (firstArgH &&
1967
- (ts_morph_1.Node.isIdentifier(firstArgH) || ts_morph_1.Node.isPropertyAccessExpression(firstArgH))) {
1968
- console.log(`[StrategyH] 🛑 skip — first arg is ${firstArgH.getKindName()} ` +
2100
+ (ts_morph_1.Node.isIdentifier(firstArgH) ||
2101
+ ts_morph_1.Node.isPropertyAccessExpression(firstArgH))) {
2102
+ console.log(`[StrategyH] 🛑 skip - first arg is ${firstArgH.getKindName()} ` +
1969
2103
  `"${firstArgH.getText()}" (semantic dispatch owns definition-site mutations)`);
1970
2104
  continue;
1971
2105
  }
@@ -2035,8 +2169,9 @@ class ASTSourceUpdater {
2035
2169
  // Block H-partial from substituting a literal for an Identifier/PropertyAccess arg.
2036
2170
  const targetFirstArg = targetCall.getArguments()[0];
2037
2171
  if (targetFirstArg &&
2038
- (ts_morph_1.Node.isIdentifier(targetFirstArg) || ts_morph_1.Node.isPropertyAccessExpression(targetFirstArg))) {
2039
- console.log(`[H-partial] 🛑 skip — arg is ${targetFirstArg.getKindName()} ` +
2172
+ (ts_morph_1.Node.isIdentifier(targetFirstArg) ||
2173
+ ts_morph_1.Node.isPropertyAccessExpression(targetFirstArg))) {
2174
+ console.log(`[H-partial] 🛑 skip - arg is ${targetFirstArg.getKindName()} ` +
2040
2175
  `"${targetFirstArg.getText()}" (semantic dispatch owns this)`);
2041
2176
  return null;
2042
2177
  }
@@ -2072,7 +2207,7 @@ class ASTSourceUpdater {
2072
2207
  }
2073
2208
  }
2074
2209
  // ─────────────────────────────────────────────────────────────
2075
- // STRATEGY F Chain Collapse
2210
+ // STRATEGY F - Chain Collapse
2076
2211
  // ─────────────────────────────────────────────────────────────
2077
2212
  strategyChainCollapse(sourceFile, caller, oldSelector, newSelector, oldSem, newSem) {
2078
2213
  console.log(`[ASTUpdater] ⛓️ Strategy F: Chain Collapse (upward traversal)`);
@@ -2138,7 +2273,7 @@ class ASTSourceUpdater {
2138
2273
  };
2139
2274
  }
2140
2275
  // ─────────────────────────────────────────────────────────────
2141
- // STRATEGY G Assertion Healing
2276
+ // STRATEGY G - Assertion Healing
2142
2277
  // ─────────────────────────────────────────────────────────────
2143
2278
  runStrategyG(sourceFile, caller, locatorVarName, contentChange) {
2144
2279
  if (!contentChange)
@@ -2170,7 +2305,7 @@ class ASTSourceUpdater {
2170
2305
  return null;
2171
2306
  }
2172
2307
  // ─────────────────────────────────────────────────────────────
2173
- // STRATEGY A Frame Upstream Healing
2308
+ // STRATEGY A - Frame Upstream Healing
2174
2309
  // ─────────────────────────────────────────────────────────────
2175
2310
  strategyFrameUpstream(sourceFile, caller, oldElementSelector, aiSegments, fullSelectorContext) {
2176
2311
  console.log(`[ASTUpdater] 🧠 Strategy A: Frame Upstream`);
@@ -2246,7 +2381,7 @@ class ASTSourceUpdater {
2246
2381
  };
2247
2382
  }
2248
2383
  // ─────────────────────────────────────────────────────────────
2249
- // STRATEGY B Deep Symbol Resolution
2384
+ // STRATEGY B - Deep Symbol Resolution
2250
2385
  // ─────────────────────────────────────────────────────────────
2251
2386
  strategyDeepSymbolResolution(sourceFile, caller, oldSelector, newSelector, oldSem, newSem, suspectIdentifier) {
2252
2387
  console.log(`[ASTUpdater] 🔭 Strategy B: Deep Symbol Resolution`);
@@ -2430,7 +2565,7 @@ class ASTSourceUpdater {
2430
2565
  };
2431
2566
  }
2432
2567
  // ─────────────────────────────────────────────────────────────
2433
- // STRATEGY C Inline Semantic Fix
2568
+ // STRATEGY C - Inline Semantic Fix
2434
2569
  // ─────────────────────────────────────────────────────────────
2435
2570
  strategyInlineSemanticFix(sourceFile, caller, oldSelector, newSelector, oldSem, newSem) {
2436
2571
  console.log(`[ASTUpdater] 🎯 Strategy C: Inline Semantic Fix`);
@@ -2488,7 +2623,7 @@ class ASTSourceUpdater {
2488
2623
  };
2489
2624
  }
2490
2625
  // ─────────────────────────────────────────────────────────────
2491
- // STRATEGY D Template Literal
2626
+ // STRATEGY D - Template Literal
2492
2627
  // ─────────────────────────────────────────────────────────────
2493
2628
  strategyTemplateLiteral(sourceFile, caller, oldSelector, newSelector) {
2494
2629
  console.log(`[ASTUpdater] 🔤 Strategy D: Template Literal`);
@@ -2545,7 +2680,7 @@ class ASTSourceUpdater {
2545
2680
  };
2546
2681
  }
2547
2682
  // ─────────────────────────────────────────────────────────────
2548
- // STRATEGY E Semantic Fragment Scan
2683
+ // STRATEGY E - Semantic Fragment Scan
2549
2684
  // ─────────────────────────────────────────────────────────────
2550
2685
  strategySemanticFragmentScan(sourceFile, caller, oldSelector, newSelector, oldSem, newSem) {
2551
2686
  console.log(`[ASTUpdater] 🔎 Strategy E: Semantic Fragment Scan`);
@@ -2666,7 +2801,7 @@ class ASTSourceUpdater {
2666
2801
  /**
2667
2802
  * Guard: returns false when replacing oldNode with proposedNewText would
2668
2803
  * substitute a hardcoded string literal for an Identifier or
2669
- * PropertyAccessExpression which is the definition-site mutation bug.
2804
+ * PropertyAccessExpression - which is the definition-site mutation bug.
2670
2805
  * All other replacements are allowed.
2671
2806
  */
2672
2807
  static isSafeReplacement(oldNode, proposedNewText) {
@@ -2690,7 +2825,7 @@ class ASTSourceUpdater {
2690
2825
  firstArg.replaceWithText(`"${safe}"`);
2691
2826
  }
2692
2827
  else {
2693
- console.warn(`[ASTUpdater] ⚠️ replaceCallArgument: blocked unsafe replacement ` +
2828
+ console.warn(`[ASTUpdater] ⚠️ replaceCallArgument: blocked unsafe replacement - ` +
2694
2829
  `arg is ${firstArg.getKindName()}, not a string literal. ` +
2695
2830
  `Heal at definition site instead.`);
2696
2831
  return false;
@@ -2992,10 +3127,10 @@ class ASTSourceUpdater {
2992
3127
  shape === "PROPERTY_ACCESS" ||
2993
3128
  shape === "TEMPLATE_LITERAL") {
2994
3129
  console.log(`[SemanticDispatch] 🛑 hard-block: "${node.getText()}" is ${shape} ` +
2995
- `but trace failed preserving call site`);
3130
+ `but trace failed - preserving call site`);
2996
3131
  return {
2997
3132
  success: false,
2998
- reason: `semantic guard: could not trace "${node.getText()}" call site preserved`,
3133
+ reason: `semantic guard: could not trace "${node.getText()}" - call site preserved`,
2999
3134
  strategy: "semantic-guard",
3000
3135
  };
3001
3136
  }
@@ -3007,6 +3142,72 @@ class ASTSourceUpdater {
3007
3142
  return null;
3008
3143
  }
3009
3144
  }
3145
+ // ═══════════════════════════════════════════════════════════════════
3146
+ // DEFINITION-SITE WRITE (with internal→public Method-Shift fallback)
3147
+ //
3148
+ // A `const X = "selector"` consumed by `.locator(X)` cannot hold a semantic
3149
+ // locator (page.getByRole(...) would reference `page` out of scope at the
3150
+ // declaration). So when the AI heals to an internal engine selector
3151
+ // (`internal:role=…`), we DON'T abort and we DON'T leak the engine string:
3152
+ // we translate it to a VALID PUBLIC selector string (`role=button[name="…"]`)
3153
+ // and write THAT into the constant — keeping the developer's architecture
3154
+ // intact — while surfacing the cleaner semantic method form (getByRole) as
3155
+ // a suggested upgrade in the advisory/report.
3156
+ // ═══════════════════════════════════════════════════════════════════
3157
+ applyDefinitionSiteHeal(targetNode, targetFile, newSelector, strategy, blastRadius) {
3158
+ const wasInternal = (0, SelectorSanitizer_1.isInternalEngineSelector)(newSelector);
3159
+ let writeValue = newSelector;
3160
+ if (wasInternal) {
3161
+ const publicStr = (0, SelectorSanitizer_1.internalToPublicSelectorString)(newSelector);
3162
+ if (!publicStr) {
3163
+ // No public string engine (e.g. label association) → a .locator()
3164
+ // string constant genuinely cannot express it. Preserve the call site.
3165
+ return {
3166
+ success: false,
3167
+ reason: `semantic guard: internal selector "${newSelector.slice(0, 48)}" has no public ` +
3168
+ `.locator() string form - call site preserved (method-shift required)`,
3169
+ strategy: "semantic-guard",
3170
+ };
3171
+ }
3172
+ writeValue = publicStr;
3173
+ console.log(`[SemanticDispatch] 🔁 Method-Shift fallback: internal selector → public string "${writeValue}"`);
3174
+ }
3175
+ const result = InitializerUpdater_1.InitializerUpdater.apply(targetNode, targetFile, writeValue);
3176
+ if (!result)
3177
+ return null;
3178
+ if (!this.validateFileSyntax(targetFile)) {
3179
+ return {
3180
+ success: false,
3181
+ reason: "post-mutation syntax validation failed - reverted",
3182
+ strategy,
3183
+ };
3184
+ }
3185
+ if (wasInternal) {
3186
+ const methodExpr = (0, SelectorSanitizer_1.internalToMethodExpression)(newSelector, "page");
3187
+ this.advisoryBuffer.push({
3188
+ code: HealingAdvisory_1.AdvisoryCode.SEMANTIC_UPGRADE_SUGGESTED,
3189
+ message: `Healed constant to public selector string "${writeValue}". ` +
3190
+ `Cleaner semantic form: ${methodExpr ?? "a getBy* method"}.`,
3191
+ affectedFile: targetFile.getFilePath(),
3192
+ affectedLine: result.lineUpdated,
3193
+ suggestedAction: `For more resilient code, rewrite the call site to ${methodExpr ?? "the semantic locator"} ` +
3194
+ `instead of the string constant.`,
3195
+ });
3196
+ }
3197
+ return {
3198
+ success: true,
3199
+ reason: `definition-site heal via ${strategy} trace` +
3200
+ (wasInternal ? " (internal→public string; semantic upgrade suggested)" : ""),
3201
+ strategy,
3202
+ lineUpdated: result.lineUpdated,
3203
+ healedLocatorString: result.healedLocatorString,
3204
+ definitionSite: {
3205
+ file: targetFile.getFilePath(),
3206
+ line: result.lineUpdated,
3207
+ },
3208
+ blastRadius,
3209
+ };
3210
+ }
3010
3211
  handleIdentifierArg(node, sourceFile, caller, newSelector) {
3011
3212
  const traceResult = DefinitionTracer_1.DefinitionTracer.traceIdentifier(node, sourceFile, caller.line);
3012
3213
  if (traceResult.found) {
@@ -3018,45 +3219,27 @@ class ASTSourceUpdater {
3018
3219
  if (blast.risk === "high") {
3019
3220
  this.advisoryBuffer.push({
3020
3221
  code: HealingAdvisory_1.AdvisoryCode.BLAST_RADIUS_BLOCKED,
3021
- message: `Mutation blocked ${blast.testFunctionCount} tests reference this constant.`,
3222
+ message: `Mutation blocked - ${blast.testFunctionCount} tests reference this constant.`,
3022
3223
  affectedFile: targetFile.getFilePath(),
3023
3224
  affectedLine: targetNode.getStartLineNumber() - 1,
3024
3225
  suggestedAction: "Review all referencing tests before changing this constant.",
3025
3226
  });
3026
3227
  return {
3027
3228
  success: false,
3028
- reason: `BlastRadius BLOCKED: ${blast.testFunctionCount} tests reference this constant manual review required`,
3229
+ reason: `BlastRadius BLOCKED: ${blast.testFunctionCount} tests reference this constant - manual review required`,
3029
3230
  strategy: "semantic-identifier",
3030
3231
  };
3031
3232
  }
3032
3233
  if (blast.risk === "medium") {
3033
3234
  this.advisoryBuffer.push({
3034
3235
  code: HealingAdvisory_1.AdvisoryCode.BLAST_RADIUS_WARNING,
3035
- message: `Mutated constant used in ${blast.testFunctionCount} tests verify no regressions.`,
3236
+ message: `Mutated constant used in ${blast.testFunctionCount} tests - verify no regressions.`,
3036
3237
  affectedFile: targetFile.getFilePath(),
3037
3238
  affectedLine: targetNode.getStartLineNumber() - 1,
3038
3239
  suggestedAction: "Run the full test suite to confirm all references still pass.",
3039
3240
  });
3040
3241
  }
3041
- const result = InitializerUpdater_1.InitializerUpdater.apply(targetNode, targetFile, newSelector);
3042
- if (!result)
3043
- return null;
3044
- if (!this.validateFileSyntax(targetFile)) {
3045
- return {
3046
- success: false,
3047
- reason: "post-mutation syntax validation failed — reverted",
3048
- strategy: "semantic-identifier",
3049
- };
3050
- }
3051
- return {
3052
- success: true,
3053
- reason: `definition-site heal via identifier trace`,
3054
- strategy: "semantic-identifier",
3055
- lineUpdated: result.lineUpdated,
3056
- healedLocatorString: result.healedLocatorString,
3057
- definitionSite: { file: targetFile.getFilePath(), line: result.lineUpdated },
3058
- blastRadius: blast.testFunctionCount,
3059
- };
3242
+ return this.applyDefinitionSiteHeal(targetNode, targetFile, newSelector, "semantic-identifier", blast.testFunctionCount);
3060
3243
  }
3061
3244
  if (!traceResult.found && traceResult.crossFile) {
3062
3245
  const xResult = CrossFileHealer_1.CrossFileHealer.heal(traceResult.modulePath, traceResult.symbolName, newSelector, caller.filePath, this.project);
@@ -3083,45 +3266,27 @@ class ASTSourceUpdater {
3083
3266
  if (blast.risk === "high") {
3084
3267
  this.advisoryBuffer.push({
3085
3268
  code: HealingAdvisory_1.AdvisoryCode.BLAST_RADIUS_BLOCKED,
3086
- message: `Mutation blocked ${blast.testFunctionCount} tests reference this property.`,
3269
+ message: `Mutation blocked - ${blast.testFunctionCount} tests reference this property.`,
3087
3270
  affectedFile: targetFile.getFilePath(),
3088
3271
  affectedLine: targetNode.getStartLineNumber() - 1,
3089
3272
  suggestedAction: "Review all referencing tests before changing this property.",
3090
3273
  });
3091
3274
  return {
3092
3275
  success: false,
3093
- reason: `BlastRadius BLOCKED: ${blast.testFunctionCount} tests reference this property manual review required`,
3276
+ reason: `BlastRadius BLOCKED: ${blast.testFunctionCount} tests reference this property - manual review required`,
3094
3277
  strategy: "semantic-property-access",
3095
3278
  };
3096
3279
  }
3097
3280
  if (blast.risk === "medium") {
3098
3281
  this.advisoryBuffer.push({
3099
3282
  code: HealingAdvisory_1.AdvisoryCode.BLAST_RADIUS_WARNING,
3100
- message: `Mutated property used in ${blast.testFunctionCount} tests verify no regressions.`,
3283
+ message: `Mutated property used in ${blast.testFunctionCount} tests - verify no regressions.`,
3101
3284
  affectedFile: targetFile.getFilePath(),
3102
3285
  affectedLine: targetNode.getStartLineNumber() - 1,
3103
3286
  suggestedAction: "Run the full test suite to confirm all references still pass.",
3104
3287
  });
3105
3288
  }
3106
- const result = InitializerUpdater_1.InitializerUpdater.apply(targetNode, targetFile, newSelector);
3107
- if (!result)
3108
- return null;
3109
- if (!this.validateFileSyntax(targetFile)) {
3110
- return {
3111
- success: false,
3112
- reason: "post-mutation syntax validation failed — reverted",
3113
- strategy: "semantic-property-access",
3114
- };
3115
- }
3116
- return {
3117
- success: true,
3118
- reason: `definition-site heal via property access trace`,
3119
- strategy: "semantic-property-access",
3120
- lineUpdated: result.lineUpdated,
3121
- healedLocatorString: result.healedLocatorString,
3122
- definitionSite: { file: targetFile.getFilePath(), line: result.lineUpdated },
3123
- blastRadius: blast.testFunctionCount,
3124
- };
3289
+ return this.applyDefinitionSiteHeal(targetNode, targetFile, newSelector, "semantic-property-access", blast.testFunctionCount);
3125
3290
  }
3126
3291
  if (!traceResult.found && traceResult.crossFile) {
3127
3292
  const xResult = CrossFileHealer_1.CrossFileHealer.heal(traceResult.modulePath, traceResult.symbolName, newSelector, caller.filePath, this.project);
@@ -3163,10 +3328,11 @@ class ASTSourceUpdater {
3163
3328
  }
3164
3329
  }
3165
3330
  }
3166
- if (patchResult.reason.includes("structural") || patchResult.reason.includes("not found")) {
3331
+ if (patchResult.reason.includes("structural") ||
3332
+ patchResult.reason.includes("not found")) {
3167
3333
  this.advisoryBuffer.push({
3168
3334
  code: HealingAdvisory_1.AdvisoryCode.MANUAL_REVIEW_TEMPLATE_COMPLEX,
3169
- message: `Template literal requires structural change automated patch skipped.`,
3335
+ message: `Template literal requires structural change - automated patch skipped.`,
3170
3336
  affectedFile: sourceFile.getFilePath(),
3171
3337
  affectedLine: node.getStartLineNumber() - 1,
3172
3338
  suggestedAction: "Manually update the template literal and its callers.",
@@ -3180,7 +3346,7 @@ class ASTSourceUpdater {
3180
3346
  if (!this.validateFileSyntax(sourceFile)) {
3181
3347
  return {
3182
3348
  success: false,
3183
- reason: "post-mutation syntax validation failed reverted",
3349
+ reason: "post-mutation syntax validation failed - reverted",
3184
3350
  strategy: "semantic-template",
3185
3351
  };
3186
3352
  }
@@ -3208,7 +3374,8 @@ class ASTSourceUpdater {
3208
3374
  if (ts_morph_1.Node.isCallExpression(init)) {
3209
3375
  return this.handleCallExpressionArg(init, sourceFile, oldSpanVal, newSpanVal);
3210
3376
  }
3211
- if (ts_morph_1.Node.isStringLiteral(init) || ts_morph_1.Node.isNoSubstitutionTemplateLiteral(init)) {
3377
+ if (ts_morph_1.Node.isStringLiteral(init) ||
3378
+ ts_morph_1.Node.isNoSubstitutionTemplateLiteral(init)) {
3212
3379
  const result = InitializerUpdater_1.InitializerUpdater.apply(init, sourceFile, newSpanVal);
3213
3380
  if (!result)
3214
3381
  return null;
@@ -3219,7 +3386,10 @@ class ASTSourceUpdater {
3219
3386
  strategy: "semantic-template",
3220
3387
  lineUpdated: result.lineUpdated,
3221
3388
  healedLocatorString: result.healedLocatorString,
3222
- definitionSite: { file: sourceFile.getFilePath(), line: result.lineUpdated },
3389
+ definitionSite: {
3390
+ file: sourceFile.getFilePath(),
3391
+ line: result.lineUpdated,
3392
+ },
3223
3393
  };
3224
3394
  }
3225
3395
  if (ts_morph_1.Node.isTemplateExpression(init)) {
@@ -3233,7 +3403,7 @@ class ASTSourceUpdater {
3233
3403
  if (fnAnalysis.kind === "complex") {
3234
3404
  this.advisoryBuffer.push({
3235
3405
  code: HealingAdvisory_1.AdvisoryCode.MANUAL_REVIEW_CONDITIONAL_RETURN,
3236
- message: `Function has conditional/complex return automated patch skipped. Reason: ${fnAnalysis.reason}`,
3406
+ message: `Function has conditional/complex return - automated patch skipped. Reason: ${fnAnalysis.reason}`,
3237
3407
  affectedFile: sourceFile.getFilePath(),
3238
3408
  suggestedAction: "Manually update the function's return expression.",
3239
3409
  });
@@ -3246,7 +3416,7 @@ class ASTSourceUpdater {
3246
3416
  if (!this.validateFileSyntax(fnAnalysis.functionFile)) {
3247
3417
  return {
3248
3418
  success: false,
3249
- reason: "post-mutation syntax validation failed reverted",
3419
+ reason: "post-mutation syntax validation failed - reverted",
3250
3420
  strategy: "semantic-call-expression",
3251
3421
  };
3252
3422
  }
@@ -3256,7 +3426,10 @@ class ASTSourceUpdater {
3256
3426
  strategy: "semantic-call-expression",
3257
3427
  lineUpdated: result.lineUpdated,
3258
3428
  healedLocatorString: result.healedLocatorString,
3259
- definitionSite: { file: fnAnalysis.functionFile.getFilePath(), line: result.lineUpdated },
3429
+ definitionSite: {
3430
+ file: fnAnalysis.functionFile.getFilePath(),
3431
+ line: result.lineUpdated,
3432
+ },
3260
3433
  };
3261
3434
  }
3262
3435
  if (fnAnalysis.kind === "template") {
@@ -3266,7 +3439,7 @@ class ASTSourceUpdater {
3266
3439
  if (patchResult.reason.includes("structural")) {
3267
3440
  this.advisoryBuffer.push({
3268
3441
  code: HealingAdvisory_1.AdvisoryCode.MANUAL_REVIEW_TEMPLATE_COMPLEX,
3269
- message: `Function template return requires structural change automated patch skipped.`,
3442
+ message: `Function template return requires structural change - automated patch skipped.`,
3270
3443
  affectedFile: fnAnalysis.functionFile.getFilePath(),
3271
3444
  affectedLine: fnAnalysis.functionLine - 1,
3272
3445
  suggestedAction: "Manually update the function's template literal.",
@@ -3280,7 +3453,7 @@ class ASTSourceUpdater {
3280
3453
  if (!this.validateFileSyntax(fnAnalysis.functionFile)) {
3281
3454
  return {
3282
3455
  success: false,
3283
- reason: "post-mutation syntax validation failed reverted",
3456
+ reason: "post-mutation syntax validation failed - reverted",
3284
3457
  strategy: "semantic-call-expression",
3285
3458
  };
3286
3459
  }
@@ -3290,7 +3463,10 @@ class ASTSourceUpdater {
3290
3463
  strategy: "semantic-call-expression",
3291
3464
  lineUpdated,
3292
3465
  healedLocatorString: newSelector,
3293
- definitionSite: { file: fnAnalysis.functionFile.getFilePath(), line: lineUpdated },
3466
+ definitionSite: {
3467
+ file: fnAnalysis.functionFile.getFilePath(),
3468
+ line: lineUpdated,
3469
+ },
3294
3470
  };
3295
3471
  }
3296
3472
  return null;
@@ -3306,11 +3482,13 @@ class ASTSourceUpdater {
3306
3482
  return !ALLOWED_CODES.has(d.getCode());
3307
3483
  });
3308
3484
  if (syntaxErrors.length > 0) {
3309
- const msgs = syntaxErrors.map((d) => String(d.getMessageText())).join("; ");
3310
- console.warn(`[SemanticDispatch] ⚠️ syntax errors after mutation: ${msgs} — reverting`);
3485
+ const msgs = syntaxErrors
3486
+ .map((d) => String(d.getMessageText()))
3487
+ .join("; ");
3488
+ console.warn(`[SemanticDispatch] ⚠️ syntax errors after mutation: ${msgs} - reverting`);
3311
3489
  this.advisoryBuffer.push({
3312
3490
  code: HealingAdvisory_1.AdvisoryCode.POST_MUTATION_TYPE_ERROR,
3313
- message: `Post-mutation type errors detected mutation reverted. Errors: ${msgs}`,
3491
+ message: `Post-mutation type errors detected - mutation reverted. Errors: ${msgs}`,
3314
3492
  affectedFile: sourceFile.getFilePath(),
3315
3493
  suggestedAction: "Manually inspect and fix the type errors introduced by this heal.",
3316
3494
  });