sela-core 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/SelaEngine.d.ts +23 -6
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +109 -34
- package/dist/services/HealReportService.d.ts +20 -3
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +105 -14
- package/dist/services/LLMService.d.ts +6 -1
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +30 -4
- package/dist/services/PRAutomationService.d.ts +9 -2
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +65 -10
- package/dist/services/SafetyGuard.d.ts +7 -1
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +7 -1
- package/package.json +1 -1
|
@@ -15,13 +15,30 @@ export declare class SelaEngine {
|
|
|
15
15
|
private _toReportFile;
|
|
16
16
|
private _safeReadLine;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* Capture the native `git diff --unified=1 HEAD -- <file>` for the file the
|
|
19
|
+
* AST mutation just wrote to. Returns an empty string when git is not
|
|
20
|
+
* available, the file is not tracked, or any other failure — callers must
|
|
21
|
+
* treat empty output as "no diff captured" and fall back to a direct disk
|
|
22
|
+
* read of the mutated line.
|
|
23
|
+
*
|
|
24
|
+
* Path safety: the absolute path is always wrapped in double quotes after
|
|
25
|
+
* escaping any embedded double quotes, which makes the call safe on Windows
|
|
26
|
+
* paths containing spaces or backslashes as well as POSIX paths.
|
|
23
27
|
*/
|
|
24
|
-
private
|
|
28
|
+
private _captureGitDiff;
|
|
29
|
+
/**
|
|
30
|
+
* Walk a unified diff and pull out the first removed line and the first
|
|
31
|
+
* added line so the legacy report fields (`oldCodeLine`, `newCodeLine`)
|
|
32
|
+
* stay populated for callers that haven't migrated to the full
|
|
33
|
+
* `gitUnifiedDiff` payload yet.
|
|
34
|
+
*
|
|
35
|
+
* Carefully skips the diff preamble (--- a/file, +++ b/file, "diff ",
|
|
36
|
+
* "index ", rename headers) — those lines would otherwise be mistaken for
|
|
37
|
+
* a -/+ change pair and produce nonsense like the original "line replaced
|
|
38
|
+
* by itself" output. Real change lines only count once the parser has seen
|
|
39
|
+
* a "@@" hunk header.
|
|
40
|
+
*/
|
|
41
|
+
private _extractFirstChangeFromDiff;
|
|
25
42
|
private _auditorBlockFromDecision;
|
|
26
43
|
private _buildAlternatives;
|
|
27
44
|
private _buildReasoningSteps;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAOxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAe,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAyChE,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;;IAanD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiDnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAyclB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;IAarB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAwBjC,OAAO,CAAC,kBAAkB;IA8B1B,OAAO,CAAC,oBAAoB;IAuC5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YA6FtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IAiClC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAqBnB;AAED,OAAO,EAAE,CAAC"}
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.SelaEngine = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
39
40
|
const LLMService_1 = require("../services/LLMService");
|
|
40
41
|
const SnapshotService_1 = require("../services/SnapshotService");
|
|
41
42
|
const DOMUtils_1 = require("../utils/DOMUtils");
|
|
@@ -44,6 +45,14 @@ const SafetyGuard_1 = require("../services/SafetyGuard");
|
|
|
44
45
|
const ConfigLoader_1 = require("../config/ConfigLoader");
|
|
45
46
|
const HealReportService_1 = require("../services/HealReportService");
|
|
46
47
|
const PRAutomationService_1 = require("../services/PRAutomationService");
|
|
48
|
+
/**
|
|
49
|
+
* Graceful percentage formatter for engine-side log lines. Mirrors the helper
|
|
50
|
+
* in PRAutomationService / HealReportService so a missing AI/auditor
|
|
51
|
+
* confidence reads as "n/a" instead of "undefined%" or "0%".
|
|
52
|
+
*/
|
|
53
|
+
function fmtPctEngine(v) {
|
|
54
|
+
return typeof v === "number" && Number.isFinite(v) ? `${v}%` : "n/a";
|
|
55
|
+
}
|
|
47
56
|
class SelaEngine {
|
|
48
57
|
llmService;
|
|
49
58
|
snapshotService;
|
|
@@ -262,7 +271,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
262
271
|
reason: safetyDecision.reason,
|
|
263
272
|
safetyLevel: safetyDecision.level,
|
|
264
273
|
auditor: this._auditorBlockFromDecision(safetyDecision.meta),
|
|
265
|
-
aiConfidence:
|
|
274
|
+
aiConfidence: aiFix.confidence,
|
|
266
275
|
aiExplanation: aiFix.explanation ?? "",
|
|
267
276
|
dnaBefore: (0, HealReportService_1.summariseSnapshot)(lastKnownState),
|
|
268
277
|
dnaAfter: (0, HealReportService_1.summariseSnapshot)(liveSnapshot ?? null),
|
|
@@ -281,16 +290,11 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
281
290
|
parts.push(`${breakdown.penalty} ancestry drift`);
|
|
282
291
|
if (breakdown.bonus !== 0)
|
|
283
292
|
parts.push(`+${breakdown.bonus} anchor match`);
|
|
284
|
-
console.log(`[Sela] 📊 Score — AI: ${aiFix.confidence}
|
|
285
|
-
`Auditor: ${breakdown.adjustedConfidence}
|
|
286
|
-
`(raw ${breakdown.rawConfidence}
|
|
293
|
+
console.log(`[Sela] 📊 Score — AI: ${fmtPctEngine(aiFix.confidence)} | ` +
|
|
294
|
+
`Auditor: ${fmtPctEngine(breakdown.adjustedConfidence)} ` +
|
|
295
|
+
`(raw ${fmtPctEngine(breakdown.rawConfidence)}, ${parts.join(", ")})`);
|
|
287
296
|
}
|
|
288
297
|
console.log(`[Sela] 🚀 Rebuilt Full Runtime Selector (Clean): ${newFullSelector}`);
|
|
289
|
-
// Snapshot the ENTIRE source file BEFORE the AST mutation.
|
|
290
|
-
// We don't know which line will actually be mutated until update()
|
|
291
|
-
// returns (JumpToDef may land on the const-decl line, not the failure
|
|
292
|
-
// line) — so we keep the pre-image around and slice into it after.
|
|
293
|
-
const preMutationLines = this._safeReadAllLines(filePath);
|
|
294
298
|
const updateResult = SourceUpdater_1.SourceUpdater.update({ filePath, line }, elementSelectorOnly, newFullSelector, neighborhoodDom, undefined, fullSelector, aiFix.segments, aiFix.contentChange, aiFix.chainSegments, aiFix.originalChainHint, this.config.updateStrategy, this.config.autoCommit);
|
|
295
299
|
// Resolve the line that was actually mutated. Order of precedence:
|
|
296
300
|
// 1. updateResult.lineUpdated (the strategy's literal write location)
|
|
@@ -308,15 +312,20 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
308
312
|
(sameFileDef ? defSite?.line : undefined) ??
|
|
309
313
|
line;
|
|
310
314
|
const mutatedFilePath = !sameFileDef && defSite ? defSite.file : filePath;
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
315
|
+
// Capture the native unified diff straight from git. This runs AFTER
|
|
316
|
+
// SourceUpdater flushed the file to disk and BEFORE any `git add` —
|
|
317
|
+
// so `git diff HEAD` reports the exact pending change at the real
|
|
318
|
+
// mutated line. No in-process line-index math, no off-by-one when
|
|
319
|
+
// JumpToDef lands above the failure site, and cross-file healing
|
|
320
|
+
// falls out for free.
|
|
321
|
+
const gitUnifiedDiff = this._captureGitDiff(mutatedFilePath);
|
|
322
|
+
const firstChange = this._extractFirstChangeFromDiff(gitUnifiedDiff);
|
|
323
|
+
// Backward-compat: keep oldCodeLine/newCodeLine populated from the
|
|
324
|
+
// first -/+ pair inside the diff. When git produced no diff (file
|
|
325
|
+
// untracked, git unavailable, …) fall back to a direct disk read of
|
|
326
|
+
// the mutated line — strictly inferior but preserves old reports.
|
|
327
|
+
const oldCodeLine = firstChange.oldLine ?? this._safeReadLine(mutatedFilePath, writtenLine);
|
|
328
|
+
const newCodeLine = firstChange.newLine ?? this._safeReadLine(mutatedFilePath, writtenLine);
|
|
320
329
|
const canonicalSelector = updateResult.healedLocatorString ?? newFullSelector;
|
|
321
330
|
console.log(`[Sela] ✅ Canonical selector (disk == runtime): "${canonicalSelector}"`);
|
|
322
331
|
// Buffer CLI-ready metadata — merged into DNA at commitUpdates() time.
|
|
@@ -364,7 +373,8 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
364
373
|
oldCodeLine,
|
|
365
374
|
newCodeLine,
|
|
366
375
|
newLineNumber: writtenLine,
|
|
367
|
-
|
|
376
|
+
gitUnifiedDiff: gitUnifiedDiff || undefined,
|
|
377
|
+
aiConfidence: aiFix.confidence,
|
|
368
378
|
aiExplanation: aiFix.explanation ?? "",
|
|
369
379
|
aiAlternatives: this._buildAlternatives(aiFix.chainSegments, aiFix.segments),
|
|
370
380
|
reasoningSteps: this._buildReasoningSteps({
|
|
@@ -461,30 +471,94 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
461
471
|
}
|
|
462
472
|
}
|
|
463
473
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
474
|
+
* Capture the native `git diff --unified=1 HEAD -- <file>` for the file the
|
|
475
|
+
* AST mutation just wrote to. Returns an empty string when git is not
|
|
476
|
+
* available, the file is not tracked, or any other failure — callers must
|
|
477
|
+
* treat empty output as "no diff captured" and fall back to a direct disk
|
|
478
|
+
* read of the mutated line.
|
|
479
|
+
*
|
|
480
|
+
* Path safety: the absolute path is always wrapped in double quotes after
|
|
481
|
+
* escaping any embedded double quotes, which makes the call safe on Windows
|
|
482
|
+
* paths containing spaces or backslashes as well as POSIX paths.
|
|
469
483
|
*/
|
|
470
|
-
|
|
484
|
+
_captureGitDiff(filePath) {
|
|
471
485
|
try {
|
|
472
486
|
const cleaned = this._toReportFile(filePath);
|
|
473
|
-
const abs = path.isAbsolute(cleaned)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
487
|
+
const abs = path.isAbsolute(cleaned)
|
|
488
|
+
? cleaned
|
|
489
|
+
: path.resolve(process.cwd(), cleaned);
|
|
490
|
+
if (!abs || !fs.existsSync(abs))
|
|
491
|
+
return "";
|
|
492
|
+
const quoted = `"${abs.replace(/"/g, '\\"')}"`;
|
|
493
|
+
const out = (0, child_process_1.execSync)(`git diff --unified=1 HEAD -- ${quoted}`, {
|
|
494
|
+
cwd: process.cwd(),
|
|
495
|
+
encoding: "utf-8",
|
|
496
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
497
|
+
windowsHide: true,
|
|
498
|
+
});
|
|
499
|
+
return out.toString();
|
|
477
500
|
}
|
|
478
|
-
catch {
|
|
479
|
-
|
|
501
|
+
catch (err) {
|
|
502
|
+
// git missing / not a repo / file untracked vs HEAD → log & fall back.
|
|
503
|
+
console.debug?.(`[Sela] git diff capture skipped: ${err?.message ?? err}`);
|
|
504
|
+
return "";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Walk a unified diff and pull out the first removed line and the first
|
|
509
|
+
* added line so the legacy report fields (`oldCodeLine`, `newCodeLine`)
|
|
510
|
+
* stay populated for callers that haven't migrated to the full
|
|
511
|
+
* `gitUnifiedDiff` payload yet.
|
|
512
|
+
*
|
|
513
|
+
* Carefully skips the diff preamble (--- a/file, +++ b/file, "diff ",
|
|
514
|
+
* "index ", rename headers) — those lines would otherwise be mistaken for
|
|
515
|
+
* a -/+ change pair and produce nonsense like the original "line replaced
|
|
516
|
+
* by itself" output. Real change lines only count once the parser has seen
|
|
517
|
+
* a "@@" hunk header.
|
|
518
|
+
*/
|
|
519
|
+
_extractFirstChangeFromDiff(diff) {
|
|
520
|
+
if (!diff)
|
|
521
|
+
return {};
|
|
522
|
+
let inHunk = false;
|
|
523
|
+
let oldLine;
|
|
524
|
+
let newLine;
|
|
525
|
+
for (const row of diff.split(/\r?\n/)) {
|
|
526
|
+
if (!inHunk) {
|
|
527
|
+
if (row.startsWith("@@"))
|
|
528
|
+
inHunk = true;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
// Defensive: a fresh @@ header starts a new hunk but does not reset
|
|
532
|
+
// the values we've already captured.
|
|
533
|
+
if (row.startsWith("@@"))
|
|
534
|
+
continue;
|
|
535
|
+
if (row.startsWith("---") || row.startsWith("+++"))
|
|
536
|
+
continue;
|
|
537
|
+
if (row.startsWith("\\"))
|
|
538
|
+
continue;
|
|
539
|
+
if (row.startsWith("-") && oldLine === undefined) {
|
|
540
|
+
oldLine = row.slice(1);
|
|
541
|
+
}
|
|
542
|
+
else if (row.startsWith("+") && newLine === undefined) {
|
|
543
|
+
newLine = row.slice(1);
|
|
544
|
+
}
|
|
545
|
+
if (oldLine !== undefined && newLine !== undefined)
|
|
546
|
+
break;
|
|
480
547
|
}
|
|
548
|
+
return { oldLine, newLine };
|
|
481
549
|
}
|
|
482
550
|
_auditorBlockFromDecision(meta) {
|
|
483
551
|
if (!meta || !meta.auditorVerdict)
|
|
484
552
|
return null;
|
|
553
|
+
// Keep auditor confidence as `undefined` when SafetyGuard did not produce
|
|
554
|
+
// one — the renderer fmtPct helper turns it into "n/a" rather than 0%.
|
|
555
|
+
const conf = typeof meta.auditorConfidence === "number" &&
|
|
556
|
+
Number.isFinite(meta.auditorConfidence)
|
|
557
|
+
? meta.auditorConfidence
|
|
558
|
+
: undefined;
|
|
485
559
|
return {
|
|
486
560
|
verdict: meta.auditorVerdict,
|
|
487
|
-
confidence:
|
|
561
|
+
confidence: conf,
|
|
488
562
|
reason: meta.auditScoreBreakdown
|
|
489
563
|
? `Adjusted ${meta.auditScoreBreakdown.rawConfidence}% → ${meta.auditScoreBreakdown.adjustedConfidence}% (penalty ${meta.auditScoreBreakdown.penalty}, bonus +${meta.auditScoreBreakdown.bonus}).`
|
|
490
564
|
: "Auditor verdict recorded by SafetyGuard.",
|
|
@@ -562,7 +636,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
562
636
|
if (ctx.aiFix.explanation) {
|
|
563
637
|
steps.push(`AI reasoning: ${ctx.aiFix.explanation}`);
|
|
564
638
|
}
|
|
565
|
-
steps.push(`Confidence: ${ctx.aiFix.confidence}
|
|
639
|
+
steps.push(`Confidence: ${fmtPctEngine(ctx.aiFix.confidence)}.`);
|
|
566
640
|
if (ctx.aiFix.contentChange) {
|
|
567
641
|
steps.push(`Detected content drift: "${ctx.aiFix.contentChange.oldText}" → "${ctx.aiFix.contentChange.newText}".`);
|
|
568
642
|
}
|
|
@@ -615,7 +689,8 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
615
689
|
if (decision.downgradeReason) {
|
|
616
690
|
console.warn(`[Sela PR] ⚠️ Strategy downgraded: '${decision.configured}' → '${decision.effective}' ` +
|
|
617
691
|
`(reason: ${decision.downgradeReason}, ` +
|
|
618
|
-
`minAI: ${decision.minAiConfidence}
|
|
692
|
+
`minAI: ${fmtPctEngine(decision.minAiConfidence)}, ` +
|
|
693
|
+
`minAuditor: ${fmtPctEngine(decision.minAuditorConfidence)})`);
|
|
619
694
|
}
|
|
620
695
|
else {
|
|
621
696
|
console.log(`[Sela PR] 🎯 Effective strategy: '${decision.effective}' on branch '${branchInfo.branch ?? "unknown"}'`);
|
|
@@ -17,7 +17,11 @@ export interface DnaSummary {
|
|
|
17
17
|
}
|
|
18
18
|
export interface AuditorBlock {
|
|
19
19
|
verdict: AuditVerdictValue;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Adjusted confidence 0-100. `undefined` when the auditor produced no score
|
|
22
|
+
* (e.g. SafetyGuard bypassed it) — reports MUST render this as "n/a".
|
|
23
|
+
*/
|
|
24
|
+
confidence?: number;
|
|
21
25
|
reason: string;
|
|
22
26
|
inversionType: AuditVerdict["inversionType"];
|
|
23
27
|
rawConfidence?: number;
|
|
@@ -39,10 +43,22 @@ export interface HealedEvent extends HealEventBase {
|
|
|
39
43
|
kind: "HEALED";
|
|
40
44
|
oldSelector: string;
|
|
41
45
|
newSelector: string;
|
|
46
|
+
/** First removed line from the native git diff (backward-compat fallback). */
|
|
42
47
|
oldCodeLine: string;
|
|
48
|
+
/** First added line from the native git diff (backward-compat fallback). */
|
|
43
49
|
newCodeLine: string;
|
|
44
50
|
newLineNumber: number;
|
|
45
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Raw `git diff --unified=1` output captured immediately after the AST
|
|
53
|
+
* mutation hit disk and BEFORE any `git add`. Includes headers (---/+++)
|
|
54
|
+
* and one or more @@ hunks. Renderers strip headers; PR body emits the
|
|
55
|
+
* hunks verbatim inside a ```diff fence so cross-file healing, multi-line
|
|
56
|
+
* edits, and template-literal changes are all displayed natively without
|
|
57
|
+
* any in-process line-index math.
|
|
58
|
+
*/
|
|
59
|
+
gitUnifiedDiff?: string;
|
|
60
|
+
/** AI-reported confidence 0-100. `undefined` when the model omitted it. */
|
|
61
|
+
aiConfidence?: number;
|
|
46
62
|
aiExplanation: string;
|
|
47
63
|
aiAlternatives: string[];
|
|
48
64
|
reasoningSteps: string[];
|
|
@@ -75,7 +91,8 @@ export interface ProtectedEvent extends HealEventBase {
|
|
|
75
91
|
reason: string;
|
|
76
92
|
safetyLevel: string;
|
|
77
93
|
auditor: AuditorBlock | null;
|
|
78
|
-
|
|
94
|
+
/** AI-reported confidence 0-100. `undefined` when the model omitted it. */
|
|
95
|
+
aiConfidence?: number;
|
|
79
96
|
aiExplanation: string;
|
|
80
97
|
dnaBefore: DnaSummary | null;
|
|
81
98
|
dnaAfter: DnaSummary | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAMlD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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"}
|
|
@@ -752,6 +752,13 @@ const REPORT_JS = String.raw `
|
|
|
752
752
|
try { return new Date(iso).toLocaleString(); } catch { return iso; }
|
|
753
753
|
}
|
|
754
754
|
function pluralize(n, one, many) { return n === 1 ? one : (many || one + "s"); }
|
|
755
|
+
// Graceful percentage formatter — mirrors the server-side helper in
|
|
756
|
+
// PRAutomationService. Anything that is not a finite number renders "n/a"
|
|
757
|
+
// instead of being coerced to 0%. Used in every spot that previously
|
|
758
|
+
// hard-coded a plain "x + %" concatenation.
|
|
759
|
+
function fmtPct(v) {
|
|
760
|
+
return (typeof v === "number" && isFinite(v)) ? (v + "%") : "n/a";
|
|
761
|
+
}
|
|
755
762
|
|
|
756
763
|
// ── Hero meta + KPIs ──────────────────────────────────────────
|
|
757
764
|
$("#meta-generated").textContent = "Generated " + fmtTime(DATA.generatedAt);
|
|
@@ -905,15 +912,16 @@ const REPORT_JS = String.raw `
|
|
|
905
912
|
function renderAuditor(a) {
|
|
906
913
|
if (!a) return '<div class="muted-block">The Intent Auditor was not consulted for this event (no AI fix attempted, or auditor unavailable).</div>';
|
|
907
914
|
const verdictCls = a.verdict.toLowerCase();
|
|
908
|
-
const
|
|
915
|
+
const hasPct = typeof a.confidence === "number" && isFinite(a.confidence);
|
|
916
|
+
const pct = hasPct ? Math.max(0, Math.min(100, a.confidence)) : 0;
|
|
909
917
|
const radius = 56;
|
|
910
918
|
const circ = 2 * Math.PI * radius;
|
|
911
|
-
const dash = (pct / 100) * circ;
|
|
919
|
+
const dash = hasPct ? (pct / 100) * circ : 0;
|
|
912
920
|
const adjustments = [];
|
|
913
921
|
if (a.penalty != null && a.penalty !== 0) adjustments.push("ancestry drift " + a.penalty);
|
|
914
922
|
if (a.bonus != null && a.bonus !== 0) adjustments.push("anchor +" + a.bonus);
|
|
915
923
|
const adjLine = adjustments.length
|
|
916
|
-
? '<div class="audit-row"><span class="label">Score adjustments</span><span class="value">raw ' + a.rawConfidence + '
|
|
924
|
+
? '<div class="audit-row"><span class="label">Score adjustments</span><span class="value">raw ' + fmtPct(a.rawConfidence) + ' → ' + fmtPct(hasPct ? pct : undefined) + ' (' + adjustments.join(", ") + ')</span></div>'
|
|
917
925
|
: "";
|
|
918
926
|
return (
|
|
919
927
|
'<div class="auditor-grid">' +
|
|
@@ -924,7 +932,7 @@ const REPORT_JS = String.raw `
|
|
|
924
932
|
'stroke-dasharray="' + dash.toFixed(2) + ' ' + circ.toFixed(2) + '"/>' +
|
|
925
933
|
'</svg>' +
|
|
926
934
|
'<div class="center">' +
|
|
927
|
-
'<div class="pct">' + pct + '
|
|
935
|
+
'<div class="pct">' + (hasPct ? pct + "%" : "n/a") + '</div>' +
|
|
928
936
|
'<div class="pct-label">Consistency</div>' +
|
|
929
937
|
'</div>' +
|
|
930
938
|
'</div>' +
|
|
@@ -938,6 +946,58 @@ const REPORT_JS = String.raw `
|
|
|
938
946
|
);
|
|
939
947
|
}
|
|
940
948
|
|
|
949
|
+
// Parse "git diff --unified=N" output into structured hunks. Skips file
|
|
950
|
+
// headers ("diff ", "index ", "--- ", "+++ ", rename/similarity lines) and
|
|
951
|
+
// walks each "@@ -a,b +c,d @@" block, tracking real old/new line numbers
|
|
952
|
+
// so the rendered diff shows correct line refs even when the AST mutation
|
|
953
|
+
// landed several lines above the failure site (JumpToDef scenario).
|
|
954
|
+
function parseUnifiedDiff(raw) {
|
|
955
|
+
const out = [];
|
|
956
|
+
if (!raw) return out;
|
|
957
|
+
const rows = raw.split(/\r?\n/);
|
|
958
|
+
let cur = null;
|
|
959
|
+
let oldLn = 0, newLn = 0;
|
|
960
|
+
for (const r of rows) {
|
|
961
|
+
if (
|
|
962
|
+
r.startsWith("diff ") ||
|
|
963
|
+
r.startsWith("index ") ||
|
|
964
|
+
r.startsWith("--- ") ||
|
|
965
|
+
r.startsWith("+++ ") ||
|
|
966
|
+
r.startsWith("new file mode") ||
|
|
967
|
+
r.startsWith("deleted file mode") ||
|
|
968
|
+
r.startsWith("similarity ") ||
|
|
969
|
+
r.startsWith("rename ") ||
|
|
970
|
+
r.startsWith("Binary files")
|
|
971
|
+
) continue;
|
|
972
|
+
const hunk = r.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
|
973
|
+
if (hunk) {
|
|
974
|
+
if (cur) out.push(cur);
|
|
975
|
+
cur = { header: r, lines: [] };
|
|
976
|
+
oldLn = parseInt(hunk[1], 10);
|
|
977
|
+
newLn = parseInt(hunk[3], 10);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (!cur) continue;
|
|
981
|
+
// "" + truly empty trailing lines: ignore
|
|
982
|
+
if (r.startsWith("\\")) continue;
|
|
983
|
+
if (r.length === 0) continue;
|
|
984
|
+
const head = r.charAt(0);
|
|
985
|
+
const text = r.slice(1);
|
|
986
|
+
if (head === "+") {
|
|
987
|
+
cur.lines.push({ type: "add", text, ln: newLn });
|
|
988
|
+
newLn++;
|
|
989
|
+
} else if (head === "-") {
|
|
990
|
+
cur.lines.push({ type: "del", text, ln: oldLn });
|
|
991
|
+
oldLn++;
|
|
992
|
+
} else if (head === " ") {
|
|
993
|
+
cur.lines.push({ type: "ctx", text, ln: newLn });
|
|
994
|
+
oldLn++; newLn++;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (cur) out.push(cur);
|
|
998
|
+
return out;
|
|
999
|
+
}
|
|
1000
|
+
|
|
941
1001
|
function renderDiff(ev) {
|
|
942
1002
|
if (ev.kind !== "HEALED") {
|
|
943
1003
|
if (ev.kind === "PROTECTED") {
|
|
@@ -947,8 +1007,45 @@ const REPORT_JS = String.raw `
|
|
|
947
1007
|
}
|
|
948
1008
|
return '<div class="muted-block">No source code was modified — the AI could not propose a confident fix for this selector.</div>';
|
|
949
1009
|
}
|
|
950
|
-
|
|
951
|
-
const
|
|
1010
|
+
|
|
1011
|
+
const raw = ev.gitUnifiedDiff || "";
|
|
1012
|
+
const hunks = parseUnifiedDiff(raw);
|
|
1013
|
+
|
|
1014
|
+
let bodyHtml;
|
|
1015
|
+
let sourceTag;
|
|
1016
|
+
if (hunks.length > 0) {
|
|
1017
|
+
sourceTag = "native git diff";
|
|
1018
|
+
bodyHtml = hunks.map(h => {
|
|
1019
|
+
const hdr =
|
|
1020
|
+
'<div class="diff-line ctx">' +
|
|
1021
|
+
'<span class="gutter">@</span><span class="ln"></span><span class="code">' + esc(h.header) + '</span>' +
|
|
1022
|
+
'</div>';
|
|
1023
|
+
const body = h.lines.map(L => {
|
|
1024
|
+
const gutter = L.type === "add" ? "+" : L.type === "del" ? "-" : " ";
|
|
1025
|
+
return '<div class="diff-line ' + L.type + '">' +
|
|
1026
|
+
'<span class="gutter">' + gutter + '</span>' +
|
|
1027
|
+
'<span class="ln">' + L.ln + '</span>' +
|
|
1028
|
+
'<span class="code">' + esc(L.text) + '</span>' +
|
|
1029
|
+
'</div>';
|
|
1030
|
+
}).join("");
|
|
1031
|
+
return hdr + body;
|
|
1032
|
+
}).join("");
|
|
1033
|
+
} else {
|
|
1034
|
+
// Fallback when git diff capture was unavailable (no git, untracked
|
|
1035
|
+
// file, etc.). Render the single before/after pair the engine carried
|
|
1036
|
+
// over from the legacy code path.
|
|
1037
|
+
sourceTag = "fallback (no git diff captured)";
|
|
1038
|
+
const oldLine = ev.oldCodeLine || "";
|
|
1039
|
+
const newLine = ev.newCodeLine || "";
|
|
1040
|
+
bodyHtml =
|
|
1041
|
+
'<div class="diff-line del">' +
|
|
1042
|
+
'<span class="gutter">-</span><span class="ln">' + (ev.newLineNumber || "") + '</span><span class="code">' + esc(oldLine) + '</span>' +
|
|
1043
|
+
'</div>' +
|
|
1044
|
+
'<div class="diff-line add">' +
|
|
1045
|
+
'<span class="gutter">+</span><span class="ln">' + (ev.newLineNumber || "") + '</span><span class="code">' + esc(newLine) + '</span>' +
|
|
1046
|
+
'</div>';
|
|
1047
|
+
}
|
|
1048
|
+
|
|
952
1049
|
return (
|
|
953
1050
|
'<div class="diff">' +
|
|
954
1051
|
'<div class="diff-head">' +
|
|
@@ -957,15 +1054,9 @@ const REPORT_JS = String.raw `
|
|
|
957
1054
|
'<span>line ' + ev.newLineNumber + '</span>' +
|
|
958
1055
|
(ev.diffStrategy ? '<span class="muted">·</span><span>strategy: ' + esc(ev.diffStrategy) + '</span>' : "") +
|
|
959
1056
|
(ev.blastRadius != null ? '<span class="muted">·</span><span>blast radius: ' + ev.blastRadius + '</span>' : "") +
|
|
1057
|
+
'<span class="muted">·</span><span>' + sourceTag + '</span>' +
|
|
960
1058
|
'</div>' +
|
|
961
|
-
'<div class="diff-body">' +
|
|
962
|
-
'<div class="diff-line del">' +
|
|
963
|
-
'<span class="gutter">-</span><span class="ln">' + ev.newLineNumber + '</span><span class="code">' + esc(oldLine) + '</span>' +
|
|
964
|
-
'</div>' +
|
|
965
|
-
'<div class="diff-line add">' +
|
|
966
|
-
'<span class="gutter">+</span><span class="ln">' + ev.newLineNumber + '</span><span class="code">' + esc(newLine) + '</span>' +
|
|
967
|
-
'</div>' +
|
|
968
|
-
'</div>' +
|
|
1059
|
+
'<div class="diff-body">' + bodyHtml + '</div>' +
|
|
969
1060
|
'</div>'
|
|
970
1061
|
);
|
|
971
1062
|
}
|
|
@@ -76,7 +76,12 @@ export interface FixResponse {
|
|
|
76
76
|
*/
|
|
77
77
|
chainSegments?: SmartChainSegment[];
|
|
78
78
|
originalChainHint?: SmartChainSegment[];
|
|
79
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Confidence score 0-100. Undefined when the model omitted it or returned a
|
|
81
|
+
* non-finite value — downstream code must treat undefined as "unknown" and
|
|
82
|
+
* render "n/a" rather than synthesising a 0.
|
|
83
|
+
*/
|
|
84
|
+
confidence?: number;
|
|
80
85
|
explanation: string;
|
|
81
86
|
new_selector?: string | null;
|
|
82
87
|
contentChange?: ContentChange;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAQtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1C;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAEpD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;IAC9B;;;OAGG;IACH,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
|
|
@@ -103,21 +103,34 @@ The healing system depends on the attribute value being present in your response
|
|
|
103
103
|
If the element's text changed, you MUST include:
|
|
104
104
|
"contentChange": { "oldText": "<previous>", "newText": "<current>" }
|
|
105
105
|
|
|
106
|
+
### CONFIDENCE SCORE (MANDATORY)
|
|
107
|
+
You MUST include a numeric "confidence" field — an INTEGER in the range 0-100 —
|
|
108
|
+
representing how certain you are that the returned chain will resolve the
|
|
109
|
+
intended element on the next test run. Use this rubric:
|
|
110
|
+
- 90-100: stable id, data-testid, or unique role+name — zero ambiguity in the DOM.
|
|
111
|
+
- 70-89: strong semantic match, but some structural drift or competing candidates.
|
|
112
|
+
- 50-69: best-guess match — multiple plausible candidates required disambiguation.
|
|
113
|
+
- 1-49: speculative match — return this only when no better candidate exists.
|
|
114
|
+
NEVER omit this field. NEVER return it as a string, boolean, or null.
|
|
115
|
+
Reports rendered downstream will show "n/a" when this field is missing — which
|
|
116
|
+
is a clear failure signal to the developer reviewing the heal.
|
|
117
|
+
|
|
106
118
|
### MANDATORY OUTPUT FORMAT
|
|
107
119
|
Return ONLY valid JSON.
|
|
108
120
|
|
|
109
121
|
Example output — Identifying a specific button in a list row:
|
|
110
122
|
{
|
|
111
123
|
"status": "FIXED",
|
|
124
|
+
"confidence": 92,
|
|
112
125
|
"chainSegments": [
|
|
113
126
|
{ "type": "locator", "selector": ".user-item" },
|
|
114
127
|
{ "type": "filter", "hasText": "Bob Smith" },
|
|
115
128
|
{ "type": "getByRole", "role": "button", "name": "Modify Bob" }
|
|
116
129
|
],
|
|
117
130
|
"segments": [
|
|
118
|
-
{
|
|
119
|
-
"type": "element",
|
|
120
|
-
"selector": ".user-item >> internal:has-text=\"Bob Smith\"i >> internal:role=button[name=\"Modify Bob\"i]"
|
|
131
|
+
{
|
|
132
|
+
"type": "element",
|
|
133
|
+
"selector": ".user-item >> internal:has-text=\"Bob Smith\"i >> internal:role=button[name=\"Modify Bob\"i]"
|
|
121
134
|
}
|
|
122
135
|
],
|
|
123
136
|
"explanation": "Updated container to .user-item and targeted the button by its new semantic name 'Modify Bob' to ensure a unique match."
|
|
@@ -343,6 +356,19 @@ Return ONLY a raw JSON object — no markdown, no preamble.`;
|
|
|
343
356
|
if (!jsonString)
|
|
344
357
|
throw new Error("AI did not return a valid JSON object");
|
|
345
358
|
const parsed = JSON.parse(jsonString);
|
|
359
|
+
// ── Normalise confidence: keep only finite numbers in [0,100] ─────
|
|
360
|
+
// Anything else (undefined, NaN, null, string, out-of-range) collapses
|
|
361
|
+
// to `undefined` so reports render "n/a" instead of synthesising 0.
|
|
362
|
+
const rawConf = parsed.confidence;
|
|
363
|
+
if (typeof rawConf === "number" && Number.isFinite(rawConf)) {
|
|
364
|
+
parsed.confidence = Math.max(0, Math.min(100, Math.round(rawConf)));
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
if (rawConf !== undefined) {
|
|
368
|
+
console.warn(`[LLMService] ⚠️ Discarded non-numeric confidence: ${JSON.stringify(rawConf)}`);
|
|
369
|
+
}
|
|
370
|
+
parsed.confidence = undefined;
|
|
371
|
+
}
|
|
346
372
|
// ── Normalize chainSegments → segments (legacy) ───────────────────
|
|
347
373
|
if (parsed.chainSegments && Array.isArray(parsed.chainSegments)) {
|
|
348
374
|
// Reconstruct a flat new_selector from chain segments for legacy paths
|
|
@@ -430,7 +456,7 @@ Return ONLY a raw JSON object — no markdown, no preamble.`;
|
|
|
430
456
|
status: "NOT_FOUND",
|
|
431
457
|
segments: [],
|
|
432
458
|
new_selector: null,
|
|
433
|
-
confidence:
|
|
459
|
+
confidence: undefined,
|
|
434
460
|
explanation: `AI Error: ${error.message}`,
|
|
435
461
|
};
|
|
436
462
|
}
|
|
@@ -10,8 +10,14 @@ export interface StrategyDecision {
|
|
|
10
10
|
effective: PRStrategy;
|
|
11
11
|
configured: PRStrategy;
|
|
12
12
|
downgradeReason: "PROTECTED_BRANCH" | "LOW_CONFIDENCE" | "LOW_AUDITOR" | null;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Lowest AI confidence observed across this session's heals, or `undefined`
|
|
15
|
+
* when no heal carried a numeric confidence (LLM omitted the field). Render
|
|
16
|
+
* via `fmtPct` so the absence reads as "n/a" instead of 0%.
|
|
17
|
+
*/
|
|
18
|
+
minAiConfidence: number | undefined;
|
|
19
|
+
/** Same semantics as `minAiConfidence`, for the auditor's adjusted score. */
|
|
20
|
+
minAuditorConfidence: number | undefined;
|
|
15
21
|
}
|
|
16
22
|
export interface ExecuteContext {
|
|
17
23
|
cwd: string;
|
|
@@ -19,6 +25,7 @@ export interface ExecuteContext {
|
|
|
19
25
|
}
|
|
20
26
|
export declare function detectBranch(cwd?: string): BranchInfo;
|
|
21
27
|
export declare function decideEffectiveStrategy(cfg: ResolvedPRAutomation, branch: string | null, heals: HealedEvent[]): StrategyDecision;
|
|
28
|
+
export declare function fmtPct(v: unknown): string;
|
|
22
29
|
export interface ExecutionResult {
|
|
23
30
|
effective: PRStrategy;
|
|
24
31
|
prUrl: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PRAutomationService.d.ts","sourceRoot":"","sources":["../../src/services/PRAutomationService.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,SAAS,EACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACf,MAAM,qBAAqB,CAAC;AAM7B,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,eAAe,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"PRAutomationService.d.ts","sourceRoot":"","sources":["../../src/services/PRAutomationService.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,SAAS,EACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACf,MAAM,qBAAqB,CAAC;AAM7B,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;AA2ED,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CA8EpE;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;AAuFD,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,wBAAsB,OAAO,CAC3B,GAAG,EAAE,oBAAoB,EACzB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,eAAe,CAAC,CA4J1B;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CA0Ff"}
|
|
@@ -48,6 +48,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
48
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
49
|
exports.detectBranch = detectBranch;
|
|
50
50
|
exports.decideEffectiveStrategy = decideEffectiveStrategy;
|
|
51
|
+
exports.fmtPct = fmtPct;
|
|
51
52
|
exports.execute = execute;
|
|
52
53
|
exports.handleBugDetected = handleBugDetected;
|
|
53
54
|
const fs = __importStar(require("fs"));
|
|
@@ -186,12 +187,19 @@ function detectBranch(cwd = process.cwd()) {
|
|
|
186
187
|
// STRATEGY DECISION
|
|
187
188
|
// ═══════════════════════════════════════════════════════════════════
|
|
188
189
|
function decideEffectiveStrategy(cfg, branch, heals) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
// Compute MIN across only the heals that reported a finite numeric
|
|
191
|
+
// confidence. When *no* heal carried one, the result is `undefined` —
|
|
192
|
+
// surfaced as "n/a" in reports rather than synthesised as 100% (which
|
|
193
|
+
// would silently mask the absence of evidence) or 0% (which would
|
|
194
|
+
// incorrectly force a downgrade for every clean run on Sonnet).
|
|
195
|
+
const aiVals = heals
|
|
196
|
+
.map((h) => h.aiConfidence)
|
|
197
|
+
.filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
198
|
+
const audVals = heals
|
|
199
|
+
.map((h) => h.auditor?.confidence)
|
|
200
|
+
.filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
201
|
+
const minAi = aiVals.length > 0 ? Math.min(...aiVals) : undefined;
|
|
202
|
+
const minAud = audVals.length > 0 ? Math.min(...audVals) : undefined;
|
|
195
203
|
const baseDecision = {
|
|
196
204
|
effective: cfg.strategy,
|
|
197
205
|
configured: cfg.strategy,
|
|
@@ -209,8 +217,11 @@ function decideEffectiveStrategy(cfg, branch, heals) {
|
|
|
209
217
|
downgradeReason: "PROTECTED_BRANCH",
|
|
210
218
|
};
|
|
211
219
|
}
|
|
212
|
-
// Rule 2: low AI confidence
|
|
220
|
+
// Rule 2: low AI confidence — only triggers when the measured min is
|
|
221
|
+
// actually below the threshold. Missing confidence is "unknown", not
|
|
222
|
+
// "zero", so we do NOT downgrade purely on absence.
|
|
213
223
|
if (cfg.reviewThresholds.minConfidenceForDirectCommit > 0 &&
|
|
224
|
+
typeof minAi === "number" &&
|
|
214
225
|
minAi < cfg.reviewThresholds.minConfidenceForDirectCommit) {
|
|
215
226
|
return {
|
|
216
227
|
...baseDecision,
|
|
@@ -218,8 +229,9 @@ function decideEffectiveStrategy(cfg, branch, heals) {
|
|
|
218
229
|
downgradeReason: "LOW_CONFIDENCE",
|
|
219
230
|
};
|
|
220
231
|
}
|
|
221
|
-
// Rule 3: low auditor score
|
|
232
|
+
// Rule 3: low auditor score — same "absence ≠ zero" rule as Rule 2.
|
|
222
233
|
if (cfg.reviewThresholds.minAuditorScoreForDirectCommit > 0 &&
|
|
234
|
+
typeof minAud === "number" &&
|
|
223
235
|
minAud < cfg.reviewThresholds.minAuditorScoreForDirectCommit) {
|
|
224
236
|
return {
|
|
225
237
|
...baseDecision,
|
|
@@ -235,6 +247,40 @@ function decideEffectiveStrategy(cfg, branch, heals) {
|
|
|
235
247
|
function fmtPct(v) {
|
|
236
248
|
return typeof v === "number" && Number.isFinite(v) ? `${v}%` : "n/a";
|
|
237
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Strip the file-level preamble from a unified diff so the GitHub Markdown
|
|
252
|
+
* renderer doesn't show `--- a/file` / `+++ b/file` as red/green lines (which
|
|
253
|
+
* read as fake deletions/additions in a ```diff fence). Keeps the `@@` hunk
|
|
254
|
+
* header and every -/+/space content row intact, so the rendered block is
|
|
255
|
+
* still syntactically a valid unified diff that GitHub highlights correctly.
|
|
256
|
+
*/
|
|
257
|
+
function stripDiffPreamble(raw) {
|
|
258
|
+
return raw
|
|
259
|
+
.split(/\r?\n/)
|
|
260
|
+
.filter((ln) => {
|
|
261
|
+
if (ln.startsWith("diff "))
|
|
262
|
+
return false;
|
|
263
|
+
if (ln.startsWith("index "))
|
|
264
|
+
return false;
|
|
265
|
+
if (ln.startsWith("--- "))
|
|
266
|
+
return false;
|
|
267
|
+
if (ln.startsWith("+++ "))
|
|
268
|
+
return false;
|
|
269
|
+
if (ln.startsWith("new file mode"))
|
|
270
|
+
return false;
|
|
271
|
+
if (ln.startsWith("deleted file mode"))
|
|
272
|
+
return false;
|
|
273
|
+
if (ln.startsWith("similarity "))
|
|
274
|
+
return false;
|
|
275
|
+
if (ln.startsWith("rename "))
|
|
276
|
+
return false;
|
|
277
|
+
if (ln.startsWith("Binary files"))
|
|
278
|
+
return false;
|
|
279
|
+
return true;
|
|
280
|
+
})
|
|
281
|
+
.join("\n")
|
|
282
|
+
.replace(/\n+$/, "");
|
|
283
|
+
}
|
|
238
284
|
function buildDefaultBody(heals, branchInfo, reportLink, decision) {
|
|
239
285
|
const lines = [];
|
|
240
286
|
lines.push(`# 🤖 Sela Insights — Automation Suite Healed!`);
|
|
@@ -256,8 +302,17 @@ function buildDefaultBody(heals, branchInfo, reportLink, decision) {
|
|
|
256
302
|
lines.push(`### \`${h.sourceFile}:${lineRef}\` — ${h.testTitle ?? "(no test title)"}`);
|
|
257
303
|
lines.push("");
|
|
258
304
|
lines.push("```diff");
|
|
259
|
-
|
|
260
|
-
|
|
305
|
+
if (h.gitUnifiedDiff && h.gitUnifiedDiff.trim().length > 0) {
|
|
306
|
+
// Emit the real git diff verbatim. This natively handles cross-file
|
|
307
|
+
// healing, multi-line edits, and template-literal changes — none of
|
|
308
|
+
// which the legacy single-line -/+ pair could represent.
|
|
309
|
+
lines.push(stripDiffPreamble(h.gitUnifiedDiff));
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
// Fallback when git was unavailable / the file was untracked.
|
|
313
|
+
lines.push(`- ${(h.oldCodeLine ?? "").trim()}`);
|
|
314
|
+
lines.push(`+ ${(h.newCodeLine ?? "").trim()}`);
|
|
315
|
+
}
|
|
261
316
|
lines.push("```");
|
|
262
317
|
if (h.aiExplanation) {
|
|
263
318
|
lines.push(`> 🧠 ${h.aiExplanation}`);
|
|
@@ -8,7 +8,13 @@ export interface HealContext {
|
|
|
8
8
|
branch?: string;
|
|
9
9
|
}
|
|
10
10
|
export interface AIFixSummary {
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Raw AI confidence 0-100. `undefined` when the LLM omitted the field —
|
|
13
|
+
* SafetyGuard's hard-stop / review-threshold logic treats absence as the
|
|
14
|
+
* conservative worst case (0) so unverified heals are NOT silently let
|
|
15
|
+
* through, while downstream report renderers display "n/a".
|
|
16
|
+
*/
|
|
17
|
+
confidence?: number;
|
|
12
18
|
contentChange?: {
|
|
13
19
|
oldText: string;
|
|
14
20
|
newText: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafetyGuard.d.ts","sourceRoot":"","sources":["../../src/services/SafetyGuard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAMpE,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"SafetyGuard.d.ts","sourceRoot":"","sources":["../../src/services/SafetyGuard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAMpE,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,uEAAuE;IACvE,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,6DAA6D;IAC7D,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,iBAAiB,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,IAAI,CAAC,EAAE;QACL,cAAc,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,cAAc,CAAC;QAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,qFAAqF;QACrF,mBAAmB,CAAC,EAAE;YACpB,aAAa,EAAE,MAAM,CAAC;YACtB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC;YACd,kBAAkB,EAAE,MAAM,CAAC;SAC5B,CAAC;KACH,CAAC;CACH;AA6DD,KAAK,cAAc,GACf,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,MAAM,GACN,iBAAiB,GACjB,iBAAiB,GACjB,SAAS,CAAC;AA2Id,qBAAa,WAAW;IACtB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAqB;gBAE3B,UAAU,CAAC,EAAE,kBAAkB;IASrC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IAyTlF,OAAO,CAAC,kBAAkB;IAY1B,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc;IAsBpD,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAiBvD,OAAO,CAAC,mBAAmB;IAyB3B,OAAO,CAAC,iBAAiB;CAY1B"}
|
|
@@ -199,7 +199,13 @@ class SafetyGuard {
|
|
|
199
199
|
// PUBLIC ENTRY POINT
|
|
200
200
|
// ─────────────────────────────────────────────────────────────
|
|
201
201
|
async evaluate(context, aiFix) {
|
|
202
|
-
const {
|
|
202
|
+
const { liveText, dnaBaselineText } = aiFix;
|
|
203
|
+
// Conservative fallback for missing confidence: treat as 0 so the
|
|
204
|
+
// hardStop / reviewThreshold rules still gate the heal. Reporting layers
|
|
205
|
+
// separately render the absence as "n/a" via fmtPct.
|
|
206
|
+
const confidence = typeof aiFix.confidence === "number" && Number.isFinite(aiFix.confidence)
|
|
207
|
+
? aiFix.confidence
|
|
208
|
+
: 0;
|
|
203
209
|
const { mode } = context;
|
|
204
210
|
// ── 0. Visibility Gate ───────────────────────────────────────
|
|
205
211
|
// Runs before any confidence or content checks. A ghost element
|