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,360 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/services/MutationApplier.ts
|
|
3
|
+
//
|
|
4
|
+
// Ticket #2 — Stage 4: Write-Discipline & the Drain Loop.
|
|
5
|
+
//
|
|
6
|
+
// Applies MutationPlans to disk under three hard contracts:
|
|
7
|
+
//
|
|
8
|
+
// 1. ZERO LAYOUT NOISE (§4.1) — surgical string splice over the EXACT node
|
|
9
|
+
// span [getStart(), getEnd()). getStart() excludes leading trivia, so
|
|
10
|
+
// comments / indentation / Prettier layout outside the span are byte-
|
|
11
|
+
// identical. We never call sourceFile.formatText() / ts.Printer.
|
|
12
|
+
//
|
|
13
|
+
// 2. TYPE-SAFETY GATE + ROLLBACK (§4.3) — after splicing in memory we
|
|
14
|
+
// re-parse and baseline-diff the diagnostics. A mutation that introduces
|
|
15
|
+
// a NEW real error (syntax break, non-Locator return) is rolled back and
|
|
16
|
+
// the file is left byte-for-byte untouched. A cheap Locator-shape pre-
|
|
17
|
+
// check rejects obviously wrong return types before the splice.
|
|
18
|
+
//
|
|
19
|
+
// 3. BATCH APPLICATION (§3.3) — mutations are grouped by file, anchors are
|
|
20
|
+
// RE-RESOLVED against the fresh parse (never trusting flag-time offsets),
|
|
21
|
+
// edits applied highest-offset-first, then the file is written atomically
|
|
22
|
+
// (temp + rename).
|
|
23
|
+
//
|
|
24
|
+
// Stage 1 (resolveAnchor) provides line-shift-immune relocation; Stage-1
|
|
25
|
+
// applyEdits provides the reverse-offset splice + overlap guard.
|
|
26
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
+
}
|
|
32
|
+
Object.defineProperty(o, k2, desc);
|
|
33
|
+
}) : (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
o[k2] = m[k];
|
|
36
|
+
}));
|
|
37
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
+
}) : function(o, v) {
|
|
40
|
+
o["default"] = v;
|
|
41
|
+
});
|
|
42
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
43
|
+
var ownKeys = function(o) {
|
|
44
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
45
|
+
var ar = [];
|
|
46
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
47
|
+
return ar;
|
|
48
|
+
};
|
|
49
|
+
return ownKeys(o);
|
|
50
|
+
};
|
|
51
|
+
return function (mod) {
|
|
52
|
+
if (mod && mod.__esModule) return mod;
|
|
53
|
+
var result = {};
|
|
54
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
55
|
+
__setModuleDefault(result, mod);
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.MutationApplier = void 0;
|
|
61
|
+
exports.isLocatorReturningExpr = isLocatorReturningExpr;
|
|
62
|
+
const fs = __importStar(require("fs"));
|
|
63
|
+
const ts_morph_1 = require("ts-morph");
|
|
64
|
+
const AnchorResolver_1 = require("./AnchorResolver");
|
|
65
|
+
const DecisionEngine_1 = require("./DecisionEngine");
|
|
66
|
+
const TraceBackEngine_1 = require("./TraceBackEngine");
|
|
67
|
+
const SelectorSanitizer_1 = require("./SelectorSanitizer");
|
|
68
|
+
const PathScopeGuard_1 = require("../config/PathScopeGuard");
|
|
69
|
+
const atomicWrite_1 = require("../utils/atomicWrite");
|
|
70
|
+
const FileLock_1 = require("../utils/FileLock");
|
|
71
|
+
// Diagnostic codes that are pure "missing types" noise in an in-memory parse
|
|
72
|
+
// without node_modules — identical allowlist to ASTSourceUpdater's validator.
|
|
73
|
+
// Syntax/binder errors (codes 1000–1999) are NEVER allowlisted.
|
|
74
|
+
const NOISE_CODES = new Set([
|
|
75
|
+
2304, 2339, 2345, 2554, 2571, 2580, 2503, 7006, 7016, 7031, 18004,
|
|
76
|
+
]);
|
|
77
|
+
const LOCATOR_RETURNING_CATEGORIES = new Set(["selector", "frame", "modifier"]);
|
|
78
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
79
|
+
// MutationApplier
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
81
|
+
class MutationApplier {
|
|
82
|
+
/** Apply many batches; each file is processed independently. */
|
|
83
|
+
static applyBatches(batches, opts = {}) {
|
|
84
|
+
return batches.map((b) => MutationApplier.applyFileBatch(b, opts));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Apply a file batch under a per-file cross-process lock (SELA-5).
|
|
88
|
+
*
|
|
89
|
+
* The lock makes the read-modify-write of a single source file atomic across
|
|
90
|
+
* concurrent Playwright workers — the real lost-update risk is two specs that
|
|
91
|
+
* heal a SHARED page-object simultaneously (each reads the same baseline and
|
|
92
|
+
* the second write drops the first's fix). The lock dir is keyed by file
|
|
93
|
+
* path, so heals on DIFFERENT files never serialise — worker scalability is
|
|
94
|
+
* preserved. Read-only / dry-run runs skip the lock entirely (no disk RMW).
|
|
95
|
+
*
|
|
96
|
+
* A stale lock (crashed holder) is reclaimed after 30s so a worker crash can
|
|
97
|
+
* never permanently wedge CI.
|
|
98
|
+
*/
|
|
99
|
+
static applyFileBatch(batch, opts = {}) {
|
|
100
|
+
const write = opts.write ?? true;
|
|
101
|
+
const respectDryRun = opts.respectDryRun ?? true;
|
|
102
|
+
const dryRun = respectDryRun && process.env.SELA_DRY_RUN === "1";
|
|
103
|
+
// Only the write path mutates disk; gate/dry-run runs need no lock.
|
|
104
|
+
if (!write || dryRun) {
|
|
105
|
+
return MutationApplier.applyFileBatchUnlocked(batch, opts);
|
|
106
|
+
}
|
|
107
|
+
const lock = new FileLock_1.FileLock((0, FileLock_1.lockDirFor)(batch.filePath), { staleMs: 30_000 });
|
|
108
|
+
const outcome = lock.withLockSync(() => MutationApplier.applyFileBatchUnlocked(batch, opts));
|
|
109
|
+
if (outcome === undefined) {
|
|
110
|
+
// Lost the per-file lock race past the busy-wait window — skip rather
|
|
111
|
+
// than risk a clobbering write. The heal is recorded as not-applied; the
|
|
112
|
+
// caller treats it like any other skipped mutation.
|
|
113
|
+
let finalContent = "";
|
|
114
|
+
try {
|
|
115
|
+
finalContent = fs.readFileSync(batch.filePath, "utf8");
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* unreadable — leave empty */
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
filePath: batch.filePath,
|
|
122
|
+
status: "skipped",
|
|
123
|
+
reason: "could not acquire per-file write lock (concurrent heal in progress)",
|
|
124
|
+
written: false,
|
|
125
|
+
finalContent,
|
|
126
|
+
skipped: [],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return outcome;
|
|
130
|
+
}
|
|
131
|
+
static applyFileBatchUnlocked(batch, opts = {}) {
|
|
132
|
+
const write = opts.write ?? true;
|
|
133
|
+
const respectDryRun = opts.respectDryRun ?? true;
|
|
134
|
+
const dryRun = respectDryRun && process.env.SELA_DRY_RUN === "1";
|
|
135
|
+
const { filePath } = batch;
|
|
136
|
+
const skipped = [];
|
|
137
|
+
let original;
|
|
138
|
+
try {
|
|
139
|
+
original = fs.readFileSync(filePath, "utf8");
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
return {
|
|
143
|
+
filePath,
|
|
144
|
+
status: "skipped",
|
|
145
|
+
reason: `cannot read file: ${e.message}`,
|
|
146
|
+
written: false,
|
|
147
|
+
finalContent: "",
|
|
148
|
+
skipped,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Fresh parse — the single source of truth for span resolution.
|
|
152
|
+
const project = newGateProject();
|
|
153
|
+
const sf = project.createSourceFile(filePath, original, { overwrite: true });
|
|
154
|
+
const baseline = realDiagnosticCounts(sf);
|
|
155
|
+
// ── Resolve each edit to a concrete span ─────────────────────────
|
|
156
|
+
const concrete = [];
|
|
157
|
+
for (const e of batch.edits) {
|
|
158
|
+
let replacement = e.replacement;
|
|
159
|
+
// ABSOLUTE GUARD: translate any internal engine selector to its public
|
|
160
|
+
// method form before writing; refuse if it cannot be translated. No
|
|
161
|
+
// `internal:role=…` string ever reaches the developer's source.
|
|
162
|
+
if (e.kind === "replace") {
|
|
163
|
+
const san = (0, SelectorSanitizer_1.sanitizeLocatorExpression)(replacement);
|
|
164
|
+
if (!san.safe) {
|
|
165
|
+
skipped.push({
|
|
166
|
+
reason: `internal-engine-selector: cannot translate "${truncate(replacement)}" to a public Playwright method`,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
replacement = san.text;
|
|
171
|
+
}
|
|
172
|
+
const requireLocator = e.requireLocator ?? e.kind === "replace";
|
|
173
|
+
if (requireLocator && !isLocatorReturningExpr(replacement)) {
|
|
174
|
+
skipped.push({
|
|
175
|
+
reason: `type-incompat: replacement is not a Locator expression ("${truncate(replacement)}")`,
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (e.kind === "replace" && e.anchor) {
|
|
180
|
+
const r = (0, AnchorResolver_1.resolveAnchor)(sf, e.anchor);
|
|
181
|
+
if (r.status !== "resolved") {
|
|
182
|
+
skipped.push({
|
|
183
|
+
reason: `anchor ${r.status}: ${"reason" in r ? r.reason : ""}`,
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
concrete.push({
|
|
188
|
+
kind: "replace",
|
|
189
|
+
span: { start: r.node.getStart(), end: r.node.getEnd() },
|
|
190
|
+
replacement,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else if (e.span) {
|
|
194
|
+
concrete.push({
|
|
195
|
+
kind: e.kind,
|
|
196
|
+
span: e.span,
|
|
197
|
+
replacement,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
skipped.push({ reason: "edit has neither anchor nor span" });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (concrete.length === 0) {
|
|
205
|
+
return {
|
|
206
|
+
filePath,
|
|
207
|
+
status: "no-op",
|
|
208
|
+
reason: "no applicable edits after resolution",
|
|
209
|
+
written: false,
|
|
210
|
+
finalContent: original,
|
|
211
|
+
skipped,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// ── Splice (reverse-offset; overlap-guarded) ─────────────────────
|
|
215
|
+
let newContent;
|
|
216
|
+
try {
|
|
217
|
+
newContent = (0, DecisionEngine_1.applyEdits)(original, concrete);
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
return {
|
|
221
|
+
filePath,
|
|
222
|
+
status: "rolled-back",
|
|
223
|
+
reason: `splice failed: ${e.message}`,
|
|
224
|
+
written: false,
|
|
225
|
+
finalContent: original,
|
|
226
|
+
skipped,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (newContent === original) {
|
|
230
|
+
return {
|
|
231
|
+
filePath,
|
|
232
|
+
status: "no-op",
|
|
233
|
+
reason: "edits produced identical content",
|
|
234
|
+
written: false,
|
|
235
|
+
finalContent: original,
|
|
236
|
+
skipped,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// ── Type-safety gate: baseline-diff diagnostics ──────────────────
|
|
240
|
+
const afterSf = project.createSourceFile(filePath, newContent, {
|
|
241
|
+
overwrite: true,
|
|
242
|
+
});
|
|
243
|
+
const after = realDiagnosticCounts(afterSf);
|
|
244
|
+
const regression = diagnosticRegression(baseline, after);
|
|
245
|
+
if (regression) {
|
|
246
|
+
return {
|
|
247
|
+
filePath,
|
|
248
|
+
status: "rolled-back",
|
|
249
|
+
reason: `type-safety gate: mutation introduced a new error (${regression}) — file left untouched`,
|
|
250
|
+
written: false,
|
|
251
|
+
finalContent: original,
|
|
252
|
+
skipped,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// ── Atomic write ─────────────────────────────────────────────────
|
|
256
|
+
if (write && !dryRun) {
|
|
257
|
+
// Safe path scoping (T1): block writes outside allowedPaths.
|
|
258
|
+
if (!PathScopeGuard_1.PathScopeGuard.enforce(filePath)) {
|
|
259
|
+
return {
|
|
260
|
+
filePath,
|
|
261
|
+
status: "skipped",
|
|
262
|
+
reason: "blocked by allowedPaths (outside safe path scope)",
|
|
263
|
+
written: false,
|
|
264
|
+
finalContent: original,
|
|
265
|
+
skipped,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
(0, atomicWrite_1.atomicWriteFile)(filePath, newContent);
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
return {
|
|
273
|
+
filePath,
|
|
274
|
+
status: "rolled-back",
|
|
275
|
+
reason: `atomic write failed: ${e.message}`,
|
|
276
|
+
written: false,
|
|
277
|
+
finalContent: original,
|
|
278
|
+
skipped,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
filePath,
|
|
284
|
+
status: "applied",
|
|
285
|
+
reason: `${concrete.length} edit(s) applied${dryRun ? " (dry-run, not written)" : ""}`,
|
|
286
|
+
written: write && !dryRun,
|
|
287
|
+
finalContent: newContent,
|
|
288
|
+
skipped,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
exports.MutationApplier = MutationApplier;
|
|
293
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
294
|
+
// TYPE-SAFETY HELPERS
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
296
|
+
function newGateProject() {
|
|
297
|
+
return new ts_morph_1.Project({
|
|
298
|
+
useInMemoryFileSystem: true,
|
|
299
|
+
compilerOptions: {
|
|
300
|
+
allowJs: true,
|
|
301
|
+
noEmit: true,
|
|
302
|
+
skipLibCheck: true,
|
|
303
|
+
noResolve: true,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/** Map of real (non-noise) diagnostic code → count. */
|
|
308
|
+
function realDiagnosticCounts(sf) {
|
|
309
|
+
const counts = new Map();
|
|
310
|
+
for (const d of sf.getPreEmitDiagnostics()) {
|
|
311
|
+
if (d.getCategory() !== 1 /* Error */)
|
|
312
|
+
continue;
|
|
313
|
+
const code = d.getCode();
|
|
314
|
+
if (NOISE_CODES.has(code))
|
|
315
|
+
continue;
|
|
316
|
+
counts.set(code, (counts.get(code) ?? 0) + 1);
|
|
317
|
+
}
|
|
318
|
+
return counts;
|
|
319
|
+
}
|
|
320
|
+
/** Returns a description of the first NEW/elevated error, or null. */
|
|
321
|
+
function diagnosticRegression(baseline, after) {
|
|
322
|
+
for (const [code, n] of after) {
|
|
323
|
+
if (n > (baseline.get(code) ?? 0)) {
|
|
324
|
+
return `TS${code}`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Cheap return-type pre-check. The replacement must be an expression whose
|
|
331
|
+
* OUTERMOST call is a Locator-returning Playwright method (locator / getBy* /
|
|
332
|
+
* frameLocator / nth / first / last / filter / and / or). Rejects actions
|
|
333
|
+
* (.click()), string queries (.title(), .textContent()), bare literals, etc.
|
|
334
|
+
*/
|
|
335
|
+
function isLocatorReturningExpr(text) {
|
|
336
|
+
const project = newGateProject();
|
|
337
|
+
let sf;
|
|
338
|
+
try {
|
|
339
|
+
sf = project.createSourceFile("_probe.ts", `const __x = ${text};`, {
|
|
340
|
+
overwrite: true,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const decl = sf.getVariableDeclaration("__x");
|
|
347
|
+
const init = decl?.getInitializer();
|
|
348
|
+
if (!init)
|
|
349
|
+
return false;
|
|
350
|
+
// Outermost expression must be a call whose method is Locator-returning.
|
|
351
|
+
if (!ts_morph_1.Node.isCallExpression(init))
|
|
352
|
+
return false;
|
|
353
|
+
const callee = init.getExpression();
|
|
354
|
+
if (!ts_morph_1.Node.isPropertyAccessExpression(callee))
|
|
355
|
+
return false;
|
|
356
|
+
return LOCATOR_RETURNING_CATEGORIES.has((0, TraceBackEngine_1.categorizeMethod)(callee.getName()));
|
|
357
|
+
}
|
|
358
|
+
function truncate(s, n = 48) {
|
|
359
|
+
return s.length > n ? `${s.slice(0, n)}…` : s;
|
|
360
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ResolvedPRAutomation, PRStrategy, BugAction } from "../config/SelaConfig";
|
|
2
|
+
import type { HealedEvent, ProtectedEvent } from "./HealReportService";
|
|
3
|
+
import { type WorkspaceSnapshotService } from "./WorkspaceSnapshotService";
|
|
4
|
+
export interface BranchInfo {
|
|
5
|
+
branch: string | null;
|
|
6
|
+
isPR: boolean;
|
|
7
|
+
prNumber: number | null;
|
|
8
|
+
source: "env" | "git" | "none";
|
|
9
|
+
}
|
|
10
|
+
export interface StrategyDecision {
|
|
11
|
+
effective: PRStrategy;
|
|
12
|
+
configured: PRStrategy;
|
|
13
|
+
downgradeReason: "PROTECTED_BRANCH" | "LOW_CONFIDENCE" | "LOW_AUDITOR" | null;
|
|
14
|
+
/**
|
|
15
|
+
* Lowest AI confidence observed across this session's heals, or `undefined`
|
|
16
|
+
* when no heal carried a numeric confidence (LLM omitted the field). Render
|
|
17
|
+
* via `fmtPct` so the absence reads as "n/a" instead of 0%.
|
|
18
|
+
*/
|
|
19
|
+
minAiConfidence: number | undefined;
|
|
20
|
+
/** Same semantics as `minAiConfidence`, for the auditor's adjusted score. */
|
|
21
|
+
minAuditorConfidence: number | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface ExecuteContext {
|
|
24
|
+
cwd: string;
|
|
25
|
+
reportHtmlPath: string | null;
|
|
26
|
+
}
|
|
27
|
+
export declare function detectBranch(cwd?: string): BranchInfo;
|
|
28
|
+
export declare function decideEffectiveStrategy(cfg: ResolvedPRAutomation, branch: string | null, heals: HealedEvent[]): StrategyDecision;
|
|
29
|
+
export declare function fmtPct(v: unknown): string;
|
|
30
|
+
export interface ExecutionResult {
|
|
31
|
+
effective: PRStrategy;
|
|
32
|
+
prUrl: string | null;
|
|
33
|
+
branchPushed: string | null;
|
|
34
|
+
errors: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clean-Room execution lifecycle.
|
|
38
|
+
*
|
|
39
|
+
* The legacy flow committed the developer's whole dirty working tree onto
|
|
40
|
+
* the PR branch - polluting reviewers with `test.only`, commented debug
|
|
41
|
+
* blocks, etc. This refactor isolates Sela's specific AST changes onto an
|
|
42
|
+
* ephemeral branch, leaves the developer's local workspace byte-identical
|
|
43
|
+
* to its pre-Sela state, and is wrapped in a try/finally that restores
|
|
44
|
+
* the workspace even on mid-flight crashes.
|
|
45
|
+
*/
|
|
46
|
+
export declare function execute(cfg: ResolvedPRAutomation, decision: StrategyDecision, heals: HealedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext,
|
|
47
|
+
/** Injectable for tests - defaults to the module singleton. */
|
|
48
|
+
workspace?: WorkspaceSnapshotService): Promise<ExecutionResult>;
|
|
49
|
+
export declare function handleBugDetected(action: BugAction, events: ProtectedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext): Promise<void>;
|
|
50
|
+
//# sourceMappingURL=PRAutomationService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PRAutomationService.d.ts","sourceRoot":"","sources":["../../src/services/PRAutomationService.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,SAAS,EACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,4BAA4B,CAAC;AAQpC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,UAAU,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,aAAa,GAAG,IAAI,CAAC;IAC9E;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAiFD,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CA+EpE;AAMD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,WAAW,EAAE,GACnB,gBAAgB,CAgElB;AAMD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAEzC;AA6FD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,oBAAoB,EACzB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc;AACnB,+DAA+D;AAC/D,SAAS,GAAE,wBAAkD,GAC5D,OAAO,CAAC,eAAe,CAAC,CAkG1B;AAsTD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CAgGf"}
|