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,517 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/reporter/SelaReporter.ts
|
|
3
|
+
//
|
|
4
|
+
// SelaReporter - Playwright Reporter implementation that drives the
|
|
5
|
+
// Sela heal-and-report pipeline at the reporter layer instead of the
|
|
6
|
+
// fixture teardown layer. The fixture path remains the primary trigger
|
|
7
|
+
// for in-test heals; this reporter is the SAFETY NET for worker crashes,
|
|
8
|
+
// timeouts, and any failure mode that skips fixture teardown.
|
|
9
|
+
//
|
|
10
|
+
// Responsibilities:
|
|
11
|
+
// 1. onBegin - flips SELA_REPORTER_ACTIVE, installs SIGINT/SIGTERM
|
|
12
|
+
// graceful-shutdown handlers.
|
|
13
|
+
// 2. onTestEnd - extracts the first PNG screenshot from
|
|
14
|
+
// TestResult.attachments and converts it to Base64 IMMEDIATELY.
|
|
15
|
+
// Playwright's temp folder is cleaned up between runs, so the
|
|
16
|
+
// bytes MUST be captured into memory before the reporter loop ends.
|
|
17
|
+
// 3. onEnd - checks for ANTHROPIC_API_KEY, flushes pending screenshots,
|
|
18
|
+
// runs SelaEngine.commitUpdates(), then prints a *non-blocking*
|
|
19
|
+
// write-and-notify summary (unified diff per healed file +
|
|
20
|
+
// vscode://file deeplink + sela-report.html deeplink). MVP flow -
|
|
21
|
+
// no Y/N prompt, no rollback. Mutations are already on disk by the
|
|
22
|
+
// time we reach this hook.
|
|
23
|
+
// 4. SIGINT / SIGTERM - runs gracefulShutdown() once, second signal
|
|
24
|
+
// hard-exits. Prevents stranded child processes from showing raw
|
|
25
|
+
// Node unhandledRejection stack traces on Ctrl+C.
|
|
26
|
+
//
|
|
27
|
+
// Clean Architecture: zero business logic - IO + aggregation only.
|
|
28
|
+
// Heal decisions live in SelaEngine, validation in ReportGenerator,
|
|
29
|
+
// rendering in HealReportService.
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.SelaReporter = void 0;
|
|
65
|
+
const fs = __importStar(require("fs"));
|
|
66
|
+
const path = __importStar(require("path"));
|
|
67
|
+
const HealReportService_js_1 = require("../services/HealReportService.js");
|
|
68
|
+
const ReportMergeService_js_1 = require("../services/ReportMergeService.js");
|
|
69
|
+
const singleton_js_1 = require("../engine/singleton.js");
|
|
70
|
+
const SelaError_js_1 = require("../errors/SelaError.js");
|
|
71
|
+
const PendingPromptLedger_js_1 = require("../services/PendingPromptLedger.js");
|
|
72
|
+
const InteractiveReview_js_1 = require("../services/InteractiveReview.js");
|
|
73
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
74
|
+
const SummaryTable_js_1 = require("../utils/SummaryTable.js");
|
|
75
|
+
// ───────────────────────────────────────────────────────────────────
|
|
76
|
+
// SelaReporter
|
|
77
|
+
// ───────────────────────────────────────────────────────────────────
|
|
78
|
+
class SelaReporter {
|
|
79
|
+
engine;
|
|
80
|
+
report;
|
|
81
|
+
logger;
|
|
82
|
+
skipApiKeyCheck;
|
|
83
|
+
cwd;
|
|
84
|
+
readLedger;
|
|
85
|
+
clearLedger;
|
|
86
|
+
printReview;
|
|
87
|
+
resolveReportPath;
|
|
88
|
+
isCi;
|
|
89
|
+
mergeShards;
|
|
90
|
+
shouldInstallSignalHandlers;
|
|
91
|
+
shutdownHandler = null;
|
|
92
|
+
shuttingDown = false;
|
|
93
|
+
/** Total tests in the suite, captured at onBegin for the end-of-run summary. */
|
|
94
|
+
totalTests = 0;
|
|
95
|
+
/**
|
|
96
|
+
* Screenshots that arrived before a matching FailedEvent existed in
|
|
97
|
+
* the shared report. Re-flushed once at onEnd time so worker-crash
|
|
98
|
+
* scenarios (no fixture teardown ⇒ no engine-side FailedEvent yet)
|
|
99
|
+
* still get their visual context attached.
|
|
100
|
+
*
|
|
101
|
+
* Key shape: `${normalisedSourceFile}::${testTitle}`.
|
|
102
|
+
*/
|
|
103
|
+
pendingScreenshots = new Map();
|
|
104
|
+
/**
|
|
105
|
+
* Mirror of the failures the reporter observed, kept so external
|
|
106
|
+
* consumers (and tests) can audit what the reporter saw without
|
|
107
|
+
* having to subscribe to the entire Playwright Reporter API.
|
|
108
|
+
*/
|
|
109
|
+
failures = [];
|
|
110
|
+
constructor(opts = {}) {
|
|
111
|
+
this.engine = (opts.engine ?? singleton_js_1.sharedEngine);
|
|
112
|
+
this.report = opts.report ?? HealReportService_js_1.sharedHealReport;
|
|
113
|
+
this.skipApiKeyCheck = opts.skipApiKeyCheck ?? false;
|
|
114
|
+
this.logger = opts.logger ?? logger_js_1.logger;
|
|
115
|
+
this.cwd = opts.cwd ?? process.cwd();
|
|
116
|
+
this.readLedger = opts.readLedger ?? PendingPromptLedger_js_1.readPendingPromptsLedger;
|
|
117
|
+
this.clearLedger = opts.clearLedger ?? PendingPromptLedger_js_1.clearPendingPromptsLedger;
|
|
118
|
+
this.printReview = opts.printReview ?? InteractiveReview_js_1.printHealReview;
|
|
119
|
+
this.resolveReportPath = opts.resolveReportPath ?? defaultResolveReportPath;
|
|
120
|
+
this.isCi = opts.isCi ?? defaultIsCi;
|
|
121
|
+
this.mergeShards = opts.mergeShards ?? defaultMergeShards;
|
|
122
|
+
this.shouldInstallSignalHandlers = opts.installSignalHandlers ?? true;
|
|
123
|
+
}
|
|
124
|
+
// ─────────────────────────────────────────────────────────────
|
|
125
|
+
// Playwright Reporter API
|
|
126
|
+
// ─────────────────────────────────────────────────────────────
|
|
127
|
+
onBegin(_config, suite) {
|
|
128
|
+
// Signal to future fixture-aware code that a reporter is driving
|
|
129
|
+
// finalisation. Today the fixture still calls engine.commitUpdates()
|
|
130
|
+
// itself; the env flag is reserved for the planned dedup guard.
|
|
131
|
+
process.env.SELA_REPORTER_ACTIVE = "1";
|
|
132
|
+
// Snapshot the suite size for the end-of-run summary table. Guarded:
|
|
133
|
+
// allTests() exists on real Playwright suites but not on test stubs.
|
|
134
|
+
try {
|
|
135
|
+
this.totalTests = suite?.allTests?.().length ?? 0;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
this.totalTests = 0;
|
|
139
|
+
}
|
|
140
|
+
// SELA-5: drop any report shards stranded by a previously crashed run so
|
|
141
|
+
// this run's merge sees only its own workers' data.
|
|
142
|
+
try {
|
|
143
|
+
(0, HealReportService_js_1.clearReportShards)(this.cwd);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
/* best-effort — a stale shard dir is non-fatal */
|
|
147
|
+
}
|
|
148
|
+
if (this.shouldInstallSignalHandlers) {
|
|
149
|
+
this.installShutdownHandler();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
onTestEnd(test, result) {
|
|
153
|
+
if (result.status !== "failed" && result.status !== "timedOut")
|
|
154
|
+
return;
|
|
155
|
+
const sourceFile = test.location?.file ?? "";
|
|
156
|
+
const sourceLine = test.location?.line;
|
|
157
|
+
const testTitle = test.title;
|
|
158
|
+
const base64Png = this.extractScreenshotBase64(result);
|
|
159
|
+
if (base64Png) {
|
|
160
|
+
// First try to attach immediately - the engine may have already
|
|
161
|
+
// recorded the FailedEvent during fixture teardown for in-test
|
|
162
|
+
// heal failures. This is the happy path.
|
|
163
|
+
const matched = this.report.attachFailureScreenshot({ sourceFile: this.normalisePath(sourceFile), testTitle }, base64Png);
|
|
164
|
+
if (matched === 0) {
|
|
165
|
+
// No matching FailedEvent yet - buffer for the onEnd retry. This
|
|
166
|
+
// covers worker-crash and fixture-skip cases where the engine
|
|
167
|
+
// never got a chance to record the FailedEvent in-flight.
|
|
168
|
+
const key = this.pendingKey(sourceFile, testTitle);
|
|
169
|
+
this.pendingScreenshots.set(key, base64Png);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.failures.push({
|
|
173
|
+
sourceFile,
|
|
174
|
+
sourceLine,
|
|
175
|
+
testTitle,
|
|
176
|
+
status: result.status,
|
|
177
|
+
hasScreenshot: !!base64Png,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async onEnd(_result) {
|
|
181
|
+
// Detach signal handlers first - a natural onEnd is the orderly path,
|
|
182
|
+
// so we don't want a late SIGINT to double-invoke shutdown logic.
|
|
183
|
+
this.uninstallShutdownHandler();
|
|
184
|
+
if (!this.skipApiKeyCheck && !process.env.ANTHROPIC_API_KEY) {
|
|
185
|
+
const err = new SelaError_js_1.SelaError({
|
|
186
|
+
subsystem: "LLMService",
|
|
187
|
+
code: SelaError_js_1.LLM_SERVICE_CODES.NO_API_KEY,
|
|
188
|
+
reason: "ANTHROPIC_API_KEY not set - SelaReporter cannot drive the heal pipeline. " +
|
|
189
|
+
"Set the env var (or remove the reporter entry from playwright.config.ts).",
|
|
190
|
+
});
|
|
191
|
+
// Graceful degradation: surface the error and continue flushing
|
|
192
|
+
// any report content the fixture path already produced.
|
|
193
|
+
this.logger.warn(err.message);
|
|
194
|
+
}
|
|
195
|
+
// Late-bind any screenshots that arrived before their FailedEvents.
|
|
196
|
+
this.flushPendingScreenshots();
|
|
197
|
+
// Snapshot the run tallies BEFORE commitUpdates() — it flushes the report
|
|
198
|
+
// to disk and then clears the in-memory event buffer.
|
|
199
|
+
const summary = this.collectRunSummary();
|
|
200
|
+
try {
|
|
201
|
+
await this.engine.commitUpdates();
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
205
|
+
this.logger.error(`[Sela] reporter commitUpdates failed: ${msg}`);
|
|
206
|
+
}
|
|
207
|
+
// ── SELA-5: fan in per-worker heal shards ───────────────────
|
|
208
|
+
// Local multi-worker runs write one shard per worker (commitUpdates) to
|
|
209
|
+
// avoid clobbering a shared report file. The reporter is the single
|
|
210
|
+
// main-process writer, so it is the safe place to merge them into the
|
|
211
|
+
// final sela-report.{html,json}. CI uses cross-machine `sela merge`.
|
|
212
|
+
if (!this.isCi()) {
|
|
213
|
+
try {
|
|
214
|
+
this.mergeShards(this.cwd);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
218
|
+
this.logger.error(`[Sela] shard merge failed: ${msg}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// ── Local DX - Write-and-notify summary ─────────────────────
|
|
222
|
+
//
|
|
223
|
+
// Workers serialise their mutated FileSnapshots to
|
|
224
|
+
// `<cwd>/.sela/pending-prompts/*.json`. The reporter drains that
|
|
225
|
+
// ledger here and prints a non-blocking summary (diff + vscode
|
|
226
|
+
// deeplink + HTML report deeplink). No Y/N prompt - the files are
|
|
227
|
+
// already mutated on disk by the time we get here.
|
|
228
|
+
//
|
|
229
|
+
// CI runs skip the notify entirely - PR automation already handled
|
|
230
|
+
// the mutations on its isolated branch.
|
|
231
|
+
if (!this.isCi()) {
|
|
232
|
+
try {
|
|
233
|
+
const entries = this.readLedger(this.cwd);
|
|
234
|
+
const reportHtmlPath = this.resolveReportPath(this.cwd);
|
|
235
|
+
if (entries.length > 0 || reportHtmlPath) {
|
|
236
|
+
this.printReview({ entries, reportHtmlPath });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
241
|
+
this.logger.error(`[Sela] review summary failed: ${msg}`);
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
try {
|
|
245
|
+
this.clearLedger(this.cwd);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Cleanup failures are non-fatal - the next run will overwrite.
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// CI path: drop the ledger so a follow-up local run doesn't replay
|
|
254
|
+
// mutations from a previous CI session.
|
|
255
|
+
try {
|
|
256
|
+
this.clearLedger(this.cwd);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// ignore
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// End-of-run ASCII summary (skipped on a clean no-activity run).
|
|
263
|
+
this.renderRunSummary(summary);
|
|
264
|
+
}
|
|
265
|
+
/** Reporter prints nothing of its own - Playwright stdio stays clean. */
|
|
266
|
+
printsToStdio() {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
// ─────────────────────────────────────────────────────────────
|
|
270
|
+
// End-of-run summary
|
|
271
|
+
// ─────────────────────────────────────────────────────────────
|
|
272
|
+
/**
|
|
273
|
+
* Tally the run's outcome from the report buffer. MUST be called before
|
|
274
|
+
* commitUpdates() clears the in-memory events. "Errors detected" counts
|
|
275
|
+
* every broken selector Sela engaged (healed + held + failed); "skipped"
|
|
276
|
+
* counts heals declined by a developer fail-fast directive.
|
|
277
|
+
*/
|
|
278
|
+
collectRunSummary() {
|
|
279
|
+
const healed = this.report.getHealedEvents().length;
|
|
280
|
+
const held = this.report.getProtectedEvents().length;
|
|
281
|
+
const failedEvents = this.report.getFailedEvents();
|
|
282
|
+
const skipped = failedEvents.filter((e) => typeof e.reason === "string" &&
|
|
283
|
+
e.reason.startsWith("Skipped by Developer")).length;
|
|
284
|
+
return {
|
|
285
|
+
testsScanned: this.totalTests,
|
|
286
|
+
errorsDetected: healed + held + failedEvents.length,
|
|
287
|
+
successfulHeals: healed,
|
|
288
|
+
skippedFiles: skipped,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Render the summary table — but only when Sela actually did something.
|
|
293
|
+
* A run with zero detected errors prints nothing, keeping the terminal
|
|
294
|
+
* clean for the common all-green case.
|
|
295
|
+
*/
|
|
296
|
+
renderRunSummary(summary) {
|
|
297
|
+
if (summary.errorsDetected === 0)
|
|
298
|
+
return;
|
|
299
|
+
this.logger.info("\nSela run summary:");
|
|
300
|
+
this.logger.info((0, SummaryTable_js_1.renderSummaryTable)(summary));
|
|
301
|
+
}
|
|
302
|
+
// ─────────────────────────────────────────────────────────────
|
|
303
|
+
// Graceful shutdown - SIGINT / SIGTERM
|
|
304
|
+
// ─────────────────────────────────────────────────────────────
|
|
305
|
+
/**
|
|
306
|
+
* Install the SIGINT/SIGTERM listener once per reporter. First signal
|
|
307
|
+
* triggers an orderly drain; second signal hard-exits with code 130
|
|
308
|
+
* (SIGINT) / 143 (SIGTERM) so the user can always escape if the drain
|
|
309
|
+
* itself wedges (e.g. a stuck LLM call).
|
|
310
|
+
*/
|
|
311
|
+
installShutdownHandler() {
|
|
312
|
+
if (this.shutdownHandler)
|
|
313
|
+
return;
|
|
314
|
+
const handler = (signal) => {
|
|
315
|
+
if (this.shuttingDown) {
|
|
316
|
+
process.stderr.write(`\n[Sela] ⏹ Force exit on second ${signal}.\n`);
|
|
317
|
+
process.exit(signal === "SIGTERM" ? 143 : 130);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.shuttingDown = true;
|
|
321
|
+
process.stderr.write(`\n[Sela] 🛑 ${signal} received - flushing state and shutting down...\n`);
|
|
322
|
+
this.gracefulShutdown()
|
|
323
|
+
.catch((err) => {
|
|
324
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
325
|
+
process.stderr.write(`[Sela] Shutdown error: ${msg}\n`);
|
|
326
|
+
})
|
|
327
|
+
.finally(() => {
|
|
328
|
+
process.stderr.write(`[Sela] 👋 Goodbye.\n`);
|
|
329
|
+
process.exit(signal === "SIGTERM" ? 143 : 130);
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
this.shutdownHandler = handler;
|
|
333
|
+
process.on("SIGINT", handler);
|
|
334
|
+
process.on("SIGTERM", handler);
|
|
335
|
+
}
|
|
336
|
+
uninstallShutdownHandler() {
|
|
337
|
+
if (!this.shutdownHandler)
|
|
338
|
+
return;
|
|
339
|
+
process.off("SIGINT", this.shutdownHandler);
|
|
340
|
+
process.off("SIGTERM", this.shutdownHandler);
|
|
341
|
+
this.shutdownHandler = null;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Best-effort drain on signal. Mirrors the orderly onEnd path but
|
|
345
|
+
* swallows every error - we never want to throw inside a signal
|
|
346
|
+
* handler because Node prints raw stack traces otherwise.
|
|
347
|
+
*
|
|
348
|
+
* Exposed for tests so they can drive the shutdown without raising
|
|
349
|
+
* a real signal in the vitest process.
|
|
350
|
+
*/
|
|
351
|
+
async gracefulShutdown() {
|
|
352
|
+
try {
|
|
353
|
+
this.flushPendingScreenshots();
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// ignore
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
await this.engine.commitUpdates();
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
// ignore
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const entries = this.isCi() ? [] : this.readLedger(this.cwd);
|
|
366
|
+
const reportHtmlPath = this.resolveReportPath(this.cwd);
|
|
367
|
+
if (entries.length > 0 || reportHtmlPath) {
|
|
368
|
+
this.printReview({ entries, reportHtmlPath });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// ignore
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
this.clearLedger(this.cwd);
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// ignore
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// ─────────────────────────────────────────────────────────────
|
|
382
|
+
// Internals (exposed for testing - prefixed underscore is the
|
|
383
|
+
// convention used elsewhere in the codebase for proxy back-doors).
|
|
384
|
+
// ─────────────────────────────────────────────────────────────
|
|
385
|
+
/**
|
|
386
|
+
* Walk every buffered pending screenshot and try to attach it to a
|
|
387
|
+
* now-existing FailedEvent. Successfully-attached entries are
|
|
388
|
+
* removed; unmatched entries are dropped (the matching FailedEvent
|
|
389
|
+
* never arrived - most likely worker crashed before the engine
|
|
390
|
+
* could record one).
|
|
391
|
+
*
|
|
392
|
+
* Returns the number of FailedEvents touched.
|
|
393
|
+
*/
|
|
394
|
+
flushPendingScreenshots() {
|
|
395
|
+
let total = 0;
|
|
396
|
+
for (const [key, base64] of Array.from(this.pendingScreenshots.entries())) {
|
|
397
|
+
const sepIndex = key.indexOf("::");
|
|
398
|
+
if (sepIndex < 0) {
|
|
399
|
+
this.pendingScreenshots.delete(key);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const filePart = key.slice(0, sepIndex);
|
|
403
|
+
const titlePart = key.slice(sepIndex + 2);
|
|
404
|
+
const matched = this.report.attachFailureScreenshot({ sourceFile: filePart, testTitle: titlePart }, base64);
|
|
405
|
+
if (matched > 0) {
|
|
406
|
+
total += matched;
|
|
407
|
+
this.pendingScreenshots.delete(key);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return total;
|
|
411
|
+
}
|
|
412
|
+
/** Test hook - read-only snapshot of recorded failures. */
|
|
413
|
+
getRecordedFailures() {
|
|
414
|
+
return this.failures.slice();
|
|
415
|
+
}
|
|
416
|
+
/** Test hook - number of screenshots still waiting for a FailedEvent. */
|
|
417
|
+
getPendingScreenshotCount() {
|
|
418
|
+
return this.pendingScreenshots.size;
|
|
419
|
+
}
|
|
420
|
+
// ─────────────────────────────────────────────────────────────
|
|
421
|
+
// Private helpers
|
|
422
|
+
// ─────────────────────────────────────────────────────────────
|
|
423
|
+
pendingKey(sourceFile, testTitle) {
|
|
424
|
+
return `${this.normalisePath(sourceFile)}::${testTitle}`;
|
|
425
|
+
}
|
|
426
|
+
normalisePath(p) {
|
|
427
|
+
if (!p)
|
|
428
|
+
return "";
|
|
429
|
+
try {
|
|
430
|
+
const abs = path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
|
431
|
+
const rel = path.relative(process.cwd(), abs);
|
|
432
|
+
return (rel || abs).split("\\").join("/");
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return p.split("\\").join("/");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Convert the first PNG attachment on a TestResult into a Base64
|
|
440
|
+
* string immediately. Prefers an in-memory `body` when Playwright
|
|
441
|
+
* already inlined the bytes (faster, no IO); falls back to reading
|
|
442
|
+
* the attachment `path` synchronously BEFORE Playwright has a chance
|
|
443
|
+
* to clean up its temp folder.
|
|
444
|
+
*/
|
|
445
|
+
extractScreenshotBase64(result) {
|
|
446
|
+
const attachments = result.attachments ?? [];
|
|
447
|
+
for (const att of attachments) {
|
|
448
|
+
const contentType = (att.contentType ?? "").toLowerCase();
|
|
449
|
+
const looksLikePng = contentType.includes("png") ||
|
|
450
|
+
(att.name ?? "").toLowerCase().includes("screenshot") ||
|
|
451
|
+
(att.path ?? "").toLowerCase().endsWith(".png");
|
|
452
|
+
if (!looksLikePng)
|
|
453
|
+
continue;
|
|
454
|
+
// Playwright >= 1.46 sometimes inlines the body for small images.
|
|
455
|
+
const inline = att.body;
|
|
456
|
+
if (inline && inline.length > 0) {
|
|
457
|
+
return Buffer.isBuffer(inline)
|
|
458
|
+
? inline.toString("base64")
|
|
459
|
+
: Buffer.from(inline).toString("base64");
|
|
460
|
+
}
|
|
461
|
+
if (att.path) {
|
|
462
|
+
try {
|
|
463
|
+
const bytes = fs.readFileSync(att.path);
|
|
464
|
+
return bytes.toString("base64");
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// Temp file already gone - nothing we can do.
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
exports.SelaReporter = SelaReporter;
|
|
475
|
+
/**
|
|
476
|
+
* Mirror of the CI detection used by `SelaEngine.commitUpdates()`.
|
|
477
|
+
* Accepts the standard `CI=1`/`CI=true` convention; everything else
|
|
478
|
+
* (empty string, undefined, `0`, `false`) is Local DX mode.
|
|
479
|
+
*/
|
|
480
|
+
function defaultIsCi() {
|
|
481
|
+
const raw = process.env.CI;
|
|
482
|
+
if (raw === undefined || raw === null)
|
|
483
|
+
return false;
|
|
484
|
+
const v = String(raw).trim().toLowerCase();
|
|
485
|
+
return v !== "" && v !== "0" && v !== "false";
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Default HTML report locator. Returns the absolute path when
|
|
489
|
+
* `<cwd>/sela-report.html` exists on disk after `commitUpdates()` has
|
|
490
|
+
* had its chance to flush, otherwise null so the printer can skip the
|
|
491
|
+
* "Insights report:" footer entirely.
|
|
492
|
+
*/
|
|
493
|
+
function defaultResolveReportPath(cwd) {
|
|
494
|
+
const candidate = path.join(cwd, "sela-report.html");
|
|
495
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Default shard fan-in (SELA-5). Discovers this run's per-worker shards under
|
|
499
|
+
* `<cwd>/.sela/report-shards/` and merges them into the final
|
|
500
|
+
* sela-report.{html,json} + rolling history via ReportMergeService, then clears
|
|
501
|
+
* the shard dir. A no-op when no shards exist (single-process / no-heal runs),
|
|
502
|
+
* so it is safe to call unconditionally on every local run.
|
|
503
|
+
*/
|
|
504
|
+
function defaultMergeShards(cwd) {
|
|
505
|
+
const shardFiles = ReportMergeService_js_1.ReportMergeService.discoverShardFiles((0, HealReportService_js_1.reportShardDir)(cwd), "sela-shard-*.json");
|
|
506
|
+
if (shardFiles.length === 0)
|
|
507
|
+
return;
|
|
508
|
+
try {
|
|
509
|
+
ReportMergeService_js_1.ReportMergeService.merge({ shardFiles, targetDir: cwd });
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
(0, HealReportService_js_1.clearReportShards)(cwd);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Default export so Playwright can resolve `reporter: [['sela-core/reporter']]`
|
|
516
|
+
// without needing the consumer to spell out the named import.
|
|
517
|
+
exports.default = SelaReporter;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { SelectorSegment } from "./LLMService";
|
|
2
|
+
import { AdvisoryBuffer } from "./HealingAdvisory";
|
|
3
|
+
import type { SmartChainSegment } from "./LLMService";
|
|
4
|
+
import { TraceOutcome } from "./TraceBackEngine";
|
|
5
|
+
import { Decision } from "./DecisionEngine";
|
|
6
|
+
import { FileBatch, ApplyOutcome, ApplyOptions } from "./MutationApplier";
|
|
7
|
+
export interface ASTUpdateResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
reason: string;
|
|
10
|
+
strategy: string;
|
|
11
|
+
lineUpdated?: number;
|
|
12
|
+
cascadeFixed?: number;
|
|
13
|
+
/** The normalized locator string that was written to disk. Single source of truth for runtime. */
|
|
14
|
+
healedLocatorString?: string;
|
|
15
|
+
/** Populated by semantic dispatch: where the definition was mutated. */
|
|
16
|
+
definitionSite?: {
|
|
17
|
+
file: string;
|
|
18
|
+
line: number;
|
|
19
|
+
};
|
|
20
|
+
/** Number of test functions referencing the mutated constant (blast radius). */
|
|
21
|
+
blastRadius?: number;
|
|
22
|
+
}
|
|
23
|
+
interface CallerContext {
|
|
24
|
+
filePath: string;
|
|
25
|
+
line: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ComposeHealInput {
|
|
28
|
+
filePath: string;
|
|
29
|
+
/** 1-based line of the failing call-site. */
|
|
30
|
+
line: number;
|
|
31
|
+
/** 0-based column; when omitted the first non-whitespace char of the line is used. */
|
|
32
|
+
column?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Healed locator EXPRESSION text, e.g. `page.locator(".btn-v2")`. When
|
|
35
|
+
* omitted, it is BUILT from `newSelector` + the traced target's root
|
|
36
|
+
* receiver (internal selectors → idiomatic method form).
|
|
37
|
+
*/
|
|
38
|
+
newLocatorExpr?: string;
|
|
39
|
+
/** Raw healed selector string (may be an `internal:` engine selector). */
|
|
40
|
+
newSelector?: string;
|
|
41
|
+
/** Runtime: only the failing test needs the new selector. */
|
|
42
|
+
contextSpecific?: boolean;
|
|
43
|
+
/** Runtime: selector rot is global across all consumers. */
|
|
44
|
+
globalCorrect?: boolean;
|
|
45
|
+
/** When true, write the result via MutationApplier (with the gate). */
|
|
46
|
+
apply?: boolean;
|
|
47
|
+
applyOptions?: ApplyOptions;
|
|
48
|
+
}
|
|
49
|
+
export interface ComposeHealResult {
|
|
50
|
+
kind: "decision" | "abort";
|
|
51
|
+
trace: TraceOutcome;
|
|
52
|
+
decision?: Decision;
|
|
53
|
+
batch?: FileBatch;
|
|
54
|
+
outcome?: ApplyOutcome;
|
|
55
|
+
/** 1-based line of the mutated target node (for report/diff). */
|
|
56
|
+
writtenLine?: number;
|
|
57
|
+
}
|
|
58
|
+
export declare class ASTSourceUpdater {
|
|
59
|
+
private project;
|
|
60
|
+
readonly advisoryBuffer: AdvisoryBuffer;
|
|
61
|
+
flushAdvisories(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Trace the failing call-site back to its source declaration, decide the
|
|
64
|
+
* mutation strategy (in-place / fork / prompt), and — when `apply` is set —
|
|
65
|
+
* write it to disk through the type-safety gate.
|
|
66
|
+
*/
|
|
67
|
+
static composeHeal(input: ComposeHealInput): ComposeHealResult;
|
|
68
|
+
/** Drain-loop entry: apply pre-built batches through the write gate. */
|
|
69
|
+
static applyMutationBatches(batches: FileBatch[], opts?: ApplyOptions): ApplyOutcome[];
|
|
70
|
+
/** Convert a DecisionEngine result into an anchored MutationApplier batch. */
|
|
71
|
+
static decisionToBatch(filePath: string, decision: Decision): FileBatch;
|
|
72
|
+
/**
|
|
73
|
+
* Centralised write hook for every ts-morph `saveSync` call in this class.
|
|
74
|
+
* When `SELA_DRY_RUN` is active the AST mutation stays in memory but the
|
|
75
|
+
* on-disk file is left untouched - the heal pipeline can still compute a
|
|
76
|
+
* candidate diff via `sourceFile.getFullText()` and the report layer
|
|
77
|
+
* tags the event with `dryRun: true`.
|
|
78
|
+
*/
|
|
79
|
+
private _persistChanges;
|
|
80
|
+
scanFailFastDirective(caller: CallerContext): {
|
|
81
|
+
failFast: boolean;
|
|
82
|
+
matchedComment?: string;
|
|
83
|
+
matchedLine?: number;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Convenience helper - runs `scanFailFastDirective` and throws the
|
|
87
|
+
* canonical SelaError when the directive is present. Used by SelaEngine
|
|
88
|
+
* as the very first gate inside `heal()`.
|
|
89
|
+
*/
|
|
90
|
+
enforceFailFastDirective(caller: CallerContext): void;
|
|
91
|
+
constructor();
|
|
92
|
+
private readonly WRAPPER_IDENTIFIERS;
|
|
93
|
+
private readonly STRUCTURAL_ROLES;
|
|
94
|
+
private readonly INTERACTIVE_ROLES;
|
|
95
|
+
update(caller: CallerContext, oldSelector: string, newSelector: string, aiSegments?: SelectorSegment[], fullSelectorContext?: string, contentChange?: {
|
|
96
|
+
oldText: string;
|
|
97
|
+
newText: string;
|
|
98
|
+
}, smartChainSegments?: SmartChainSegment[], originalChainHint?: SmartChainSegment[]): ASTUpdateResult;
|
|
99
|
+
private strategySmartChain;
|
|
100
|
+
private attemptPartialChainHeal;
|
|
101
|
+
private strategyChainCollapse;
|
|
102
|
+
private runStrategyG;
|
|
103
|
+
private strategyFrameUpstream;
|
|
104
|
+
private strategyDeepSymbolResolution;
|
|
105
|
+
private strategyInlineSemanticFix;
|
|
106
|
+
private strategyTemplateLiteral;
|
|
107
|
+
private strategySemanticFragmentScan;
|
|
108
|
+
private findMatchingChainTail;
|
|
109
|
+
private replaceChainTailOnly;
|
|
110
|
+
private applySemanticFix;
|
|
111
|
+
/**
|
|
112
|
+
* Guard: returns false when replacing oldNode with proposedNewText would
|
|
113
|
+
* substitute a hardcoded string literal for an Identifier or
|
|
114
|
+
* PropertyAccessExpression - which is the definition-site mutation bug.
|
|
115
|
+
* All other replacements are allowed.
|
|
116
|
+
*/
|
|
117
|
+
private static isSafeReplacement;
|
|
118
|
+
private replaceCallArgument;
|
|
119
|
+
private semanticToInternal;
|
|
120
|
+
private performCascadeHeal;
|
|
121
|
+
private fixPropertyAccessDefinition;
|
|
122
|
+
private isBlockedByStructuralWall;
|
|
123
|
+
private identifySuspectIdentifier;
|
|
124
|
+
private getEnclosingStatement;
|
|
125
|
+
private findNodeAtLine;
|
|
126
|
+
private findCallExpressionsInRange;
|
|
127
|
+
private replaceFrameLocatorArg;
|
|
128
|
+
private replaceStringLiteralInRange;
|
|
129
|
+
private updateObjectProperty;
|
|
130
|
+
private lineToOffset;
|
|
131
|
+
resolveFilePath(raw: string): string | null;
|
|
132
|
+
private semanticDispatch;
|
|
133
|
+
private applyDefinitionSiteHeal;
|
|
134
|
+
private handleIdentifierArg;
|
|
135
|
+
private handlePropertyAccessArg;
|
|
136
|
+
private handleTemplateLiteralArg;
|
|
137
|
+
private traceSpanExpression;
|
|
138
|
+
private handleCallExpressionArg;
|
|
139
|
+
private validateFileSyntax;
|
|
140
|
+
private broadcastAndReturn;
|
|
141
|
+
}
|
|
142
|
+
export {};
|
|
143
|
+
//# sourceMappingURL=ASTSourceUpdater.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ASTSourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/ASTSourceUpdater.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAQ/C,OAAO,EAAE,cAAc,EAAgB,MAAM,mBAAmB,CAAC;AAOjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAmB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAkB,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAEL,SAAS,EAET,YAAY,EACZ,YAAY,EACb,MAAM,mBAAmB,CAAC;AAY3B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA07DD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA4BD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAU;IACzB,QAAQ,CAAC,cAAc,iBAAwB;IAE/C,eAAe,IAAI,IAAI;IAYvB;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB;IAoE9D,wEAAwE;IACxE,MAAM,CAAC,oBAAoB,CACzB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB,8EAA8E;IAC9E,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS;IA8BvE;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAqBvB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG;QAC5C,QAAQ,EAAE,OAAO,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;IAsDD;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;;IAgCrD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAcjC;IAEH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CA8B9B;IAEH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAuB/B;IAMH,MAAM,CACJ,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,eAAe,EAAE,EAC9B,mBAAmB,CAAC,EAAE,MAAM,EAC5B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EACxC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,GACtC,eAAe;IA8QlB,OAAO,CAAC,kBAAkB;IAmM1B,OAAO,CAAC,uBAAuB;IA0F/B,OAAO,CAAC,qBAAqB;IAuF7B,OAAO,CAAC,YAAY;IAkDpB,OAAO,CAAC,qBAAqB;IA0G7B,OAAO,CAAC,4BAA4B;IAgRpC,OAAO,CAAC,yBAAyB;IAsGjC,OAAO,CAAC,uBAAuB;IA4E/B,OAAO,CAAC,4BAA4B;IA4EpC,OAAO,CAAC,qBAAqB;IA6B7B,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,gBAAgB;IAwBxB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAahC,OAAO,CAAC,mBAAmB;IAuC3B,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,2BAA2B;IAsCnC,OAAO,CAAC,yBAAyB;IAqCjC,OAAO,CAAC,yBAAyB;IA+CjC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,0BAA0B;IAmBlC,OAAO,CAAC,sBAAsB;IA2B9B,OAAO,CAAC,2BAA2B;IAoCnC,OAAO,CAAC,oBAAoB;IA2B5B,OAAO,CAAC,YAAY;IAUpB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkB3C,OAAO,CAAC,gBAAgB;IAmGxB,OAAO,CAAC,uBAAuB;IAuE/B,OAAO,CAAC,mBAAmB;IAiF3B,OAAO,CAAC,uBAAuB;IAiF/B,OAAO,CAAC,wBAAwB;IAyFhC,OAAO,CAAC,mBAAmB;IAyD3B,OAAO,CAAC,uBAAuB;IA0F/B,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,kBAAkB;CAgB3B"}
|