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,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/services/ReportGenerator.ts
|
|
3
|
+
//
|
|
4
|
+
// Sela Insights — Pure (no-IO) report layer.
|
|
5
|
+
//
|
|
6
|
+
// Responsibilities:
|
|
7
|
+
// 1. SCHEMA VALIDATION — every event is validated against
|
|
8
|
+
// MANDATORY_FIELDS_BY_KIND before the HTML template ever sees it.
|
|
9
|
+
// 2. STATS + METRICS — computeStats(), computeRoiMetrics(), and
|
|
10
|
+
// buildCurrentHistoricalRun() are the single source of truth for
|
|
11
|
+
// every number rendered in the Management Dashboard.
|
|
12
|
+
// 3. CONFIDENCE SAFETY — formatConfidence() always emits "<n>%" or
|
|
13
|
+
// "n/a"; never "undefined", "null", or "NaN".
|
|
14
|
+
// 4. NO IO — disk operations stay in HealReportService so
|
|
15
|
+
// this module is trivially unit-testable without mocking fs.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ReportGenerator = exports.MANDATORY_FIELDS_BY_KIND = exports.TIME_SAVED_CONSTANTS = void 0;
|
|
18
|
+
const SelaError_1 = require("../errors/SelaError");
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// TIME-SAVED HEURISTICS (minutes per event kind)
|
|
21
|
+
// Exposed as a constant so tests can pin the math against the same multipliers
|
|
22
|
+
// as production without duplicating them.
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
exports.TIME_SAVED_CONSTANTS = {
|
|
25
|
+
/** A healed selector saved an engineer from manually hunting + patching. */
|
|
26
|
+
PER_HEAL: 12,
|
|
27
|
+
/**
|
|
28
|
+
* Catching a masked regression is worth more — it would have silently
|
|
29
|
+
* changed test semantics and required a full debugging cycle to uncover.
|
|
30
|
+
*/
|
|
31
|
+
PER_PROTECT: 18,
|
|
32
|
+
/**
|
|
33
|
+
* Partial value: Sela narrowed the debug scope even when it couldn't fix.
|
|
34
|
+
*/
|
|
35
|
+
PER_FAILED: 4,
|
|
36
|
+
};
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// FIELD MANIFESTS — mandatory per event kind
|
|
39
|
+
//
|
|
40
|
+
// "Mandatory" means the HTML card renderer produces a broken / misleading UI
|
|
41
|
+
// without this field. Optional fields belong in the interface declarations in
|
|
42
|
+
// HealReportService.ts but NOT here.
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
const COMMON_MANDATORY = [
|
|
45
|
+
"kind",
|
|
46
|
+
"stableId",
|
|
47
|
+
"sourceFile",
|
|
48
|
+
"sourceLine",
|
|
49
|
+
"timestamp",
|
|
50
|
+
];
|
|
51
|
+
const HEALED_MANDATORY = [
|
|
52
|
+
"oldSelector",
|
|
53
|
+
"newSelector",
|
|
54
|
+
"dnaBefore",
|
|
55
|
+
];
|
|
56
|
+
const FAILED_MANDATORY = [
|
|
57
|
+
"oldSelector",
|
|
58
|
+
"reason",
|
|
59
|
+
];
|
|
60
|
+
const PROTECTED_MANDATORY = [
|
|
61
|
+
"oldSelector",
|
|
62
|
+
"reason",
|
|
63
|
+
"safetyLevel",
|
|
64
|
+
];
|
|
65
|
+
exports.MANDATORY_FIELDS_BY_KIND = {
|
|
66
|
+
HEALED: [...COMMON_MANDATORY, ...HEALED_MANDATORY],
|
|
67
|
+
FAILED: [...COMMON_MANDATORY, ...FAILED_MANDATORY],
|
|
68
|
+
PROTECTED: [...COMMON_MANDATORY, ...PROTECTED_MANDATORY],
|
|
69
|
+
};
|
|
70
|
+
const VALID_KINDS = new Set(["HEALED", "FAILED", "PROTECTED"]);
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
// HELPERS
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
function throwReport(code, reason, ctx = {}) {
|
|
75
|
+
throw new SelaError_1.SelaError({ subsystem: "Report", code, reason, context: ctx });
|
|
76
|
+
}
|
|
77
|
+
function isPresent(value) {
|
|
78
|
+
if (value === undefined || value === null)
|
|
79
|
+
return false;
|
|
80
|
+
if (typeof value === "string" && value.length === 0)
|
|
81
|
+
return false;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
function shortDateLabel(iso) {
|
|
85
|
+
try {
|
|
86
|
+
const d = new Date(iso);
|
|
87
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return iso.slice(0, 10);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function mean(values) {
|
|
94
|
+
if (values.length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
return Math.round(values.reduce((a, b) => a + b, 0) / values.length);
|
|
97
|
+
}
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
// ReportGenerator
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
class ReportGenerator {
|
|
102
|
+
// ── Validation ────────────────────────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Validates a single event against MANDATORY_FIELDS_BY_KIND.
|
|
105
|
+
* Throws a SelaError(subsystem="Report") on the first violation.
|
|
106
|
+
*/
|
|
107
|
+
static validateEvent(event) {
|
|
108
|
+
if (!event || typeof event !== "object") {
|
|
109
|
+
throwReport(SelaError_1.REPORT_CODES.MALFORMED_EVENT, `Event must be an object, received ${event === null ? "null" : typeof event}`);
|
|
110
|
+
}
|
|
111
|
+
const ev = event;
|
|
112
|
+
if (!ev.kind || !VALID_KINDS.has(ev.kind)) {
|
|
113
|
+
throwReport(SelaError_1.REPORT_CODES.UNKNOWN_KIND, `Unknown event.kind "${String(ev.kind)}" — must be HEALED|FAILED|PROTECTED`, { stableId: ev.stableId });
|
|
114
|
+
}
|
|
115
|
+
const required = exports.MANDATORY_FIELDS_BY_KIND[ev.kind];
|
|
116
|
+
for (const field of required) {
|
|
117
|
+
const val = ev[field];
|
|
118
|
+
if (!isPresent(val)) {
|
|
119
|
+
throwReport(SelaError_1.REPORT_CODES.MISSING_FIELD, `${ev.kind} event missing mandatory field "${field}"`, { stableId: ev.stableId, kind: ev.kind, field });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (typeof ev.sourceLine !== "number") {
|
|
123
|
+
throwReport(SelaError_1.REPORT_CODES.INVALID_FIELD_TYPE, `Field "sourceLine" must be a number, received ${typeof ev.sourceLine}`, { stableId: ev.stableId, kind: ev.kind, field: "sourceLine" });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Validates every event in the buffer. Returns the typed array when all
|
|
128
|
+
* events pass. Throws on the first violation.
|
|
129
|
+
*/
|
|
130
|
+
static validateAll(events) {
|
|
131
|
+
for (const e of events)
|
|
132
|
+
ReportGenerator.validateEvent(e);
|
|
133
|
+
return events;
|
|
134
|
+
}
|
|
135
|
+
// ── Stats ─────────────────────────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Pure aggregate counters. No clock, no IO — trivially pinnable in tests.
|
|
138
|
+
*/
|
|
139
|
+
static computeStats(events) {
|
|
140
|
+
let healed = 0, failed = 0, protectedCnt = 0, iframeCnt = 0, shadowCnt = 0;
|
|
141
|
+
for (const e of events) {
|
|
142
|
+
if (e.kind === "HEALED")
|
|
143
|
+
healed++;
|
|
144
|
+
else if (e.kind === "FAILED")
|
|
145
|
+
failed++;
|
|
146
|
+
else if (e.kind === "PROTECTED")
|
|
147
|
+
protectedCnt++;
|
|
148
|
+
if (e.inIframe)
|
|
149
|
+
iframeCnt++;
|
|
150
|
+
if (e.inShadowDom)
|
|
151
|
+
shadowCnt++;
|
|
152
|
+
}
|
|
153
|
+
const timeSavedMin = healed * exports.TIME_SAVED_CONSTANTS.PER_HEAL +
|
|
154
|
+
protectedCnt * exports.TIME_SAVED_CONSTANTS.PER_PROTECT +
|
|
155
|
+
failed * exports.TIME_SAVED_CONSTANTS.PER_FAILED;
|
|
156
|
+
return {
|
|
157
|
+
total: events.length,
|
|
158
|
+
healed,
|
|
159
|
+
failed,
|
|
160
|
+
protectedCnt,
|
|
161
|
+
iframeCnt,
|
|
162
|
+
shadowCnt,
|
|
163
|
+
timeSavedMin,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// ── ROI Metrics ───────────────────────────────────────────────────────────
|
|
167
|
+
/**
|
|
168
|
+
* Derives Management Dashboard KPIs from the event buffer + historical runs.
|
|
169
|
+
*
|
|
170
|
+
* `historicalRuns` should already include the current run if you want
|
|
171
|
+
* cumulative totals to include this run — pass the array AFTER appending
|
|
172
|
+
* via `buildCurrentHistoricalRun()`.
|
|
173
|
+
*/
|
|
174
|
+
static computeRoiMetrics(events, historicalRuns, stats) {
|
|
175
|
+
// bugs prevented = all PROTECTED events this run
|
|
176
|
+
const bugsPrevented = stats.protectedCnt;
|
|
177
|
+
// cumulative time saved across all historical runs (includes current if
|
|
178
|
+
// caller appended it before passing here)
|
|
179
|
+
const timeSavedCumulativeMin = historicalRuns.reduce((sum, r) => sum + r.time_saved_min, 0);
|
|
180
|
+
// high-confidence rate: among events that have an auditor confidence,
|
|
181
|
+
// what fraction scored ≥ 80?
|
|
182
|
+
const audited = events.filter((e) => (e.kind === "HEALED" || e.kind === "PROTECTED") &&
|
|
183
|
+
e.auditor != null &&
|
|
184
|
+
typeof e.auditor.confidence === "number" &&
|
|
185
|
+
isFinite(e.auditor.confidence));
|
|
186
|
+
const highConfidenceRatePct = audited.length === 0
|
|
187
|
+
? null
|
|
188
|
+
: Math.round((audited.filter((e) => (e.auditor.confidence ?? 0) >= 80).length /
|
|
189
|
+
audited.length) *
|
|
190
|
+
100);
|
|
191
|
+
// avg heal confidence (HEALED events only)
|
|
192
|
+
const healedWithConf = events.filter((e) => e.kind === "HEALED")
|
|
193
|
+
.map((e) => e.aiConfidence)
|
|
194
|
+
.filter((v) => typeof v === "number" && isFinite(v));
|
|
195
|
+
const avgHealConfidence = mean(healedWithConf);
|
|
196
|
+
return {
|
|
197
|
+
timeSavedThisRunMin: stats.timeSavedMin,
|
|
198
|
+
timeSavedCumulativeMin,
|
|
199
|
+
bugsPrevented,
|
|
200
|
+
highConfidenceRatePct,
|
|
201
|
+
avgHealConfidence,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// ── Historical run builder ────────────────────────────────────────────────
|
|
205
|
+
/**
|
|
206
|
+
* Builds the `HistoricalRun` entry for the current run so the caller can
|
|
207
|
+
* append it to the persisted sidecar file.
|
|
208
|
+
*
|
|
209
|
+
* `runId` — optional override; defaults to ISO timestamp. Pass a CI run ID
|
|
210
|
+
* (e.g. `process.env.GITHUB_RUN_ID`) when available so deduplication is
|
|
211
|
+
* reliable across retries.
|
|
212
|
+
*/
|
|
213
|
+
static buildCurrentHistoricalRun(events, stats, opts = {}) {
|
|
214
|
+
const now = opts.now ? opts.now() : new Date();
|
|
215
|
+
const ts = now.toISOString();
|
|
216
|
+
const confidenceValues = events
|
|
217
|
+
.filter((e) => typeof e.aiConfidence === "number")
|
|
218
|
+
.map((e) => e.aiConfidence)
|
|
219
|
+
.filter((v) => isFinite(v));
|
|
220
|
+
const avg_confidence = confidenceValues.length > 0
|
|
221
|
+
? Math.round(confidenceValues.reduce((a, b) => a + b, 0) /
|
|
222
|
+
confidenceValues.length)
|
|
223
|
+
: 0;
|
|
224
|
+
return {
|
|
225
|
+
id: opts.runId ?? ts,
|
|
226
|
+
label: shortDateLabel(ts),
|
|
227
|
+
healed: stats.healed,
|
|
228
|
+
protected: stats.protectedCnt,
|
|
229
|
+
failed: stats.failed,
|
|
230
|
+
avg_confidence,
|
|
231
|
+
time_saved_min: stats.timeSavedMin,
|
|
232
|
+
timestamp: ts,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
// ── Full payload builder ──────────────────────────────────────────────────
|
|
236
|
+
/**
|
|
237
|
+
* Validates every event, computes all metrics, and assembles the
|
|
238
|
+
* `ReportData` payload ready for serialisation.
|
|
239
|
+
*
|
|
240
|
+
* `historicalRuns` — pass the array from `.sela-history.json` BEFORE
|
|
241
|
+
* appending the current run; this method appends it internally so the
|
|
242
|
+
* trend chart always includes the present run.
|
|
243
|
+
*/
|
|
244
|
+
static generate(events, opts = {}) {
|
|
245
|
+
const validated = ReportGenerator.validateAll(events);
|
|
246
|
+
const now = opts.now ?? (() => new Date());
|
|
247
|
+
const stats = ReportGenerator.computeStats(validated);
|
|
248
|
+
const currentRun = ReportGenerator.buildCurrentHistoricalRun(validated, stats, { runId: opts.runId, now });
|
|
249
|
+
// Merge history: drop any prior entry with the same id, append current,
|
|
250
|
+
// then keep only the most recent HISTORY_WINDOW entries.
|
|
251
|
+
const HISTORY_WINDOW = 14;
|
|
252
|
+
const prior = (opts.historicalRuns ?? []).filter((r) => r.id !== currentRun.id);
|
|
253
|
+
const historical_runs = [...prior, currentRun].slice(-HISTORY_WINDOW);
|
|
254
|
+
const roi = ReportGenerator.computeRoiMetrics(validated, historical_runs, stats);
|
|
255
|
+
return {
|
|
256
|
+
generatedAt: now().toISOString(),
|
|
257
|
+
startedAt: opts.startedAt ?? now().toISOString(),
|
|
258
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
259
|
+
events: validated,
|
|
260
|
+
stats,
|
|
261
|
+
roi,
|
|
262
|
+
historical_runs,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// ── Confidence formatter ──────────────────────────────────────────────────
|
|
266
|
+
/**
|
|
267
|
+
* Format a confidence value for UI consumption.
|
|
268
|
+
* Returns a percentage string ("87%") for finite numbers, "n/a" for
|
|
269
|
+
* anything else. NEVER emits literal "undefined" / "null" / "NaN".
|
|
270
|
+
*/
|
|
271
|
+
static formatConfidence(value) {
|
|
272
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
273
|
+
return `${Math.max(0, Math.min(100, Math.round(value)))}%`;
|
|
274
|
+
}
|
|
275
|
+
return "n/a";
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
exports.ReportGenerator = ReportGenerator;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type HistoricalRun, type ReportData } from "./ReportGenerator.js";
|
|
2
|
+
import { type HealEvent } from "./HealReportService.js";
|
|
3
|
+
export interface MergeOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Explicit shard report files (`sela-report.json` shape, or a bare event
|
|
6
|
+
* array). When empty, the caller is expected to have discovered files via
|
|
7
|
+
* {@link discoverShardFiles}.
|
|
8
|
+
*/
|
|
9
|
+
shardFiles: string[];
|
|
10
|
+
/** Where the merged report + rolling history are written. */
|
|
11
|
+
targetDir: string;
|
|
12
|
+
/**
|
|
13
|
+
* Single dedup key for the merged history row. Defaults to
|
|
14
|
+
* GITHUB_RUN_ID ?? CI_PIPELINE_ID ?? ISO timestamp (resolved by
|
|
15
|
+
* ReportGenerator). Pass explicitly in CI so retries overwrite cleanly.
|
|
16
|
+
*/
|
|
17
|
+
runId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Number of shards the pipeline launched. When provided and more than the
|
|
20
|
+
* number of readable shard files, the merge is flagged as partial.
|
|
21
|
+
*/
|
|
22
|
+
expectedShards?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Optional logger seam (tests inject a spy). Defaults to console.
|
|
25
|
+
*/
|
|
26
|
+
logger?: {
|
|
27
|
+
warn: (msg: string) => void;
|
|
28
|
+
info: (msg: string) => void;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface MergeResult {
|
|
32
|
+
htmlPath: string;
|
|
33
|
+
/** Total events read across all shard files BEFORE dedup. */
|
|
34
|
+
totalEvents: number;
|
|
35
|
+
/** Events surviving content-identity dedup (== payload event count). */
|
|
36
|
+
uniqueEvents: number;
|
|
37
|
+
/** totalEvents - uniqueEvents. */
|
|
38
|
+
duplicatesDropped: number;
|
|
39
|
+
/** Shard files successfully parsed. */
|
|
40
|
+
shardsRead: number;
|
|
41
|
+
/** Shard files that existed/were passed but failed to parse. */
|
|
42
|
+
shardsSkipped: number;
|
|
43
|
+
/** expectedShards echoed back, or null when not supplied. */
|
|
44
|
+
shardsExpected: number | null;
|
|
45
|
+
/** max(0, expectedShards - shardsRead); 0 when not supplied. */
|
|
46
|
+
shardsMissing: number;
|
|
47
|
+
/** True when shardsMissing > 0 — the report is built from partial data. */
|
|
48
|
+
partial: boolean;
|
|
49
|
+
/** Human-readable advisories surfaced during the merge. */
|
|
50
|
+
warnings: string[];
|
|
51
|
+
/** The full computed payload (handy for tests / programmatic callers). */
|
|
52
|
+
data: ReportData;
|
|
53
|
+
}
|
|
54
|
+
export declare class ReportMergeService {
|
|
55
|
+
/**
|
|
56
|
+
* Discover shard report files inside `dir` whose filename matches `glob`.
|
|
57
|
+
* Only a single `*` wildcard is supported (e.g. `sela-shard-*.json`) — we
|
|
58
|
+
* deliberately avoid pulling in a glob dependency. Returns absolute paths,
|
|
59
|
+
* sorted for deterministic ordering.
|
|
60
|
+
*/
|
|
61
|
+
static discoverShardFiles(dir: string, glob?: string): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Content identity for an event. A retried test replayed on a different
|
|
64
|
+
* shard yields an identical identity and is collapsed to one. Timestamp is
|
|
65
|
+
* intentionally EXCLUDED — clock skew across runners must not defeat dedup.
|
|
66
|
+
*/
|
|
67
|
+
static eventIdentity(e: HealEvent): string;
|
|
68
|
+
/**
|
|
69
|
+
* Read one shard file and return its events array.
|
|
70
|
+
* Accepts the `sela-report.json` shape (`{ events: [...] }`) or a bare
|
|
71
|
+
* `HealEvent[]`. Throws SelaError(SHARD_UNREADABLE) on malformed input so
|
|
72
|
+
* the caller can decide whether a single bad shard is fatal (it is not —
|
|
73
|
+
* `merge()` catches and flags it).
|
|
74
|
+
*/
|
|
75
|
+
static readShardEvents(file: string): HealEvent[];
|
|
76
|
+
/**
|
|
77
|
+
* Read the prior rolling history with a STRICT integrity guard.
|
|
78
|
+
* • Absent file → [] (first run / clean bootstrap).
|
|
79
|
+
* • Present, valid JSON array → the array.
|
|
80
|
+
* • Present but NOT a JSON array (corrupt / truncated / wrong shape)
|
|
81
|
+
* → THROW.
|
|
82
|
+
*
|
|
83
|
+
* The throw is the whole point: in the artifact flow the prior history was
|
|
84
|
+
* just downloaded; if it is corrupt we must NOT overwrite the durable copy
|
|
85
|
+
* with a fresh 1-entry window. Aborting forces an operator to look instead
|
|
86
|
+
* of silently destroying the trend.
|
|
87
|
+
*/
|
|
88
|
+
static readPriorHistory(targetDir: string): HistoricalRun[];
|
|
89
|
+
/**
|
|
90
|
+
* Merge N shard reports into a single coherent history row + report.
|
|
91
|
+
* See the file header for the full rationale.
|
|
92
|
+
*/
|
|
93
|
+
static merge(opts: MergeOptions): MergeResult;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=ReportMergeService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReportMergeService.d.ts","sourceRoot":"","sources":["../../src/services/ReportMergeService.ts"],"names":[],"mappings":"AAyCA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAQ3E,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACvE;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0EAA0E;IAC1E,IAAI,EAAE,UAAU,CAAC;CAClB;AAMD,qBAAa,kBAAkB;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAsB,GAAG,MAAM,EAAE;IAe5E;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM;IAO1C;;;;;;OAMG;IACH,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE;IA2CjD;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,EAAE;IAuC3D;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW;CA6F9C"}
|
|
Binary file
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { IntentAuditor } from "./IntentAuditor";
|
|
2
|
+
import { ResolvedThresholds } from "../config/SelaConfig";
|
|
3
|
+
import { VisibilityInfo, AncestorNode, AnchorInfo } from "../types";
|
|
4
|
+
import { SafetyGuardCode } from "../errors/SelaError";
|
|
5
|
+
import { FunctionalRole } from "./safety/roleClassifier";
|
|
6
|
+
export type HealMode = "assertion" | "action";
|
|
7
|
+
export interface HealContext {
|
|
8
|
+
mode: HealMode;
|
|
9
|
+
action?: string;
|
|
10
|
+
/** Current git branch; used for occlusion policy (hard-fail on main). */
|
|
11
|
+
branch?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AIFixSummary {
|
|
14
|
+
/**
|
|
15
|
+
* Raw AI confidence 0-100. `undefined` when the LLM omitted the field -
|
|
16
|
+
* SafetyGuard's hard-stop / review-threshold logic treats absence as the
|
|
17
|
+
* conservative worst case (0) so unverified heals are NOT silently let
|
|
18
|
+
* through, while downstream report renderers display "n/a".
|
|
19
|
+
*/
|
|
20
|
+
confidence?: number;
|
|
21
|
+
contentChange?: {
|
|
22
|
+
oldText: string;
|
|
23
|
+
newText: string;
|
|
24
|
+
};
|
|
25
|
+
explanation?: string;
|
|
26
|
+
/** Live innerText of the new selector fetched directly from the browser. */
|
|
27
|
+
liveText?: string;
|
|
28
|
+
/** Baseline text from the DNA snapshot - what the element showed before. */
|
|
29
|
+
dnaBaselineText?: string;
|
|
30
|
+
/** ARIA role from the DNA snapshot - used by the Intent Auditor. */
|
|
31
|
+
dnaRole?: string;
|
|
32
|
+
/** Visibility info captured from the candidate element (new selector). */
|
|
33
|
+
liveVisibility?: VisibilityInfo;
|
|
34
|
+
/** Ancestry chain captured from the candidate element at heal-time. */
|
|
35
|
+
liveAncestry?: AncestorNode[];
|
|
36
|
+
/** Label/row anchors captured from the candidate element. */
|
|
37
|
+
liveAnchors?: AnchorInfo;
|
|
38
|
+
/** Ancestry chain from the DNA snapshot. */
|
|
39
|
+
dnaAncestry?: AncestorNode[];
|
|
40
|
+
/** Label/row anchors from the DNA snapshot. */
|
|
41
|
+
dnaAnchors?: AnchorInfo;
|
|
42
|
+
}
|
|
43
|
+
export type RiskLevel = "SAFE" | "REQUIRES_REVIEW" | "FAIL_HARD" | "BLOCKED";
|
|
44
|
+
/**
|
|
45
|
+
* Engine-facing action derived from the risk level (decision #1, quarantine):
|
|
46
|
+
* - APPLY : write the heal to source and use it this run (SAFE)
|
|
47
|
+
* - HOLD : propose the heal but DO NOT write or execute it;
|
|
48
|
+
* quarantined for explicit approval (REQUIRES_REVIEW)
|
|
49
|
+
* - REJECT : refuse the heal outright (BLOCKED / FAIL_HARD)
|
|
50
|
+
*
|
|
51
|
+
* `canProceed` is retained for back-compat (true ⇔ not REJECT); `disposition`
|
|
52
|
+
* is the three-way signal the engine branches on so that REQUIRES_REVIEW no
|
|
53
|
+
* longer silently writes to disk.
|
|
54
|
+
*/
|
|
55
|
+
export type SafetyDisposition = "APPLY" | "HOLD" | "REJECT";
|
|
56
|
+
export declare function dispositionFor(level: RiskLevel): SafetyDisposition;
|
|
57
|
+
export interface SafetyDecision {
|
|
58
|
+
level: RiskLevel;
|
|
59
|
+
reason: string;
|
|
60
|
+
canProceed: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Three-way engine action derived from `level`. Populated centrally by
|
|
63
|
+
* evaluate(); rules need not set it. Optional on the type so test
|
|
64
|
+
* fixtures can build bare decisions, but always present on a value
|
|
65
|
+
* returned from evaluate().
|
|
66
|
+
*/
|
|
67
|
+
disposition?: SafetyDisposition;
|
|
68
|
+
/**
|
|
69
|
+
* Machine-readable error code from SAFETY_GUARD_CODES. Always populated
|
|
70
|
+
* for rejection decisions (BLOCKED / FAIL_HARD) so callers can throw a
|
|
71
|
+
* SelaError without re-parsing `reason`. Pass-through decisions (SAFE /
|
|
72
|
+
* REQUIRES_REVIEW) may omit it.
|
|
73
|
+
*/
|
|
74
|
+
code?: SafetyGuardCode;
|
|
75
|
+
/** Structured info for policy-aware log messages in SelaEngine. */
|
|
76
|
+
meta?: {
|
|
77
|
+
auditorVerdict?: "CONSISTENT" | "SUSPICIOUS" | "INCONSISTENT";
|
|
78
|
+
auditorConfidence?: number;
|
|
79
|
+
/** Score breakdown forwarded from IntentAuditor for engine-level summary logging. */
|
|
80
|
+
auditScoreBreakdown?: {
|
|
81
|
+
rawConfidence: number;
|
|
82
|
+
penalty: number;
|
|
83
|
+
bonus: number;
|
|
84
|
+
adjustedConfidence: number;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Constructor options for SafetyGuard.
|
|
90
|
+
*
|
|
91
|
+
* - `thresholds`: per-instance threshold overrides (existing behavior).
|
|
92
|
+
* - `intentAuditor`: pre-built IntentAuditor. Tests inject a stub so
|
|
93
|
+
* SafetyGuard can be unit-tested without an Anthropic key.
|
|
94
|
+
*/
|
|
95
|
+
export interface SafetyGuardOptions {
|
|
96
|
+
thresholds?: ResolvedThresholds;
|
|
97
|
+
intentAuditor?: IntentAuditor;
|
|
98
|
+
}
|
|
99
|
+
export declare class SafetyGuard {
|
|
100
|
+
private intentAuditor;
|
|
101
|
+
private thresholds;
|
|
102
|
+
/**
|
|
103
|
+
* Accepts either the legacy positional `thresholds` argument or an
|
|
104
|
+
* options bag (DI-friendly). Existing call sites keep working
|
|
105
|
+
* unchanged.
|
|
106
|
+
*/
|
|
107
|
+
constructor(arg?: ResolvedThresholds | SafetyGuardOptions);
|
|
108
|
+
private static normalizeArgs;
|
|
109
|
+
evaluate(context: HealContext, aiFix: AIFixSummary): Promise<SafetyDecision>;
|
|
110
|
+
classifyFunctionalRole(text: string): FunctionalRole;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=SafetyGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SafetyGuard.d.ts","sourceRoot":"","sources":["../../src/services/SafetyGuard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/services/SafetyGuard.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.SafetyGuard = void 0;
|
|
5
|
+
exports.dispositionFor = dispositionFor;
|
|
6
|
+
const IntentAuditor_1 = require("./IntentAuditor");
|
|
7
|
+
const PipelineContext_1 = require("./safety/PipelineContext");
|
|
8
|
+
const rules_1 = require("./safety/rules");
|
|
9
|
+
const roleClassifier_1 = require("./safety/roleClassifier");
|
|
10
|
+
function dispositionFor(level) {
|
|
11
|
+
switch (level) {
|
|
12
|
+
case "SAFE":
|
|
13
|
+
return "APPLY";
|
|
14
|
+
case "REQUIRES_REVIEW":
|
|
15
|
+
return "HOLD";
|
|
16
|
+
case "BLOCKED":
|
|
17
|
+
case "FAIL_HARD":
|
|
18
|
+
return "REJECT";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
22
|
+
// DEFAULT THRESHOLDS (match pre-config hardcoded values exactly)
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
24
|
+
const DEFAULT_THRESHOLDS = {
|
|
25
|
+
confidenceHardStop: 85,
|
|
26
|
+
confidenceReviewThreshold: 95,
|
|
27
|
+
auditorConfidenceMin: 75,
|
|
28
|
+
allowSuspicious: false,
|
|
29
|
+
auditorStrictness: "balanced",
|
|
30
|
+
};
|
|
31
|
+
class SafetyGuard {
|
|
32
|
+
intentAuditor;
|
|
33
|
+
thresholds;
|
|
34
|
+
/**
|
|
35
|
+
* Accepts either the legacy positional `thresholds` argument or an
|
|
36
|
+
* options bag (DI-friendly). Existing call sites keep working
|
|
37
|
+
* unchanged.
|
|
38
|
+
*/
|
|
39
|
+
constructor(arg) {
|
|
40
|
+
const opts = SafetyGuard.normalizeArgs(arg);
|
|
41
|
+
this.thresholds = opts.thresholds ?? DEFAULT_THRESHOLDS;
|
|
42
|
+
this.intentAuditor = opts.intentAuditor ?? new IntentAuditor_1.IntentAuditor();
|
|
43
|
+
}
|
|
44
|
+
static normalizeArgs(arg) {
|
|
45
|
+
if (!arg)
|
|
46
|
+
return {};
|
|
47
|
+
// Heuristic: an options bag has either `intentAuditor` or a nested
|
|
48
|
+
// `thresholds` field; otherwise the value itself is a ResolvedThresholds.
|
|
49
|
+
if ("intentAuditor" in arg || "thresholds" in arg) {
|
|
50
|
+
return arg;
|
|
51
|
+
}
|
|
52
|
+
return { thresholds: arg };
|
|
53
|
+
}
|
|
54
|
+
// ─────────────────────────────────────────────────────────────
|
|
55
|
+
// PUBLIC ENTRY POINT
|
|
56
|
+
//
|
|
57
|
+
// Builds the shared PipelineContext, then runs the ordered rule
|
|
58
|
+
// chain. The first rule to return a SafetyDecision short-circuits;
|
|
59
|
+
// the terminal ConfidenceTierRule always returns, so the loop never
|
|
60
|
+
// falls through (the post-loop throw is a defensive invariant guard).
|
|
61
|
+
// ─────────────────────────────────────────────────────────────
|
|
62
|
+
async evaluate(context, aiFix) {
|
|
63
|
+
const ctx = (0, PipelineContext_1.buildContext)(context, aiFix, this.thresholds, this.intentAuditor);
|
|
64
|
+
for (const rule of rules_1.SAFETY_RULES) {
|
|
65
|
+
const decision = await rule.run(ctx);
|
|
66
|
+
if (decision) {
|
|
67
|
+
// Stamp the engine-facing disposition centrally so every rule's
|
|
68
|
+
// verdict carries it without each rule having to set it.
|
|
69
|
+
decision.disposition = dispositionFor(decision.level);
|
|
70
|
+
return decision;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Unreachable: ConfidenceTierRule is terminal and always decides.
|
|
74
|
+
throw new Error("[SafetyGuard] rule pipeline exhausted without a decision - this is a bug");
|
|
75
|
+
}
|
|
76
|
+
// ─────────────────────────────────────────────────────────────
|
|
77
|
+
// FUNCTIONAL ROLE CLASSIFICATION (public for tests / external use)
|
|
78
|
+
//
|
|
79
|
+
// Thin delegate to the extracted pure classifier. Kept on the class
|
|
80
|
+
// so existing callers (`new SafetyGuard().classifyFunctionalRole(...)`)
|
|
81
|
+
// and the adversarial test suite continue to work unchanged.
|
|
82
|
+
// ─────────────────────────────────────────────────────────────
|
|
83
|
+
classifyFunctionalRole(text) {
|
|
84
|
+
return (0, roleClassifier_1.classifyFunctionalRole)(text);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.SafetyGuard = SafetyGuard;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SanitizeOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Mask the `value=` of `<input>` elements whose type is password/tel/email.
|
|
4
|
+
* Structural (type-driven), so it never touches a button label. Default true.
|
|
5
|
+
*/
|
|
6
|
+
redactInputValues?: boolean;
|
|
7
|
+
/** Extra already-compiled patterns, each masked to [REDACTED]. */
|
|
8
|
+
customPatterns?: RegExp[];
|
|
9
|
+
}
|
|
10
|
+
export declare const REDACTIONS: {
|
|
11
|
+
readonly EMAIL: "[EMAIL_REDACTED]";
|
|
12
|
+
readonly CREDIT_CARD: "[CC_REDACTED]";
|
|
13
|
+
readonly CVV: "[CVV_REDACTED]";
|
|
14
|
+
readonly PHONE: "[PHONE_REDACTED]";
|
|
15
|
+
readonly PASSWORD: "[PASSWORD_REDACTED]";
|
|
16
|
+
readonly BEARER: "[BEARER_REDACTED]";
|
|
17
|
+
readonly TOKEN: "[TOKEN_REDACTED]";
|
|
18
|
+
readonly GENERIC: "[REDACTED]";
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Mask PII/secrets in a single string. Pure — returns a new string; the input
|
|
22
|
+
* is never mutated. Safe to call on already-sanitized text (idempotent on the
|
|
23
|
+
* placeholders, which contain no maskable patterns).
|
|
24
|
+
*/
|
|
25
|
+
export declare function sanitizeText(input: string, opts?: SanitizeOptions): string;
|
|
26
|
+
/** Compile customPatterns (regex source strings) → RegExp[]; skip invalid. */
|
|
27
|
+
export declare function compileCustomPatterns(sources: string[]): RegExp[];
|
|
28
|
+
//# sourceMappingURL=Sanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sanitizer.d.ts","sourceRoot":"","sources":["../../src/services/Sanitizer.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,UAAU;;;;;;;;;CASb,CAAC;AA8CX;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,MAAM,CAyC9E;AAsBD,8EAA8E;AAC9E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAYjE"}
|