sela-core 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +158 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +166 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- package/dist/cli/commands/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- package/dist/cli/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/showReport.d.ts +3 -0
- package/dist/cli/commands/showReport.d.ts.map +1 -0
- package/dist/cli/commands/showReport.js +82 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +131 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +94 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +77 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +295 -0
- package/dist/config/ConfigLoader.d.ts +16 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +185 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +80 -0
- package/dist/config/PathScopeGuard.d.ts +42 -0
- package/dist/config/PathScopeGuard.d.ts.map +1 -0
- package/dist/config/PathScopeGuard.js +192 -0
- package/dist/config/SelaConfig.d.ts +178 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +141 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +99 -0
- package/dist/engine/SelaEngine.d.ts +57 -0
- package/dist/engine/SelaEngine.d.ts.map +1 -0
- package/dist/engine/SelaEngine.js +1146 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/errors/SelaError.d.ts +146 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +168 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +246 -0
- package/dist/fixtures/index.d.ts +30 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +765 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +54 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/reporter/SelaReporter.d.ts +165 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +517 -0
- package/dist/services/ASTSourceUpdater.d.ts +143 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3514 -0
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +107 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +570 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +135 -0
- package/dist/services/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +366 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +217 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealBudget.d.ts +32 -0
- package/dist/services/HealBudget.d.ts.map +1 -0
- package/dist/services/HealBudget.js +85 -0
- package/dist/services/HealReportService.d.ts +161 -0
- package/dist/services/HealReportService.d.ts.map +1 -0
- package/dist/services/HealReportService.js +1039 -0
- package/dist/services/HealingAdvisory.d.ts +30 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +51 -0
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +404 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +73 -0
- package/dist/services/IntentAuditor.d.ts +56 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +326 -0
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +128 -0
- package/dist/services/LLMService.d.ts +121 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +475 -0
- package/dist/services/MutationApplier.d.ts +69 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +360 -0
- package/dist/services/PRAutomationService.d.ts +50 -0
- package/dist/services/PRAutomationService.d.ts.map +1 -0
- package/dist/services/PRAutomationService.js +760 -0
- package/dist/services/PendingPromptLedger.d.ts +51 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +205 -0
- package/dist/services/ReportGenerator.d.ts +156 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +278 -0
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +112 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +87 -0
- package/dist/services/Sanitizer.d.ts +28 -0
- package/dist/services/Sanitizer.d.ts.map +1 -0
- package/dist/services/Sanitizer.js +142 -0
- package/dist/services/SanitizingAnthropic.d.ts +19 -0
- package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
- package/dist/services/SanitizingAnthropic.js +84 -0
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +352 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +165 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1091 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +334 -0
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/services/safety/PipelineContext.d.ts +63 -0
- package/dist/services/safety/PipelineContext.d.ts.map +1 -0
- package/dist/services/safety/PipelineContext.js +34 -0
- package/dist/services/safety/roleClassifier.d.ts +4 -0
- package/dist/services/safety/roleClassifier.d.ts.map +1 -0
- package/dist/services/safety/roleClassifier.js +233 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
- package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
- package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
- package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
- package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
- package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
- package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
- package/dist/services/safety/rules/VisibilityRule.js +53 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
- package/dist/services/safety/rules/index.d.ts +3 -0
- package/dist/services/safety/rules/index.d.ts.map +1 -0
- package/dist/services/safety/rules/index.js +34 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +32 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +49 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +466 -0
- package/dist/utils/FileLock.d.ts +44 -0
- package/dist/utils/FileLock.d.ts.map +1 -0
- package/dist/utils/FileLock.js +189 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/utils/SummaryTable.d.ts +17 -0
- package/dist/utils/SummaryTable.d.ts.map +1 -0
- package/dist/utils/SummaryTable.js +37 -0
- package/dist/utils/atomicWrite.d.ts +9 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +83 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +123 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- package/package.json +71 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HealthReportService = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function isWithin7Days(iso) {
|
|
10
|
+
return new Date(iso).getTime() >= Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
11
|
+
}
|
|
12
|
+
function topHealedFiles(records) {
|
|
13
|
+
const counts = new Map();
|
|
14
|
+
for (const r of records) {
|
|
15
|
+
if (r.lastHealed !== null && r.sourceFile) {
|
|
16
|
+
counts.set(r.sourceFile, (counts.get(r.sourceFile) ?? 0) + 1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [...counts.entries()]
|
|
20
|
+
.sort((a, b) => b[1] - a[1])
|
|
21
|
+
.map(([sourceFile, count]) => ({ sourceFile, count }));
|
|
22
|
+
}
|
|
23
|
+
class HealthReportService {
|
|
24
|
+
generate(index) {
|
|
25
|
+
const { records, snapshotDir, generatedAt } = index;
|
|
26
|
+
return {
|
|
27
|
+
generatedAt,
|
|
28
|
+
snapshotDir,
|
|
29
|
+
total: records.length,
|
|
30
|
+
orphaned: records.filter((r) => r.isOrphaned),
|
|
31
|
+
unverifiedHeals: records.filter((r) => !r.isOrphaned && r.status === "healed"),
|
|
32
|
+
recentlyHealed: records.filter((r) => r.lastHealed !== null && isWithin7Days(r.lastHealed)),
|
|
33
|
+
neverHealed: records.filter((r) => r.lastHealed === null),
|
|
34
|
+
topHealedFiles: topHealedFiles(records),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
format(report) {
|
|
38
|
+
const lines = [""];
|
|
39
|
+
lines.push(chalk_1.default.bold.cyan(" Sela Health Report") +
|
|
40
|
+
chalk_1.default.dim(` (${new Date(report.generatedAt).toISOString()})`));
|
|
41
|
+
lines.push("");
|
|
42
|
+
const row = (label, value) => ` ${chalk_1.default.dim(label.padEnd(20))} ${value}`;
|
|
43
|
+
lines.push(row("Total DNAs", chalk_1.default.bold(String(report.total))));
|
|
44
|
+
const orphanCount = report.orphaned.length;
|
|
45
|
+
lines.push(row("Orphaned", orphanCount > 0 ? chalk_1.default.yellow(`${orphanCount} ⚠`) : chalk_1.default.green("0")));
|
|
46
|
+
const unverifiedCount = report.unverifiedHeals.length;
|
|
47
|
+
lines.push(row("Unverified heals", unverifiedCount > 0
|
|
48
|
+
? chalk_1.default.yellow(`${unverifiedCount} ⚠ ${chalk_1.default.dim("(AI-healed, needs review)")}`)
|
|
49
|
+
: chalk_1.default.green("0")));
|
|
50
|
+
const brokenTotal = orphanCount + unverifiedCount;
|
|
51
|
+
const brokenRatio = report.total > 0
|
|
52
|
+
? ((brokenTotal / report.total) * 100).toFixed(1)
|
|
53
|
+
: "0.0";
|
|
54
|
+
lines.push(row("Broken ratio", (brokenTotal > 0 ? chalk_1.default.yellow : chalk_1.default.green)(`${brokenRatio}% ${chalk_1.default.dim(`(${brokenTotal}/${report.total})`)}`)));
|
|
55
|
+
lines.push(row("Healed (7 days)", chalk_1.default.bold(String(report.recentlyHealed.length))));
|
|
56
|
+
lines.push(row("Healed (all time)", chalk_1.default.bold(String(report.total - report.neverHealed.length))));
|
|
57
|
+
lines.push(row("Never healed", chalk_1.default.bold(String(report.neverHealed.length))));
|
|
58
|
+
if (report.topHealedFiles.length > 0) {
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(` ${chalk_1.default.bold("Top healed files:")}`);
|
|
61
|
+
for (const { sourceFile, count } of report.topHealedFiles.slice(0, 5)) {
|
|
62
|
+
const rel = path_1.default.relative(process.cwd(), sourceFile);
|
|
63
|
+
const label = (rel.length < sourceFile.length ? rel : sourceFile)
|
|
64
|
+
.slice(-60)
|
|
65
|
+
.padEnd(62);
|
|
66
|
+
const noun = count === 1 ? "heal" : "heals";
|
|
67
|
+
lines.push(` ${chalk_1.default.cyan(label)} ${chalk_1.default.dim("-")} ${chalk_1.default.bold(String(count))} ${chalk_1.default.dim(noun)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (report.orphaned.length > 0) {
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(` ${chalk_1.default.yellow.bold("Orphaned DNA files:")}`);
|
|
73
|
+
for (const r of report.orphaned.slice(0, 10)) {
|
|
74
|
+
lines.push(` ${chalk_1.default.dim(r.key)} ${chalk_1.default.yellow(r.sourceFile)}`);
|
|
75
|
+
}
|
|
76
|
+
if (report.orphaned.length > 10) {
|
|
77
|
+
lines.push(chalk_1.default.dim(` … and ${report.orphaned.length - 10} more`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
lines.push("");
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.HealthReportService = HealthReportService;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Node, SourceFile } from "ts-morph";
|
|
2
|
+
export interface InitializerResult {
|
|
3
|
+
lineUpdated: number;
|
|
4
|
+
healedLocatorString: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class InitializerUpdater {
|
|
7
|
+
/**
|
|
8
|
+
* Mutate a StringLiteral or NoSubstitutionTemplateLiteral node to
|
|
9
|
+
* hold newValue, then save the containing source file.
|
|
10
|
+
*
|
|
11
|
+
* Returns null if the node type is not directly mutable or if the
|
|
12
|
+
* mutation throws (caller should fall through to existing strategies).
|
|
13
|
+
*/
|
|
14
|
+
static apply(valueNode: Node, sourceFile: SourceFile, newValue: string): InitializerResult | null;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=InitializerUpdater.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InitializerUpdater.d.ts","sourceRoot":"","sources":["../../src/services/InitializerUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAC;AAiB3D,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,kBAAkB;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CACV,SAAS,EAAE,IAAI,EACf,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,iBAAiB,GAAG,IAAI;CAwD5B"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InitializerUpdater = void 0;
|
|
4
|
+
const ts_morph_1 = require("ts-morph");
|
|
5
|
+
const TemplateDiffService_js_1 = require("./TemplateDiffService.js");
|
|
6
|
+
const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
7
|
+
const PathScopeGuard_1 = require("../config/PathScopeGuard");
|
|
8
|
+
const WorkspaceSnapshotService_1 = require("./WorkspaceSnapshotService");
|
|
9
|
+
const SelectorSanitizer_1 = require("./SelectorSanitizer");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
class InitializerUpdater {
|
|
12
|
+
/**
|
|
13
|
+
* Mutate a StringLiteral or NoSubstitutionTemplateLiteral node to
|
|
14
|
+
* hold newValue, then save the containing source file.
|
|
15
|
+
*
|
|
16
|
+
* Returns null if the node type is not directly mutable or if the
|
|
17
|
+
* mutation throws (caller should fall through to existing strategies).
|
|
18
|
+
*/
|
|
19
|
+
static apply(valueNode, sourceFile, newValue) {
|
|
20
|
+
// ABSOLUTE GUARD: never serialise a Playwright internal engine selector
|
|
21
|
+
// into a string-literal definition. `.locator()` strings cannot express
|
|
22
|
+
// a semantic locator (getByRole/…), so writing `internal:role=…` here is
|
|
23
|
+
// the bug that leaks engine internals into user source. Refuse → the
|
|
24
|
+
// caller escalates to a semantic-guard (no regex fallback corruption).
|
|
25
|
+
if ((0, SelectorSanitizer_1.isInternalEngineSelector)(newValue)) {
|
|
26
|
+
logger_1.logger.warn(`Refusing to write internal engine selector into source: "${newValue.slice(0, 64)}"`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
// Safe path scoping (T1): block (and skip the in-memory AST mutation for)
|
|
30
|
+
// files outside allowedPaths before touching the node.
|
|
31
|
+
if (!PathScopeGuard_1.PathScopeGuard.enforce(sourceFile.getFilePath())) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
if (ts_morph_1.Node.isStringLiteral(valueNode)) {
|
|
36
|
+
valueNode.setLiteralValue(newValue);
|
|
37
|
+
}
|
|
38
|
+
else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(valueNode)) {
|
|
39
|
+
valueNode.replaceWithText("`" + (0, TemplateDiffService_js_1.escapeForTemplate)(newValue) + "`");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
// Centralised write gate - mirrors ASTSourceUpdater._persistChanges.
|
|
45
|
+
// InitializerUpdater is the canonical write-head for definition-site
|
|
46
|
+
// mutations (SemanticDispatch → tracer-resolved identifier / property
|
|
47
|
+
// access / cross-file paths all converge here), so the Clean-Room
|
|
48
|
+
// snapshot ledger and DryRunGuard MUST be enforced here too.
|
|
49
|
+
// Without this, an upstream-traced fix (e.g. global StringLiteral at
|
|
50
|
+
// top of file) writes to disk but `WorkspaceSnapshotService` never
|
|
51
|
+
// sees it - `getMutatedSnapshots()` returns empty and PR automation
|
|
52
|
+
// skips the heal entirely.
|
|
53
|
+
const fp = sourceFile.getFilePath();
|
|
54
|
+
if (DryRunGuard_1.DryRunGuard.active()) {
|
|
55
|
+
DryRunGuard_1.DryRunGuard.logSkippedWrite("saveSync", fp);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.preWrite(fp);
|
|
59
|
+
sourceFile.saveSync();
|
|
60
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.postWrite(fp);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
lineUpdated: valueNode.getStartLineNumber() - 1,
|
|
64
|
+
healedLocatorString: newValue,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
logger_1.logger.warn(`InitializerUpdater apply failed: ${e.message}`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.InitializerUpdater = InitializerUpdater;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { AncestorNode, AnchorInfo } from "../types";
|
|
3
|
+
export interface AuditRequest {
|
|
4
|
+
dnaText: string;
|
|
5
|
+
dnaRole?: string;
|
|
6
|
+
liveText: string;
|
|
7
|
+
liveRole?: string;
|
|
8
|
+
healMode: "assertion" | "action";
|
|
9
|
+
/** Ancestry chain from the DNA snapshot (empty for migrated v1 snapshots). */
|
|
10
|
+
dnaAncestry?: AncestorNode[];
|
|
11
|
+
/** Ancestry chain freshly captured from the candidate element at heal-time. */
|
|
12
|
+
liveAncestry?: AncestorNode[];
|
|
13
|
+
/** Label/row anchors from the DNA snapshot. */
|
|
14
|
+
dnaAnchors?: AnchorInfo;
|
|
15
|
+
/** Label/row anchors freshly captured from the candidate element. */
|
|
16
|
+
liveAnchors?: AnchorInfo;
|
|
17
|
+
}
|
|
18
|
+
export type AuditVerdictValue = "CONSISTENT" | "INCONSISTENT" | "SUSPICIOUS";
|
|
19
|
+
export interface AuditVerdict {
|
|
20
|
+
verdict: AuditVerdictValue;
|
|
21
|
+
confidence: number;
|
|
22
|
+
reason: string;
|
|
23
|
+
inversionType: "polarity" | "quantitative" | "state" | "identity" | null;
|
|
24
|
+
/** Populated when structural scoring adjusted the raw LLM confidence. */
|
|
25
|
+
scoreBreakdown?: {
|
|
26
|
+
rawConfidence: number;
|
|
27
|
+
penalty: number;
|
|
28
|
+
bonus: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Constructor options for IntentAuditor.
|
|
33
|
+
*
|
|
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).
|
|
40
|
+
*/
|
|
41
|
+
export interface IntentAuditorOptions {
|
|
42
|
+
anthropic?: Anthropic | null;
|
|
43
|
+
apiKey?: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare class IntentAuditor {
|
|
47
|
+
private anthropic;
|
|
48
|
+
private readonly disabled;
|
|
49
|
+
private readonly model;
|
|
50
|
+
constructor(opts?: IntentAuditorOptions);
|
|
51
|
+
auditIntent(req: AuditRequest): Promise<AuditVerdict>;
|
|
52
|
+
computeAncestryPenalty(dnaPath: AncestorNode[], livePath: AncestorNode[]): number;
|
|
53
|
+
computeAnchorBonus(dnaAnchors: AnchorInfo | null | undefined, liveAnchors: AnchorInfo | null | undefined): number;
|
|
54
|
+
private parseVerdict;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=IntentAuditor.d.ts.map
|
|
@@ -0,0 +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;AASpD,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;AAkED;;;;;;;;;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;IA4CT,kBAAkB,CAChB,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACzC,WAAW,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACzC,MAAM;IAmCT,OAAO,CAAC,YAAY;CA8CrB"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IntentAuditor = void 0;
|
|
37
|
+
const dotenv = __importStar(require("dotenv"));
|
|
38
|
+
const SanitizingAnthropic_1 = require("./SanitizingAnthropic");
|
|
39
|
+
const logger_1 = require("../utils/logger");
|
|
40
|
+
dotenv.config();
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
42
|
+
// CONSTANTS
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
44
|
+
const FALLBACK_VERDICT = {
|
|
45
|
+
verdict: "SUSPICIOUS",
|
|
46
|
+
confidence: 0,
|
|
47
|
+
reason: "Auditor unavailable - defaulting to cautious SUSPICIOUS verdict",
|
|
48
|
+
inversionType: null,
|
|
49
|
+
};
|
|
50
|
+
// Ancestry drift penalties (spec §2.1).
|
|
51
|
+
// Applied as negative adjustments to the LLM's confidence score.
|
|
52
|
+
// Maximum combined penalty: −30 pts.
|
|
53
|
+
const DRIFT_PENALTIES = [
|
|
54
|
+
{ full: -20, partial: -10 }, // depth 1 - direct parent
|
|
55
|
+
{ full: -10, partial: -5 }, // depth 2 - grandparent
|
|
56
|
+
];
|
|
57
|
+
// Anchor bonuses: positive-only (spec §3.3).
|
|
58
|
+
// closestLabel exact match: +10 pts
|
|
59
|
+
// rowAnchor exact match: +5 pts
|
|
60
|
+
// Maximum combined bonus: +15 pts
|
|
61
|
+
const ANCHOR_BONUS_LABEL = 10;
|
|
62
|
+
const ANCHOR_BONUS_ROW = 5;
|
|
63
|
+
const SYSTEM_PROMPT = `You are a semantic intent auditor for a self-healing test automation system.
|
|
64
|
+
|
|
65
|
+
Your job: given a BEFORE text (DNA baseline) and an AFTER text (live element), decide whether the element's semantic intent is consistent, inconsistent, or suspicious.
|
|
66
|
+
|
|
67
|
+
You must respond with ONLY a JSON object. No prose, no markdown, no explanation outside the JSON.
|
|
68
|
+
|
|
69
|
+
VERDICT DEFINITIONS:
|
|
70
|
+
- CONSISTENT: The semantic intent is preserved. The element represents the same concept, state, or action. Minor wording, phrasing, or language differences are acceptable (e.g. "Submit" → "Send", "Username" → "Email address", "Connexion" → "Login"). Small quantitative changes that are not inversions are acceptable (e.g. "3 items" → "4 items").
|
|
71
|
+
- INCONSISTENT: The semantic intent has inverted or fundamentally changed. The element now represents an opposite or unrelated concept. Examples: "Success" → "Error", "Enabled" → "Disabled", "Login" → "Logout", "Confirm" → "Cancel", "Add" → "Remove".
|
|
72
|
+
- SUSPICIOUS: Cannot determine with confidence. The change is ambiguous, the texts are too short to judge, or the relationship is unclear. Err toward SUSPICIOUS when unsure.
|
|
73
|
+
|
|
74
|
+
INVERSION TYPES (populate when INCONSISTENT, null otherwise):
|
|
75
|
+
- "polarity": Boolean/state opposites (enabled/disabled, on/off, true/false, success/failure)
|
|
76
|
+
- "state": Workflow or lifecycle state inversions (login/logout, open/closed, connected/disconnected)
|
|
77
|
+
- "quantitative": Numeric inversions that suggest logic change (not just count changes)
|
|
78
|
+
- "identity": Fundamentally different concept (e.g. "Delete" button became "Save" button)
|
|
79
|
+
|
|
80
|
+
RULES:
|
|
81
|
+
1. Language is irrelevant. "Connexion" and "Login" are CONSISTENT.
|
|
82
|
+
2. Casing is irrelevant. "SUBMIT" and "submit" are CONSISTENT.
|
|
83
|
+
3. Minor reformatting is irrelevant. "Save Changes" and "Save" are CONSISTENT.
|
|
84
|
+
4. Role/aria context matters. A button that was "Submit" becoming "Cancel" is INCONSISTENT (identity inversion).
|
|
85
|
+
5. When both texts are very short (3 chars or less) and differ, prefer SUSPICIOUS unless an obvious opposite pair.
|
|
86
|
+
6. In "assertion" mode, be stricter - lean toward SUSPICIOUS or INCONSISTENT when the change is ambiguous.
|
|
87
|
+
7. In "action" mode, be more lenient - lean toward CONSISTENT when the change looks like a UI refactor.
|
|
88
|
+
|
|
89
|
+
RESPONSE FORMAT (strict JSON, no other text):
|
|
90
|
+
{
|
|
91
|
+
"verdict": "CONSISTENT" | "INCONSISTENT" | "SUSPICIOUS",
|
|
92
|
+
"confidence": <integer 0-100>,
|
|
93
|
+
"reason": "<one sentence explaining the verdict>",
|
|
94
|
+
"inversionType": "polarity" | "quantitative" | "state" | "identity" | null
|
|
95
|
+
}`;
|
|
96
|
+
const DEFAULT_AUDITOR_MODEL = "claude-haiku-4-5-20251001";
|
|
97
|
+
class IntentAuditor {
|
|
98
|
+
anthropic;
|
|
99
|
+
disabled;
|
|
100
|
+
model;
|
|
101
|
+
constructor(opts = {}) {
|
|
102
|
+
this.model = opts.model ?? DEFAULT_AUDITOR_MODEL;
|
|
103
|
+
// Explicit `null` from the caller forces disabled mode - used by tests.
|
|
104
|
+
if (opts.anthropic === null) {
|
|
105
|
+
this.anthropic = null;
|
|
106
|
+
this.disabled = true;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Caller supplied a ready-to-use client → use it as-is.
|
|
110
|
+
if (opts.anthropic !== undefined) {
|
|
111
|
+
this.anthropic = opts.anthropic;
|
|
112
|
+
this.disabled = false;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Otherwise fall back to env-based construction (legacy default).
|
|
116
|
+
const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
117
|
+
if (!apiKey) {
|
|
118
|
+
logger_1.logger.warn("ANTHROPIC_API_KEY not set - auditor disabled, will return SUSPICIOUS for all audited heals");
|
|
119
|
+
this.anthropic = null;
|
|
120
|
+
this.disabled = true;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Route through the PII/secrets sanitizer (T3): the auditor prompt
|
|
124
|
+
// embeds live element text, so it must be scrubbed before leaving too.
|
|
125
|
+
this.anthropic = (0, SanitizingAnthropic_1.createSanitizingAnthropic)({ apiKey });
|
|
126
|
+
this.disabled = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async auditIntent(req) {
|
|
130
|
+
if (this.disabled || !this.anthropic) {
|
|
131
|
+
return { ...FALLBACK_VERDICT };
|
|
132
|
+
}
|
|
133
|
+
const userMessage = [
|
|
134
|
+
`BEFORE (DNA baseline): "${req.dnaText}"`,
|
|
135
|
+
req.dnaRole ? `BEFORE role: ${req.dnaRole}` : null,
|
|
136
|
+
`AFTER (live text): "${req.liveText}"`,
|
|
137
|
+
req.liveRole ? `AFTER role: ${req.liveRole}` : null,
|
|
138
|
+
`Heal mode: ${req.healMode}`,
|
|
139
|
+
]
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.join("\n");
|
|
142
|
+
try {
|
|
143
|
+
const response = await this.anthropic.messages.create({
|
|
144
|
+
model: this.model,
|
|
145
|
+
max_tokens: 256,
|
|
146
|
+
temperature: 0.1,
|
|
147
|
+
system: SYSTEM_PROMPT,
|
|
148
|
+
messages: [{ role: "user", content: userMessage }],
|
|
149
|
+
});
|
|
150
|
+
const raw = response.content[0].type === "text" ? response.content[0].text : "";
|
|
151
|
+
// parseVerdict returns the raw LLM verdict without the low-confidence
|
|
152
|
+
// downgrade so that scoring adjustments below are applied first.
|
|
153
|
+
const rawVerdict = this.parseVerdict(raw);
|
|
154
|
+
// ── Structural scoring ────────────────────────────────────────
|
|
155
|
+
// Apply ancestry drift penalty and anchor bonus to the LLM's raw
|
|
156
|
+
// confidence. The semantic verdict category (CONSISTENT / SUSPICIOUS /
|
|
157
|
+
// INCONSISTENT) is never changed by scoring - only the confidence value.
|
|
158
|
+
// The low-confidence CONSISTENT→SUSPICIOUS downgrade is re-evaluated
|
|
159
|
+
// after scoring so the adjusted value is used for the threshold.
|
|
160
|
+
const penalty = this.computeAncestryPenalty(req.dnaAncestry ?? [], req.liveAncestry ?? []);
|
|
161
|
+
const bonus = this.computeAnchorBonus(req.dnaAnchors, req.liveAnchors);
|
|
162
|
+
const scoreAdjustment = penalty + bonus;
|
|
163
|
+
let finalVerdict;
|
|
164
|
+
if (scoreAdjustment !== 0) {
|
|
165
|
+
const adjConf = Math.max(0, Math.min(100, rawVerdict.confidence + scoreAdjustment));
|
|
166
|
+
const parts = [];
|
|
167
|
+
if (penalty !== 0)
|
|
168
|
+
parts.push(`drift ${penalty}`);
|
|
169
|
+
if (bonus !== 0)
|
|
170
|
+
parts.push(`anchor +${bonus}`);
|
|
171
|
+
logger_1.logger.debug(`Confidence: ${rawVerdict.confidence}% → ${adjConf}% (${parts.join(", ")})`);
|
|
172
|
+
// Re-apply low-confidence CONSISTENT downgrade with the adjusted value.
|
|
173
|
+
// The raw check in parseVerdict used the pre-adjustment confidence; a
|
|
174
|
+
// penalty may push a borderline CONSISTENT below the 50% threshold.
|
|
175
|
+
const scoring = {
|
|
176
|
+
rawConfidence: rawVerdict.confidence,
|
|
177
|
+
penalty,
|
|
178
|
+
bonus,
|
|
179
|
+
};
|
|
180
|
+
if (rawVerdict.verdict === "CONSISTENT" && adjConf < 50) {
|
|
181
|
+
finalVerdict = {
|
|
182
|
+
verdict: "SUSPICIOUS",
|
|
183
|
+
confidence: adjConf,
|
|
184
|
+
reason: `Low-confidence CONSISTENT downgraded after drift/anchor scoring: ${rawVerdict.reason}`,
|
|
185
|
+
inversionType: null,
|
|
186
|
+
scoreBreakdown: scoring,
|
|
187
|
+
};
|
|
188
|
+
logger_1.logger.debug(`CONSISTENT→SUSPICIOUS after scoring (${rawVerdict.confidence}%→${adjConf}%)`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
finalVerdict = {
|
|
192
|
+
...rawVerdict,
|
|
193
|
+
confidence: adjConf,
|
|
194
|
+
scoreBreakdown: scoring,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
finalVerdict = rawVerdict;
|
|
200
|
+
}
|
|
201
|
+
logger_1.logger.debug(`verdict=${finalVerdict.verdict} (${finalVerdict.confidence}%) - ${finalVerdict.reason}`);
|
|
202
|
+
return finalVerdict;
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
logger_1.logger.warn(`Auditor call failed: ${err.message} - returning SUSPICIOUS`);
|
|
206
|
+
return {
|
|
207
|
+
...FALLBACK_VERDICT,
|
|
208
|
+
reason: `Auditor call failed: ${err.message}`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ─────────────────────────────────────────────────────────────
|
|
213
|
+
// computeAncestryPenalty - DNA v2 Ancestry Drift (spec §2)
|
|
214
|
+
//
|
|
215
|
+
// Compares at most 2 ancestor depths between the DNA baseline and
|
|
216
|
+
// the live candidate. Returns a value in [−30, 0]; 0 = no drift.
|
|
217
|
+
//
|
|
218
|
+
// Empty dnaPath → 0 penalty (v1 migrated snapshot has no data).
|
|
219
|
+
// livePath shorter than dnaPath → compare up to min length (§2.5).
|
|
220
|
+
//
|
|
221
|
+
// Penalty table (§2.1):
|
|
222
|
+
// depth 1 full mismatch −20 pts partial mismatch −10 pts
|
|
223
|
+
// depth 2 full mismatch −10 pts partial mismatch −5 pts
|
|
224
|
+
//
|
|
225
|
+
// Full mismatch = tags differ
|
|
226
|
+
// Partial mismatch = same tag, fingerprint differs (id or classes)
|
|
227
|
+
// ─────────────────────────────────────────────────────────────
|
|
228
|
+
computeAncestryPenalty(dnaPath, livePath) {
|
|
229
|
+
if (dnaPath.length === 0)
|
|
230
|
+
return 0; // v1 migrated snapshot - skip
|
|
231
|
+
let totalPenalty = 0;
|
|
232
|
+
const maxDepth = Math.min(dnaPath.length, livePath.length, 2);
|
|
233
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
234
|
+
const dna = dnaPath[i];
|
|
235
|
+
const live = livePath[i];
|
|
236
|
+
const { full, partial } = DRIFT_PENALTIES[i];
|
|
237
|
+
if (dna.fingerprint === live.fingerprint) {
|
|
238
|
+
continue; // exact match, no penalty
|
|
239
|
+
}
|
|
240
|
+
const depthLabel = i === 0 ? "depth-1 (parent)" : "depth-2 (grandparent)";
|
|
241
|
+
if (dna.tag !== live.tag) {
|
|
242
|
+
totalPenalty += full;
|
|
243
|
+
logger_1.logger.debug(`Ancestry drift ${full} pts - ${depthLabel}: ` +
|
|
244
|
+
`tag mismatch (${dna.fingerprint} → ${live.fingerprint})`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
totalPenalty += partial;
|
|
248
|
+
logger_1.logger.debug(`Ancestry drift ${partial} pts - ${depthLabel}: ` +
|
|
249
|
+
`same tag, id/class changed (${dna.fingerprint} → ${live.fingerprint})`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return totalPenalty;
|
|
253
|
+
}
|
|
254
|
+
// ─────────────────────────────────────────────────────────────
|
|
255
|
+
// computeAnchorBonus - DNA v2 Anchor Confirmation (spec §3.3)
|
|
256
|
+
//
|
|
257
|
+
// Anchors only provide positive confirmation - no penalty for
|
|
258
|
+
// mismatch. Returns a value in [0, +15].
|
|
259
|
+
//
|
|
260
|
+
// closestLabel exact match (case-insensitive): +10 pts
|
|
261
|
+
// rowAnchor exact match (case-insensitive): +5 pts
|
|
262
|
+
// ─────────────────────────────────────────────────────────────
|
|
263
|
+
computeAnchorBonus(dnaAnchors, liveAnchors) {
|
|
264
|
+
if (!dnaAnchors || !liveAnchors)
|
|
265
|
+
return 0;
|
|
266
|
+
let bonus = 0;
|
|
267
|
+
const dnaLabel = dnaAnchors.closestLabel?.toLowerCase().trim();
|
|
268
|
+
const liveLabel = liveAnchors.closestLabel?.toLowerCase().trim();
|
|
269
|
+
if (dnaLabel && liveLabel && dnaLabel === liveLabel) {
|
|
270
|
+
bonus += ANCHOR_BONUS_LABEL;
|
|
271
|
+
logger_1.logger.debug(`Anchor +${ANCHOR_BONUS_LABEL} pts - closestLabel match: "${dnaAnchors.closestLabel}"`);
|
|
272
|
+
}
|
|
273
|
+
const dnaRow = dnaAnchors.rowAnchor?.toLowerCase().trim();
|
|
274
|
+
const liveRow = liveAnchors.rowAnchor?.toLowerCase().trim();
|
|
275
|
+
if (dnaRow && liveRow && dnaRow === liveRow) {
|
|
276
|
+
bonus += ANCHOR_BONUS_ROW;
|
|
277
|
+
logger_1.logger.debug(`Anchor +${ANCHOR_BONUS_ROW} pts - rowAnchor match: "${dnaAnchors.rowAnchor}"`);
|
|
278
|
+
}
|
|
279
|
+
return bonus;
|
|
280
|
+
}
|
|
281
|
+
// ─────────────────────────────────────────────────────────────
|
|
282
|
+
// PRIVATE: parse and validate raw LLM JSON response.
|
|
283
|
+
//
|
|
284
|
+
// Returns the verdict as-is from the LLM. The low-confidence
|
|
285
|
+
// CONSISTENT downgrade is intentionally NOT performed here so
|
|
286
|
+
// that auditIntent() can apply structural scoring first and then
|
|
287
|
+
// run the downgrade once against the adjusted confidence.
|
|
288
|
+
// ─────────────────────────────────────────────────────────────
|
|
289
|
+
parseVerdict(raw) {
|
|
290
|
+
try {
|
|
291
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
292
|
+
if (!jsonMatch)
|
|
293
|
+
throw new Error("No JSON object found in response");
|
|
294
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
295
|
+
const verdict = parsed.verdict === "CONSISTENT" ||
|
|
296
|
+
parsed.verdict === "INCONSISTENT" ||
|
|
297
|
+
parsed.verdict === "SUSPICIOUS"
|
|
298
|
+
? parsed.verdict
|
|
299
|
+
: "SUSPICIOUS";
|
|
300
|
+
const confidence = typeof parsed.confidence === "number"
|
|
301
|
+
? Math.max(0, Math.min(100, Math.round(parsed.confidence)))
|
|
302
|
+
: 0;
|
|
303
|
+
const reason = typeof parsed.reason === "string" && parsed.reason.length > 0
|
|
304
|
+
? parsed.reason
|
|
305
|
+
: "No reason provided";
|
|
306
|
+
const validInversionTypes = new Set([
|
|
307
|
+
"polarity",
|
|
308
|
+
"quantitative",
|
|
309
|
+
"state",
|
|
310
|
+
"identity",
|
|
311
|
+
]);
|
|
312
|
+
const inversionType = validInversionTypes.has(parsed.inversionType)
|
|
313
|
+
? parsed.inversionType
|
|
314
|
+
: null;
|
|
315
|
+
return { verdict, confidence, reason, inversionType };
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
logger_1.logger.warn(`Failed to parse Auditor response: ${err.message}`);
|
|
319
|
+
return {
|
|
320
|
+
...FALLBACK_VERDICT,
|
|
321
|
+
reason: `Parse failure: ${err.message}`,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
exports.IntentAuditor = IntentAuditor;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PendingPromptEntry } from "./PendingPromptLedger";
|
|
2
|
+
export interface PrintReviewDeps {
|
|
3
|
+
entries: PendingPromptEntry[];
|
|
4
|
+
/** Override the console sink - tests capture output for assertions. */
|
|
5
|
+
log?: (msg: string) => void;
|
|
6
|
+
/** Absolute path of the rendered Sela Insights HTML report, when present. */
|
|
7
|
+
reportHtmlPath?: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Print the post-run review summary. Returns immediately - never blocks
|
|
11
|
+
* on stdin. Safe to call from the reporter's onEnd() in any environment
|
|
12
|
+
* (TTY, piped CI, IDE-integrated runner).
|
|
13
|
+
*/
|
|
14
|
+
export declare function printHealReview(deps: PrintReviewDeps): void;
|
|
15
|
+
//# sourceMappingURL=InteractiveReview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InteractiveReview.d.ts","sourceRoot":"","sources":["../../src/services/InteractiveReview.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,uEAAuE;IACvE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CA6B3D"}
|