sela-core 0.1.0-alpha.39 → 0.1.0-alpha.41

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 (97) hide show
  1. package/dist/boot/bootImpl.d.ts +14 -0
  2. package/dist/boot/bootImpl.d.ts.map +1 -0
  3. package/dist/boot/bootImpl.js +68 -0
  4. package/dist/boot/register.cjs +45 -0
  5. package/dist/boot/register.d.cts +2 -0
  6. package/dist/boot/register.d.cts.map +1 -0
  7. package/dist/boot/register.d.mts +2 -0
  8. package/dist/boot/register.d.mts.map +1 -0
  9. package/dist/boot/register.mjs +10 -0
  10. package/dist/cli/commands/init.js +48 -16
  11. package/dist/cli/commands/merge.d.ts.map +1 -1
  12. package/dist/cli/commands/merge.js +5 -0
  13. package/dist/cli/commands/shadowRegression.js +1 -1
  14. package/dist/config/ConfigLoader.d.ts.map +1 -1
  15. package/dist/config/ConfigLoader.js +33 -12
  16. package/dist/config/SelaConfig.d.ts +59 -0
  17. package/dist/config/SelaConfig.d.ts.map +1 -1
  18. package/dist/config/SelaConfig.js +36 -0
  19. package/dist/engine/SelaEngine.d.ts +3 -1
  20. package/dist/engine/SelaEngine.d.ts.map +1 -1
  21. package/dist/engine/SelaEngine.js +193 -3
  22. package/dist/errors/SelaError.d.ts +7 -0
  23. package/dist/errors/SelaError.d.ts.map +1 -1
  24. package/dist/errors/SelaError.js +7 -1
  25. package/dist/fixtures/index.d.ts.map +1 -1
  26. package/dist/fixtures/index.js +5 -4
  27. package/dist/providers/AnthropicProvider.d.ts +9 -0
  28. package/dist/providers/AnthropicProvider.d.ts.map +1 -0
  29. package/dist/providers/AnthropicProvider.js +33 -0
  30. package/dist/providers/LLMProvider.d.ts +13 -0
  31. package/dist/providers/LLMProvider.d.ts.map +1 -0
  32. package/dist/providers/LLMProvider.js +7 -0
  33. package/dist/providers/OpenAICompatibleProvider.d.ts +8 -0
  34. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  35. package/dist/providers/OpenAICompatibleProvider.js +48 -0
  36. package/dist/providers/ProviderFactory.d.ts +4 -0
  37. package/dist/providers/ProviderFactory.d.ts.map +1 -0
  38. package/dist/providers/ProviderFactory.js +50 -0
  39. package/dist/providers/SanitizingProvider.d.ts +8 -0
  40. package/dist/providers/SanitizingProvider.d.ts.map +1 -0
  41. package/dist/providers/SanitizingProvider.js +27 -0
  42. package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
  43. package/dist/services/ASTSourceUpdater.js +39 -5
  44. package/dist/services/CrossFileHealer.d.ts +7 -1
  45. package/dist/services/CrossFileHealer.d.ts.map +1 -1
  46. package/dist/services/CrossFileHealer.js +243 -9
  47. package/dist/services/DefinitionTracer.d.ts +1 -0
  48. package/dist/services/DefinitionTracer.d.ts.map +1 -1
  49. package/dist/services/DefinitionTracer.js +59 -36
  50. package/dist/services/HealReportService.d.ts +32 -0
  51. package/dist/services/HealReportService.d.ts.map +1 -1
  52. package/dist/services/HealReportService.js +111 -18
  53. package/dist/services/IntentAuditor.d.ts +9 -7
  54. package/dist/services/IntentAuditor.d.ts.map +1 -1
  55. package/dist/services/IntentAuditor.js +21 -16
  56. package/dist/services/LLMService.d.ts +10 -7
  57. package/dist/services/LLMService.d.ts.map +1 -1
  58. package/dist/services/LLMService.js +76 -10
  59. package/dist/services/PRAutomationService.d.ts +7 -2
  60. package/dist/services/PRAutomationService.d.ts.map +1 -1
  61. package/dist/services/PRAutomationService.js +144 -133
  62. package/dist/services/RegressionManifestService.d.ts +8 -0
  63. package/dist/services/RegressionManifestService.d.ts.map +1 -1
  64. package/dist/services/RegressionManifestService.js +8 -0
  65. package/dist/services/RegressionReclassifier.d.ts +25 -0
  66. package/dist/services/RegressionReclassifier.d.ts.map +1 -0
  67. package/dist/services/RegressionReclassifier.js +195 -0
  68. package/dist/services/ReportMergeService.d.ts +9 -0
  69. package/dist/services/ReportMergeService.d.ts.map +1 -1
  70. package/dist/services/ReportMergeService.js +28 -0
  71. package/dist/services/SmartDOMPruner.d.ts +154 -0
  72. package/dist/services/SmartDOMPruner.d.ts.map +1 -0
  73. package/dist/services/SmartDOMPruner.js +678 -0
  74. package/dist/services/TraceBackEngine.d.ts +23 -6
  75. package/dist/services/TraceBackEngine.d.ts.map +1 -1
  76. package/dist/services/TraceBackEngine.js +51 -13
  77. package/dist/services/VisualSnapshotService.d.ts +54 -0
  78. package/dist/services/VisualSnapshotService.d.ts.map +1 -0
  79. package/dist/services/VisualSnapshotService.js +279 -0
  80. package/dist/services/vcs/GitHubProvider.d.ts +17 -0
  81. package/dist/services/vcs/GitHubProvider.d.ts.map +1 -0
  82. package/dist/services/vcs/GitHubProvider.js +162 -0
  83. package/dist/services/vcs/GitLabProvider.d.ts +17 -0
  84. package/dist/services/vcs/GitLabProvider.d.ts.map +1 -0
  85. package/dist/services/vcs/GitLabProvider.js +175 -0
  86. package/dist/services/vcs/VCSProvider.d.ts +100 -0
  87. package/dist/services/vcs/VCSProvider.d.ts.map +1 -0
  88. package/dist/services/vcs/VCSProvider.js +7 -0
  89. package/dist/services/vcs/VCSProviderFactory.d.ts +7 -0
  90. package/dist/services/vcs/VCSProviderFactory.d.ts.map +1 -0
  91. package/dist/services/vcs/VCSProviderFactory.js +42 -0
  92. package/dist/types/index.d.ts +7 -0
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/dist/utils/shell.d.ts +7 -0
  95. package/dist/utils/shell.d.ts.map +1 -0
  96. package/dist/utils/shell.js +32 -0
  97. package/package.json +7 -1
@@ -65,6 +65,7 @@ const crypto_1 = require("crypto");
65
65
  const atomicWrite_1 = require("../utils/atomicWrite");
66
66
  const logger_1 = require("../utils/logger");
67
67
  const ReportGenerator_1 = require("./ReportGenerator");
68
+ const RegressionReclassifier_1 = require("./RegressionReclassifier");
68
69
  /**
69
70
  * Compute a stable, run-independent key for an event.
70
71
  * Based solely on immutable facts: normalized file path, line number, and original (broken) selector.
@@ -314,6 +315,13 @@ exports.REPORT_JSON_FILE = "sela-report.json";
314
315
  * Merge a regression verdict into the report at `targetDir` and rewrite
315
316
  * `sela-report.html` + `sela-report.json`. Returns the HTML path.
316
317
  * Throws when no base report exists (Job 2 must run after Job 1).
318
+ *
319
+ * When `regression.records` (the manifest) is supplied, each BYPASSED event is
320
+ * reclassified against its OWN blast-radius verdict (see
321
+ * {@link reclassifyBypassEvents}): a fix whose downstream tests all passed is
322
+ * promoted to HEALED, one whose tests broke is demoted to FAILED. Stats + ROI
323
+ * are recomputed from the reclassified stream so the donut, KPIs and filters
324
+ * reflect the post-regression truth — history is left untouched (Job 1 owns it).
317
325
  */
318
326
  function mergeRegressionIntoReport(targetDir, regression, opts = {}) {
319
327
  const jsonPath = opts.reportJsonPath ?? path.join(targetDir, exports.REPORT_JSON_FILE);
@@ -322,8 +330,20 @@ function mergeRegressionIntoReport(targetDir, regression, opts = {}) {
322
330
  `Run the primary suite (Job 1) before shadow-regression (Job 2).`);
323
331
  }
324
332
  const base = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
333
+ let events = base.events;
334
+ let stats = base.stats;
335
+ let roi = base.roi;
336
+ if (regression.records && regression.records.length > 0) {
337
+ const { events: reclassified } = (0, RegressionReclassifier_1.reclassifyBypassEvents)(base.events, regression.records, regression.regressionTests);
338
+ events = reclassified;
339
+ stats = ReportGenerator_1.ReportGenerator.computeStats(events);
340
+ roi = ReportGenerator_1.ReportGenerator.computeRoiMetrics(events, base.historical_runs ?? [], stats);
341
+ }
325
342
  const merged = {
326
343
  ...base,
344
+ events,
345
+ stats,
346
+ roi,
327
347
  regressionState: regression.regressionState,
328
348
  regressionTests: regression.regressionTests,
329
349
  };
@@ -719,6 +739,12 @@ const REPORT_CSS = [
719
739
  ".fail-img-wrap{background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
720
740
  ".fail-img-wrap img{display:block;max-width:100%;height:auto}",
721
741
  ".fail-img-meta{padding:6px 11px;font-size:11px;color:var(--text3);font-family:var(--mono)}",
742
+ ".vd-pair{display:grid;grid-template-columns:1fr 1fr;gap:12px}",
743
+ ".vd-col{background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
744
+ ".vd-col img{display:block;width:100%;height:auto}",
745
+ ".vd-label{padding:6px 10px;font-size:11px;font-weight:600;color:var(--text2)}",
746
+ ".vd-ts{padding:0 10px 7px;font-size:10px;color:var(--text3);font-family:var(--mono)}",
747
+ ".vd-placeholder{padding:28px 16px;text-align:center;font-size:12px;color:var(--text3)}",
722
748
  ".prot-notice{padding:12px 14px;background:rgba(162,89,255,.07);border:1px solid rgba(162,89,255,.2);border-radius:var(--radius);font-size:12.5px;color:var(--text2);line-height:1.55}",
723
749
  ".prot-notice strong{color:var(--purple)}",
724
750
  ".bypass-banner{padding:13px 15px;background:rgba(255,184,77,.08);border:1px solid rgba(255,184,77,.32);border-radius:var(--radius);margin-bottom:14px}",
@@ -733,6 +759,13 @@ const REPORT_CSS = [
733
759
  ".row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}",
734
760
  "@media(max-width:700px){.row2{grid-template-columns:1fr}}",
735
761
  ".muted-block{padding:12px 14px;background:var(--bg1);border:1px dashed var(--line);border-radius:var(--radius);font-size:12px;color:var(--text3)}",
762
+ ".brain-details{margin-top:14px;border:1px solid var(--line2);border-radius:var(--radius);overflow:hidden}",
763
+ ".brain-summary{display:flex;align-items:center;gap:8px;padding:10px 14px;cursor:pointer;font-size:13px;font-weight:600;color:var(--text2);background:var(--bg2);user-select:none;list-style:none}",
764
+ ".brain-summary::-webkit-details-marker{display:none}",
765
+ ".brain-summary .brain-arrow{font-size:10px;color:var(--text3);transition:transform .2s ease;display:inline-block}",
766
+ "details.brain-details[open] .brain-summary .brain-arrow{transform:rotate(90deg)}",
767
+ ".brain-body{padding:14px;display:flex;flex-direction:column;gap:16px;border-top:1px solid var(--line)}",
768
+ ".brain-section-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text3);margin-bottom:6px}",
736
769
  ".hist-row{display:grid;grid-template-columns:1fr repeat(5,80px);gap:0;font-size:12px}",
737
770
  ".hist-row .hc{padding:6px 10px;border-bottom:1px solid var(--line)}",
738
771
  ".hist-row.hdr .hc{color:var(--text3);font-size:10.5px;letter-spacing:.6px;text-transform:uppercase;background:var(--bg3)}",
@@ -816,6 +849,12 @@ const REPORT_CSS = [
816
849
  ".reg-trace{display:none;padding:10px 18px 14px;background:var(--bg0)}",
817
850
  ".reg-entry.reg-row-open .reg-trace{display:block}",
818
851
  ".reg-trace pre{font-family:var(--mono);font-size:11px;line-height:1.5;color:var(--text2);white-space:pre-wrap;word-break:break-word;margin:0}",
852
+ // ── Per-fix (scoped) blast-radius regression panel inside an event card
853
+ ".fixreg-count{margin-left:6px;font-family:var(--mono);font-size:11px;color:var(--text3);font-weight:400}",
854
+ ".fixreg-note{font-size:12.5px;line-height:1.55;padding:10px 13px;border-radius:var(--radius);margin-bottom:10px}",
855
+ ".fixreg-note.fixreg-ok{background:var(--green-dim);border:1px solid rgba(0,229,160,.32);color:var(--green)}",
856
+ ".fixreg-note.fixreg-bad{background:var(--red-dim);border:1px solid rgba(255,77,106,.32);color:var(--red)}",
857
+ ".fixreg-list{border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
819
858
  ].join("\n");
820
859
  /* eslint-enable prettier/prettier */
821
860
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1007,7 +1046,8 @@ const REPORT_JS = [
1007
1046
  "function confBadge(pct){",
1008
1047
  " if(typeof pct!=='number'||!isFinite(pct))return'';",
1009
1048
  " var cls=pct>=80?'conf-high':pct>=60?'conf-mid':'conf-low';",
1010
- " return'<span class=\"conf-badge '+cls+'\">'+pct+'%</span>';",
1049
+ " var lbl=pct>=80?'High':pct>=60?'Medium':'Low';",
1050
+ " return'<span class=\"conf-badge '+cls+'\">'+lbl+' \xb7 '+pct+'%</span>';",
1011
1051
  "}",
1012
1052
  // PROTECTED events were BLOCKED - showing the AI's high confidence would be
1013
1053
  // a lie. Surface a strict Blocked pill instead; the honest consistency score
@@ -1025,8 +1065,10 @@ const REPORT_JS = [
1025
1065
  " :'confidence too low ('+ev.aiConfidence+'/100)';",
1026
1066
  " return'<div class=\"prot-notice\"><strong>Source code untouched.</strong> Sela blocked this auto-fix \u2014 '+blockMsg+'. This is a real regression surfacing in CI. Investigate <a href=\"'+vscodeLink(ev.sourceFile,ev.sourceLine)+'\">'+esc(ev.sourceFile)+':'+ev.sourceLine+'</a> manually.</div>';",
1027
1067
  " }",
1028
- " if(ev.kind==='FAILED')",
1068
+ " if(ev.kind==='FAILED'){",
1069
+ " if(ev.wasBypassed)return'<div class=\"muted-block\">This shared-locator heal was applied in memory for the regression run only \u2014 never written to disk. It is listed as Failed because its blast-radius regression did not pass (see the breakdown above).</div>';",
1029
1070
  " return'<div class=\"muted-block\">No source code was modified. The AI could not propose a confident fix \u2014 all candidates resolved to 0 elements. Open the file directly to investigate.</div>';",
1071
+ " }",
1030
1072
  " var isBypass=ev.kind==='BYPASSED';",
1031
1073
  " var raw=isBypass?(ev.ghostDiff||''):(ev.gitUnifiedDiff||'');",
1032
1074
  " var skipPfx=['diff ','index ','--- ','+++','new file','deleted','similarity','rename','Binary'];",
@@ -1179,8 +1221,52 @@ const REPORT_JS = [
1179
1221
  ' +\'<div class="fail-img-wrap"><img src="data:image/png;base64,\'+ev.failureScreenshotPng+\'" alt="Failure screenshot" loading="lazy"/>\'',
1180
1222
  " +'<div class=\"fail-img-meta\">captured at test failure \xb7 base64-embedded</div></div></div>';",
1181
1223
  "}",
1224
+ // ── Visual element diff (DNA baseline vs live capture)
1225
+ "function renderVisualDiff(ev){",
1226
+ " if(ev.kind!=='HEALED'||!ev.visualDiff)return'';",
1227
+ " var vd=ev.visualDiff;",
1228
+ " function vdCol(label,ts,imgPath,placeholder){",
1229
+ " var meta=ts?'<div class=\"vd-ts\">'+esc(ts)+'</div>':'';",
1230
+ " var body=imgPath",
1231
+ " ?'<img src=\"'+esc(imgPath)+'\" alt=\"'+esc(label)+'\" loading=\"lazy\"/>'",
1232
+ " :'<div class=\"vd-placeholder\">'+esc(placeholder)+'</div>';",
1233
+ " return'<div class=\"vd-col\"><div class=\"vd-label\">'+esc(label)+'</div>'+body+meta+'</div>';",
1234
+ " }",
1235
+ " var leftImg=vd.dnaSnapshotPath||null;",
1236
+ " var leftLabel='DNA Baseline';",
1237
+ " var leftPlaceholder=leftImg===null?'First heal — no baseline yet':'';",
1238
+ " var rightImg=vd.elementFound&&vd.liveSnapshotPath?vd.liveSnapshotPath:null;",
1239
+ " var rightPlaceholder=vd.elementFound?'':'Element not found';",
1240
+ " var pair='<div class=\"vd-pair\">'",
1241
+ " +vdCol(leftLabel,vd.dnaSnapshotCapturedAt,leftImg,leftPlaceholder)",
1242
+ " +vdCol('Current (this fix)',vd.liveSnapshotCapturedAt,rightImg,rightPlaceholder)",
1243
+ " +'</div>';",
1244
+ " return'<div class=\"body-section\"><div class=\"bs-title\"><span class=\"bst-dot\"></span>Visual Element Comparison</div>'+pair+'</div>';",
1245
+ "}",
1246
+ // ── Per-fix scoped blast-radius regression (shadow-regression provenance)
1247
+ // Rendered only on events reclassified by `sela shadow-regression`: a HEALED
1248
+ // event promoted from a verified bypass, or a FAILED event demoted from a
1249
+ // rejected one. This is a personal mini-Overview — exactly the tests in THIS
1250
+ // fix's blast radius, which passed and which broke.
1251
+ "function renderFixRegression(ev){",
1252
+ " if(!ev.regressionTests||!ev.regressionTests.length)return'';",
1253
+ " var st=ev.regressionState||'';",
1254
+ " var passN=ev.regressionTests.filter(function(t){return t.status==='PASSED';}).length;",
1255
+ " var failN=ev.regressionTests.filter(function(t){return t.status==='FAILED';}).length;",
1256
+ " var noteCls=st==='SUCCESS'?'fixreg-ok':'fixreg-bad';",
1257
+ " var note=ev.regressionNote?'<div class=\"fixreg-note '+noteCls+'\">'+esc(ev.regressionNote)+'</div>':'';",
1258
+ " var rows=ev.regressionTests.map(function(t){",
1259
+ " var file=esc(t.filePath||'');var name=esc(t.testName||'');",
1260
+ " if(t.status==='PASSED')return'<div class=\"reg-entry reg-pass\"><div class=\"reg-row\"><span class=\"reg-row-icon\">✓</span><span class=\"reg-row-name\">'+name+'</span><span class=\"reg-row-file\">'+file+'</span></div></div>';",
1261
+ " var err=t.error||{};var trace=(err.message||'')+(err.stack?'\\n\\n'+err.stack:'');",
1262
+ " return'<div class=\"reg-entry reg-fail\"><div class=\"reg-row\" onclick=\"this.parentElement.classList.toggle(\\'reg-row-open\\')\"><span class=\"reg-row-icon\">✕</span><span class=\"reg-row-name\">'+name+'</span><span class=\"reg-row-file\">'+file+'</span><svg class=\"reg-row-chev\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"5 3 11 8 5 13\"/></svg></div><div class=\"reg-trace\"><pre>'+esc(trace)+'</pre></div></div>';",
1263
+ " }).join('');",
1264
+ " var title=st==='SUCCESS'?'Blast-Radius Regression — Passed':'Blast-Radius Regression — Failed';",
1265
+ " return'<div class=\"body-section\"><div class=\"bs-title\"><span class=\"bst-dot\"></span>'+esc(title)+'<span class=\"fixreg-count\">'+passN+' passed \xb7 '+failN+' failed</span></div>'+note+'<div class=\"fixreg-list\">'+rows+'</div></div>';",
1266
+ "}",
1182
1267
  // ── Shared-locator runtime bypass (blast-radius protection)
1183
1268
  "var WARN_TRIANGLE='<svg class=\"warn-tri\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 1.5L15 14H1L8 1.5z\"/><line x1=\"8\" y1=\"6\" x2=\"8\" y2=\"9.5\"/><circle cx=\"8\" cy=\"11.6\" r=\"0.7\" fill=\"currentColor\" stroke=\"none\"/></svg>';",
1269
+ "var LIGHTBULB='<svg class=\"lightbulb-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 1a3.5 3.5 0 0 0-3.5 3.5c0 1.5.5 2.5 1 3.5v1.5h5v-1.5c.5-1 1-2 1-3.5A3.5 3.5 0 0 0 8 1z\"/><line x1=\"5\" y1=\"10.5\" x2=\"11\" y2=\"10.5\"/><path d=\"M6 12.5h4\"/><path d=\"M6.5 14h3\"/></svg>';",
1184
1270
  "function renderBypassBody(ev){",
1185
1271
  " var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
1186
1272
  " var files=(ev.affectedFiles&&ev.affectedFiles.length)?'<div class=\"bb-files\">Affected: '+ev.affectedFiles.map(esc).join(', ')+'</div>':'';",
@@ -1197,15 +1283,15 @@ const REPORT_JS = [
1197
1283
  " steps.push('Sela healed this run in memory but did NOT write to disk — the locator is shared by '+((ev.testFunctionCount||0)+' test(s).')+'');",
1198
1284
  " steps.push('Review whether the shared declaration at '+esc(ev.variablePath||'')+' needs a structural refactor.');",
1199
1285
  " }",
1200
- " var row2='<div class=\"row2\" style=\"margin-top:16px\">'",
1201
- " +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>AI Reasoning \xb7 Chain of Thought</div>'+renderCoT(steps)+'</div>'",
1202
- " +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>Intent Auditor</div>'+renderAuditor(ev.auditor||null)+'</div>'",
1203
- " +'</div>';",
1286
+ " var bypassBrainInner=''",
1287
+ " +'<div><div class=\"brain-section-title\">Chain of Thought</div>'+renderCoT(steps)+'</div>'",
1288
+ " +'<div><div class=\"brain-section-title\">Intent Audit</div>'+renderAuditor(ev.auditor||null)+'</div>';",
1289
+ " var bypassBrain='<details class=\"brain-details\"><summary class=\"brain-summary\"><span class=\"brain-arrow\">▶</span>'+LIGHTBULB+' AI Reasoning \xb7 Audit</summary><div class=\"brain-body\">'+bypassBrainInner+'</div></details>';",
1204
1290
  " var actions='<div class=\"actions\">'",
1205
1291
  " +'<a class=\"btn btn-primary\" href=\"'+link+'\">→ Open shared declaration in VS Code</a>'",
1206
1292
  " +'</div>';",
1207
1293
  " return banner",
1208
- " +row2",
1294
+ " +bypassBrain",
1209
1295
  " +renderVectors(ev)",
1210
1296
  " +'<div class=\"body-section\"><div class=\"bs-title\"><span class=\"bst-dot\"></span>Speculative Heal (not written to disk)</div>'+renderDiff(ev)+'</div>'",
1211
1297
  " +'<div class=\"body-section\"><div class=\"bs-title\"><span class=\"bst-dot\"></span>Visual DNA Evidence</div>'+renderDna(ev.dnaBefore||null,ev.dnaAfter||null)+'</div>'",
@@ -1230,7 +1316,7 @@ const REPORT_JS = [
1230
1316
  " }",
1231
1317
  " var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
1232
1318
  " var actions='';",
1233
- " if(ev.kind==='HEALED'){",
1319
+ " if(ev.kind==='HEALED'&&!ev.wasBypassed){",
1234
1320
  " actions='<div class=\"actions\">'",
1235
1321
  " +'<button class=\"btn btn-primary\" data-action=\"keep\" data-id=\"'+ev.stableId+'\" onclick=\"handleAction(this,\\'keep\\',\\''+ev.stableId+'\\')\">\u2713 Keep changes</button>'",
1236
1322
  " +'<button class=\"btn btn-danger\" onclick=\"handleAction(this,\\'rollback\\',\\''+ev.stableId+'\\')\">&#x21B6; Rollback</button>'",
@@ -1246,20 +1332,27 @@ const REPORT_JS = [
1246
1332
  " +'<a class=\"btn btn-primary\" href=\"'+link+'\">\u2192 Open in VS Code</a>'",
1247
1333
  " +'</div>';",
1248
1334
  " }",
1249
- " var alts=(ev.kind==='HEALED'&&ev.aiAlternatives&&ev.aiAlternatives.length)",
1250
- ' ?\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Alternative selectors considered</div><div class="alts">\'',
1335
+ " var brainInner=''",
1336
+ " +'<div><div class=\"brain-section-title\">Chain of Thought</div>'+renderCoT(steps)+'</div>'",
1337
+ " +'<div><div class=\"brain-section-title\">Intent Audit</div>'+renderAuditor(ev.auditor||null)+'</div>';",
1338
+ " if(ev.kind==='HEALED'&&ev.aiAlternatives&&ev.aiAlternatives.length){",
1339
+ " brainInner+='<div><div class=\"brain-section-title\">Alternatives Considered</div><div class=\"alts\">'",
1251
1340
  " +ev.aiAlternatives.map(function(a){return'<div class=\"alt-row\"><span>'+esc(a)+'</span><button class=\"alt-copy\" onclick=\"copyText(\\''+esc(a)+'\\',this)\">Copy</button></div>';}).join('')",
1252
- " +'</div></div>'",
1253
- " :'';",
1254
- ' return\'<div class="row2" style="margin-top:16px">\'',
1255
- " +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>AI Reasoning \xb7 Chain of Thought</div>'+renderCoT(steps)+'</div>'",
1256
- " +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>Intent Auditor</div>'+renderAuditor(ev.auditor||null)+'</div>'",
1257
- " +'</div>'",
1341
+ " +'</div></div>';",
1342
+ " }",
1343
+ " if(ev.dnaBefore){",
1344
+ " var _d=ev.dnaBefore;",
1345
+ " var _snip=(_d.tagName||'element')+(_d.role?' role=\"'+_d.role+'\"':'')+(_d.text?' \\''+String(_d.text).slice(0,60)+'\\'':'');",
1346
+ " brainInner+='<div><div class=\"brain-section-title\">Target DOM Snapshot</div><div class=\"muted-block\" style=\"font-family:var(--mono);font-size:12px\">'+esc(_snip)+'</div></div>';",
1347
+ " }",
1348
+ " var brain='<details class=\"brain-details\"><summary class=\"brain-summary\"><span class=\"brain-arrow\">▶</span>'+LIGHTBULB+' AI Reasoning & Audit</summary><div class=\"brain-body\">'+brainInner+'</div></details>';",
1349
+ ' return\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Source Code Diff</div>\'+renderDiff(ev)+\'</div>\'',
1350
+ " +brain",
1351
+ " +renderFixRegression(ev)",
1258
1352
  " +renderVectors(ev)",
1259
- ' +\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Source Code Diff</div>\'+renderDiff(ev)+\'</div>\'',
1353
+ " +renderVisualDiff(ev)",
1260
1354
  ' +\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Visual DNA Evidence</div>\'+renderDna(ev.dnaBefore||null,ev.dnaAfter||null)+\'</div>\'',
1261
1355
  " +renderScreenshot(ev)",
1262
- " +alts",
1263
1356
  " +actions;",
1264
1357
  "}",
1265
1358
  // ── Event card
@@ -1,5 +1,6 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { AncestorNode, AnchorInfo } from "../types";
3
+ import type { LLMProvider } from "../providers/LLMProvider";
3
4
  export interface AuditRequest {
4
5
  dnaText: string;
5
6
  dnaRole?: string;
@@ -31,20 +32,21 @@ export interface AuditVerdict {
31
32
  /**
32
33
  * Constructor options for IntentAuditor.
33
34
  *
34
- * - `anthropic`: pre-built Anthropic client to use. Pass `null` explicitly
35
- * to force the auditor into its disabled state (useful in tests that
36
- * want to assert the SUSPICIOUS fallback path without touching env vars).
37
- * Pass `undefined` (default) to fall back to env-based construction.
38
- * - `apiKey`: override the ANTHROPIC_API_KEY env var (DI-friendly).
39
- * - `model`: override the Haiku model id (lets tests pin the SDK call).
35
+ * - `provider`: pre-built LLMProvider (new path, used by SelaEngine via ProviderFactory).
36
+ * Pass `null` explicitly to force disabled mode.
37
+ * - `anthropic`: pre-built Anthropic client (legacy path for existing tests).
38
+ * Pass `null` explicitly to force disabled mode.
39
+ * - `apiKey`: override ANTHROPIC_API_KEY for the default env-based client.
40
+ * - `model`: override the Haiku model id.
40
41
  */
41
42
  export interface IntentAuditorOptions {
43
+ provider?: LLMProvider | null;
42
44
  anthropic?: Anthropic | null;
43
45
  apiKey?: string;
44
46
  model?: string;
45
47
  }
46
48
  export declare class IntentAuditor {
47
- private anthropic;
49
+ private readonly provider;
48
50
  private readonly disabled;
49
51
  private readonly model;
50
52
  constructor(opts?: IntentAuditorOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"IntentAuditor.d.ts","sourceRoot":"","sources":["../../src/services/IntentAuditor.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAapD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IAEjC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,qEAAqE;IACrE,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IACzE,yEAAyE;IACzE,cAAc,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E;AAqDD;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,GAAE,oBAAyB;IAiCrC,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAyH3D,sBAAsB,CACpB,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,EAAE,YAAY,EAAE,GACvB,MAAM;IAkBT,kBAAkB,CAChB,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACzC,WAAW,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACzC,MAAM;IAiBT,OAAO,CAAC,YAAY;CA8CrB"}
1
+ {"version":3,"file":"IntentAuditor.d.ts","sourceRoot":"","sources":["../../src/services/IntentAuditor.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAY5D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IAEjC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,qEAAqE;IACrE,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IACzE,yEAAyE;IACzE,cAAc,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E;AAqDD;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,GAAE,oBAAyB;IAwCrC,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAqH3D,sBAAsB,CACpB,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,EAAE,YAAY,EAAE,GACvB,MAAM;IAkBT,kBAAkB,CAChB,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACzC,WAAW,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACzC,MAAM;IAiBT,OAAO,CAAC,YAAY;CA8CrB"}
@@ -37,6 +37,7 @@ exports.IntentAuditor = void 0;
37
37
  const dotenv = __importStar(require("dotenv"));
38
38
  const SanitizingAnthropic_1 = require("./SanitizingAnthropic");
39
39
  const logger_1 = require("../utils/logger");
40
+ const AnthropicProvider_1 = require("../providers/AnthropicProvider");
40
41
  const StructuralScoringUtils_1 = require("./confidence/StructuralScoringUtils");
41
42
  dotenv.config();
42
43
  // ═══════════════════════════════════════════════════════════════════
@@ -84,39 +85,45 @@ RESPONSE FORMAT (strict JSON, no other text):
84
85
  }`;
85
86
  const DEFAULT_AUDITOR_MODEL = "claude-haiku-4-5-20251001";
86
87
  class IntentAuditor {
87
- anthropic;
88
+ provider;
88
89
  disabled;
89
90
  model;
90
91
  constructor(opts = {}) {
91
92
  this.model = opts.model ?? DEFAULT_AUDITOR_MODEL;
92
- // Explicit `null` from the caller forces disabled mode - used by tests.
93
+ // New provider injection path (opts.provider is explicitly set).
94
+ if (opts.provider !== undefined) {
95
+ this.provider = opts.provider;
96
+ this.disabled = opts.provider === null;
97
+ return;
98
+ }
99
+ // Legacy: explicit `null` forces disabled mode (used by tests).
93
100
  if (opts.anthropic === null) {
94
- this.anthropic = null;
101
+ this.provider = null;
95
102
  this.disabled = true;
96
103
  return;
97
104
  }
98
- // Caller supplied a ready-to-use client → use it as-is.
105
+ // Legacy: caller supplied a ready-to-use Anthropic client.
99
106
  if (opts.anthropic !== undefined) {
100
- this.anthropic = opts.anthropic;
107
+ this.provider = new AnthropicProvider_1.AnthropicProvider(this.model, opts.anthropic);
101
108
  this.disabled = false;
102
109
  return;
103
110
  }
104
- // Otherwise fall back to env-based construction (legacy default).
111
+ // Default: env-based construction (backward compat).
105
112
  const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
106
113
  if (!apiKey) {
107
114
  logger_1.logger.warn("ANTHROPIC_API_KEY not set - auditor disabled, will return SUSPICIOUS for all audited heals");
108
- this.anthropic = null;
115
+ this.provider = null;
109
116
  this.disabled = true;
110
117
  }
111
118
  else {
112
- // Route through the PII/secrets sanitizer (T3): the auditor prompt
113
- // embeds live element text, so it must be scrubbed before leaving too.
114
- this.anthropic = (0, SanitizingAnthropic_1.createSanitizingAnthropic)({ apiKey });
119
+ // Route through the PII/secrets sanitizer (T3).
120
+ const sanitizedClient = (0, SanitizingAnthropic_1.createSanitizingAnthropic)({ apiKey });
121
+ this.provider = new AnthropicProvider_1.AnthropicProvider(this.model, sanitizedClient);
115
122
  this.disabled = false;
116
123
  }
117
124
  }
118
125
  async auditIntent(req) {
119
- if (this.disabled || !this.anthropic) {
126
+ if (this.disabled || !this.provider) {
120
127
  return { ...FALLBACK_VERDICT };
121
128
  }
122
129
  const userMessage = [
@@ -129,14 +136,12 @@ class IntentAuditor {
129
136
  .filter(Boolean)
130
137
  .join("\n");
131
138
  try {
132
- const response = await this.anthropic.messages.create({
133
- model: this.model,
134
- max_tokens: 256,
135
- temperature: 0.1,
139
+ const raw = await this.provider.complete({
136
140
  system: SYSTEM_PROMPT,
137
141
  messages: [{ role: "user", content: userMessage }],
142
+ maxTokens: 256,
143
+ temperature: 0.1,
138
144
  });
139
- const raw = response.content[0].type === "text" ? response.content[0].text : "";
140
145
  // parseVerdict returns the raw LLM verdict without the low-confidence
141
146
  // downgrade so that scoring adjustments below are applied first.
142
147
  const rawVerdict = this.parseVerdict(raw);
@@ -1,5 +1,6 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { FixRequest } from "../types";
3
+ import type { LLMProvider } from "../providers/LLMProvider";
3
4
  export interface SelectorSegment {
4
5
  type: "frame" | "element";
5
6
  selector: string;
@@ -66,7 +67,7 @@ export type SmartChainSegment = {
66
67
  index: number;
67
68
  };
68
69
  export interface FixResponse {
69
- status: "FIXED" | "NOT_FOUND";
70
+ status: "FIXED" | "NOT_FOUND" | "NO_HEAL";
70
71
  /**
71
72
  * LEGACY/PROXY: This selector is used for IMMEDIATE RETRY in the proxy.
72
73
  * CRITICAL: Must be as specific as the chainSegments to avoid Strict Mode errors.
@@ -86,23 +87,25 @@ export interface FixResponse {
86
87
  explanation: string;
87
88
  new_selector?: string | null;
88
89
  contentChange?: ContentChange;
90
+ /** Populated when status === "NO_HEAL": the LLM's reason for refusing to heal. */
91
+ reason?: string;
89
92
  }
90
93
  /**
91
94
  * Constructor options for LLMService.
92
95
  *
93
- * - `anthropic`: pre-built Anthropic client. Tests use this to inject a
94
- * mock client without touching env vars or network.
95
- * - `apiKey`: override the env-based ANTHROPIC_API_KEY when constructing
96
- * the default client.
97
- * - `model`: override the Sonnet model id (lets tests pin the SDK call).
96
+ * - `provider`: pre-built LLMProvider (new path, used by SelaEngine via ProviderFactory).
97
+ * - `anthropic`: pre-built Anthropic client (legacy path for existing tests).
98
+ * - `apiKey`: override ANTHROPIC_API_KEY for the default env-based client.
99
+ * - `model`: override the model id.
98
100
  */
99
101
  export interface LLMServiceOptions {
102
+ provider?: LLMProvider;
100
103
  anthropic?: Anthropic;
101
104
  apiKey?: string;
102
105
  model?: string;
103
106
  }
104
107
  export declare class LLMService {
105
- private anthropic;
108
+ private readonly provider;
106
109
  private readonly model;
107
110
  constructor(opts?: LLMServiceOptions);
108
111
  private cleanJson;
@@ -1 +1 @@
1
- {"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
@@ -39,6 +39,7 @@ const SelaError_1 = require("../errors/SelaError");
39
39
  const SanitizingAnthropic_1 = require("./SanitizingAnthropic");
40
40
  const logger_1 = require("../utils/logger");
41
41
  const ConfigLoader_1 = require("../config/ConfigLoader");
42
+ const AnthropicProvider_1 = require("../providers/AnthropicProvider");
42
43
  const Conventions_1 = require("../config/Conventions");
43
44
  dotenv.config();
44
45
  /**
@@ -178,6 +179,32 @@ NEVER omit this field. NEVER return it as a string, boolean, or null.
178
179
  Reports rendered downstream will show "n/a" when this field is missing - which
179
180
  is a clear failure signal to the developer reviewing the heal.
180
181
 
182
+ ### SCOPE BOUNDARY CHECK (evaluate BEFORE proposing any selector)
183
+ Before returning a FIXED response, determine whether the failure is:
184
+ (A) SELECTOR DRIFT — the element exists in the DOM but its selector changed structurally
185
+ (class renamed, id updated, attribute value changed). → Proceed with FIXED.
186
+ (B) APP LOGIC / STATE MISMATCH — the element is ENTIRELY ABSENT because of a
187
+ functional bug, wrong routing, empty state, error page, or dynamic runtime variable.
188
+ → Return NO_HEAL immediately.
189
+
190
+ Patterns that MUST trigger NO_HEAL:
191
+ 1. The failing selector ends with a numeric suffix (e.g., "status-201", "item-3", "hi-200")
192
+ AND the DOM contains a similar selector with a different numeric suffix. The number
193
+ difference is likely a runtime state value (HTTP status code, counter, index), NOT a
194
+ CSS rename. Healing this would mask a backend regression.
195
+ 2. The target element is completely absent AND the nearest DOM candidate has a fundamentally
196
+ different semantic role/action (e.g., original was a "Delete" action, candidate is "Save").
197
+ 3. An error message, empty state placeholder, or loading spinner is the only visible content
198
+ — the application is in an unexpected state, not a selector-drift state.
199
+ 4. The failing selector contains programmatic patterns (UUIDs, timestamps, sequential IDs)
200
+ that suggest the value is generated at runtime.
201
+
202
+ NO_HEAL response format:
203
+ {
204
+ "status": "NO_HEAL",
205
+ "reason": "<one sentence: why this is app logic, not selector drift>"
206
+ }
207
+
181
208
  ### MANDATORY OUTPUT FORMAT
182
209
  Return ONLY valid JSON.
183
210
 
@@ -238,6 +265,12 @@ Example output D - Multiple typo corrections, each unambiguous (confidence 85-92
238
265
  { "type": "element", "selector": ".user-table >> .active-row >> internal:role=button[name=\"Submit\"i]" }
239
266
  ],
240
267
  "explanation": "Two typos corrected: '.user-tble' → '.user-table' (confirmed by DNA ancestry class) and button name 'Sumbit' → 'Submit' (confirmed by DNA text). Each fix is an unambiguous spelling correction. DNA text 'Submit' matches live DOM exactly. Semantic identity is certain."
268
+ }
269
+
270
+ Example output E - Application state mismatch, not selector drift (NO_HEAL):
271
+ {
272
+ "status": "NO_HEAL",
273
+ "reason": "Selector 'hi-201' uses a numeric HTTP status suffix. DOM contains 'hi-200' with a different status number — this is a backend state bug, not selector drift."
241
274
  }`;
242
275
  // ─────────────────────────────────────────────────────────────────
243
276
  // HELPERS
@@ -324,12 +357,17 @@ function inferOriginalChainHint(selector) {
324
357
  }
325
358
  const DEFAULT_LLM_MODEL = "claude-sonnet-4-6";
326
359
  class LLMService {
327
- anthropic;
360
+ provider;
328
361
  model;
329
362
  constructor(opts = {}) {
330
363
  this.model = opts.model ?? process.env.SELA_LLM_MODEL ?? DEFAULT_LLM_MODEL;
364
+ if (opts.provider) {
365
+ this.provider = opts.provider;
366
+ return;
367
+ }
331
368
  if (opts.anthropic) {
332
- this.anthropic = opts.anthropic;
369
+ // Legacy path: wrap injected Anthropic client (used by tests).
370
+ this.provider = new AnthropicProvider_1.AnthropicProvider(this.model, opts.anthropic);
333
371
  return;
334
372
  }
335
373
  const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
@@ -337,12 +375,12 @@ class LLMService {
337
375
  throw new SelaError_1.SelaError({
338
376
  subsystem: "LLMService",
339
377
  code: SelaError_1.LLM_SERVICE_CODES.NO_API_KEY,
340
- reason: "ANTHROPIC_API_KEY not found - pass an `anthropic` client or `apiKey` to LLMService(), or set the env var",
378
+ reason: "ANTHROPIC_API_KEY not found - pass a `provider` or `anthropic` client to LLMService(), or set the env var",
341
379
  });
342
380
  }
343
- // Route the default client through the PII/secrets sanitizer (T3) so all
344
- // outbound DOM/DNA context is scrubbed before leaving the process.
345
- this.anthropic = (0, SanitizingAnthropic_1.createSanitizingAnthropic)({ apiKey });
381
+ // Route the default client through the PII/secrets sanitizer (T3).
382
+ const sanitizedClient = (0, SanitizingAnthropic_1.createSanitizingAnthropic)({ apiKey });
383
+ this.provider = new AnthropicProvider_1.AnthropicProvider(this.model, sanitizedClient);
346
384
  }
347
385
  cleanJson(text) {
348
386
  const cleaned = text.replace(/```json/g, "").replace(/```/g, "").trim();
@@ -520,20 +558,30 @@ ${task2}
520
558
 
521
559
  Return ONLY a raw JSON object - no markdown, no preamble.`;
522
560
  try {
523
- const msg = await this.anthropic.messages.create({
524
- model: this.model,
525
- max_tokens: 1024,
561
+ const rawText = await this.provider.complete({
526
562
  system: SYSTEM_PROMPT_TEXT +
527
563
  buildConventionsBlock() +
528
564
  "\nIMPORTANT: Respond ONLY with a raw JSON object.",
529
565
  messages: [{ role: "user", content: userMessage }],
566
+ maxTokens: 1024,
530
567
  });
531
- const rawText = msg.content[0].type === "text" ? msg.content[0].text : "";
532
568
  logger_1.logger.debug(`AI Raw Response: ${rawText}`);
533
569
  const jsonString = this.cleanJson(rawText);
534
570
  if (!jsonString)
535
571
  throw new Error("AI did not return a valid JSON object");
536
572
  const parsed = JSON.parse(jsonString);
573
+ // ── NO_HEAL: LLM detected app logic failure — skip all processing ──
574
+ // Return immediately so SelaEngine can record a FailedEvent and surface
575
+ // the native Playwright error without any AST mutation attempt.
576
+ if (parsed.status === "NO_HEAL") {
577
+ logger_1.logger.warn(`[Sela] LLM NO_HEAL: ${parsed.reason ?? "application state mismatch"}`);
578
+ return {
579
+ status: "NO_HEAL",
580
+ reason: parsed.reason ?? "Application logic/state mismatch detected by LLM",
581
+ segments: [],
582
+ explanation: parsed.reason ?? "Application logic/state mismatch detected by LLM",
583
+ };
584
+ }
537
585
  // ── Normalise confidence: keep only finite numbers in [0,100] ─────
538
586
  // Anything else (undefined, NaN, null, string, out-of-range) collapses
539
587
  // to `undefined` so reports render "n/a" instead of synthesising 0.
@@ -611,6 +659,24 @@ Return ONLY a raw JSON object - no markdown, no preamble.`;
611
659
  if (!parsed.segments || !Array.isArray(parsed.segments)) {
612
660
  throw new Error("AI response missing 'segments' array");
613
661
  }
662
+ // ── Static output contract ────────────────────────────────────────
663
+ // Sela only writes static selector strings to disk. If LLM returned
664
+ // a dynamic expression (template literal interpolation, assertion,
665
+ // arrow function, or JS string concatenation), treat as NOT_FOUND —
666
+ // never write executable code to test source.
667
+ //
668
+ // Patterns blocked:
669
+ // ${...} — template literal interpolation
670
+ // expect( — assertion leak
671
+ // => { — arrow function body
672
+ // "str" + ident — string concat with variable (e.g. "id-" + status)
673
+ // ident + "str" — string concat with variable (e.g. status + "-id")
674
+ const DYNAMIC_EXPR_RE = /\$\{[^}]+\}|expect\s*\(|=>\s*\{|['"`][^'"`]*['"`]\s*\+\s*[a-zA-Z_$]|\b[a-zA-Z_$]\w*\s*\+\s*['"`]/;
675
+ const hasDynamicOutput = parsed.segments.some((s) => typeof s.selector === "string" && DYNAMIC_EXPR_RE.test(s.selector));
676
+ if (hasDynamicOutput) {
677
+ logger_1.logger.warn("[Sela] Static output violation — LLM returned dynamic expression in selector. Blocking.");
678
+ throw new Error("ENG_DYNAMIC_OUTPUT_BLOCKED: selector contains dynamic expression");
679
+ }
614
680
  parsed.new_selector =
615
681
  parsed.new_selector ??
616
682
  parsed.segments.map((s) => s.selector).join(" >> ");
@@ -1,6 +1,7 @@
1
1
  import type { ResolvedPRAutomation, PRStrategy, BugAction } from "../config/SelaConfig";
2
2
  import type { HealedEvent, ProtectedEvent } from "./HealReportService";
3
3
  import { type WorkspaceSnapshotService } from "./WorkspaceSnapshotService";
4
+ import type { IVCSProvider } from "./vcs/VCSProvider";
4
5
  export interface BranchInfo {
5
6
  branch: string | null;
6
7
  isPR: boolean;
@@ -45,8 +46,12 @@ export interface ExecutionResult {
45
46
  */
46
47
  export declare function execute(cfg: ResolvedPRAutomation, decision: StrategyDecision, heals: HealedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext,
47
48
  /** Injectable for tests - defaults to the module singleton. */
48
- workspace?: WorkspaceSnapshotService): Promise<ExecutionResult>;
49
- export declare function handleBugDetected(action: BugAction, events: ProtectedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext): Promise<void>;
49
+ workspace?: WorkspaceSnapshotService,
50
+ /** Injectable for tests - defaults to auto-detected platform provider. */
51
+ provider?: IVCSProvider): Promise<ExecutionResult>;
52
+ export declare function handleBugDetected(action: BugAction, events: ProtectedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext,
53
+ /** Injectable for tests - defaults to auto-detected platform provider. */
54
+ provider?: IVCSProvider): Promise<void>;
50
55
  export interface CentralizedGitFlowOpts {
51
56
  cwd: string;
52
57
  healedEvents: HealedEvent[];
@@ -1 +1 @@
1
- {"version":3,"file":"PRAutomationService.d.ts","sourceRoot":"","sources":["../../src/services/PRAutomationService.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,SAAS,EACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,4BAA4B,CAAC;AAQpC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,UAAU,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,aAAa,GAAG,IAAI,CAAC;IAC9E;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAiFD,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CA+EpE;AAMD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,WAAW,EAAE,GACnB,gBAAgB,CAgElB;AAMD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAEzC;AA6FD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,oBAAoB,EACzB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc;AACnB,+DAA+D;AAC/D,SAAS,GAAE,wBAAkD,GAC5D,OAAO,CAAC,eAAe,CAAC,CAkG1B;AAsTD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CAgGf;AAMD,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAgDf"}
1
+ {"version":3,"file":"PRAutomationService.d.ts","sourceRoot":"","sources":["../../src/services/PRAutomationService.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,SAAS,EACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAQvE,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,4BAA4B,CAAC;AAKpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMtD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,UAAU,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,aAAa,GAAG,IAAI,CAAC;IAC9E;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAyDD,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CAyFpE;AAMD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,WAAW,EAAE,GACnB,gBAAgB,CAgElB;AAMD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAEzC;AA6FD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,oBAAoB,EACzB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc;AACnB,+DAA+D;AAC/D,SAAS,GAAE,wBAAkD;AAC7D,0EAA0E;AAC1E,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,eAAe,CAAC,CAwG1B;AAgWD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc;AACnB,0EAA0E;AAC1E,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,IAAI,CAAC,CAwFf;AAMD,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAgDf"}