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,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.LLMService = void 0;
|
|
37
|
+
const dotenv = __importStar(require("dotenv"));
|
|
38
|
+
const SelaError_1 = require("../errors/SelaError");
|
|
39
|
+
const SanitizingAnthropic_1 = require("./SanitizingAnthropic");
|
|
40
|
+
const logger_1 = require("../utils/logger");
|
|
41
|
+
dotenv.config();
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────
|
|
43
|
+
// SYSTEM PROMPT
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────
|
|
45
|
+
const SYSTEM_PROMPT_TEXT = `### ROLE
|
|
46
|
+
You are an expert Playwright Automation Engineer specializing in self-healing test selectors (Project Sela).
|
|
47
|
+
You deeply understand Playwright's locator API and always prefer semantic, resilient selectors
|
|
48
|
+
over brittle CSS or XPath expressions.
|
|
49
|
+
|
|
50
|
+
### STRICT RULES
|
|
51
|
+
1. **NO PSEUDO-CLASSES**: NEVER use ':first-of-type', ':nth-child', etc., unless it is the ONLY way.
|
|
52
|
+
2. **PRIORITIZE ATTRIBUTES**: Search DOM for stable attributes: id, data-testid, aria-label, name, role.
|
|
53
|
+
3. **FRAME ACCURACY**: If a frame's ID changed, use the NEW ID from the DOM.
|
|
54
|
+
4. **NO FX-ID**: Never return 'fx-id'.
|
|
55
|
+
5. **VALID METHODS ONLY**: Only use real Playwright Locator methods.
|
|
56
|
+
6. **RUNTIME SYNC (CRITICAL)**: The flat selector in "segments" MUST match the specificity of "chainSegments".
|
|
57
|
+
If your chain uses a 'name' or 'text' filter, you MUST include it in the flat string (e.g., using " >> internal:has-text="..."" or ":has-text('...')").
|
|
58
|
+
7. **DATA-* ATTRIBUTE PRESERVATION (CRITICAL)**: If the original selector used a data-* attribute (e.g., data-role, data-testid, data-id), you MUST preserve that attribute in your response.
|
|
59
|
+
NEVER simplify "input[data-role='old-value']" to just "input".
|
|
60
|
+
Instead update the attribute value: "input[data-role='new-value']".
|
|
61
|
+
Even if the DOM context appears incomplete, when the DNA shows an attribute value changed, include the updated attribute value in your response.
|
|
62
|
+
|
|
63
|
+
### VALID PLAYWRIGHT CHAIN METHODS
|
|
64
|
+
Locator producers: locator, frameLocator, getByRole, getByLabel, getByText,
|
|
65
|
+
getByPlaceholder, getByTestId, getByAltText, getByTitle
|
|
66
|
+
Narrowing: filter, first, last, nth
|
|
67
|
+
|
|
68
|
+
### SMART CHAIN RESPONSE FORMAT
|
|
69
|
+
You MUST return a "chainSegments" array for the permanent code fix AND a matching "segments" array for the runtime proxy.
|
|
70
|
+
|
|
71
|
+
Build the chain in this priority order:
|
|
72
|
+
PRIORITY 1 - getByTestId
|
|
73
|
+
PRIORITY 2 - getByRole (ALWAYS include "name" if it exists in the DOM to ensure uniqueness)
|
|
74
|
+
PRIORITY 3 - getByLabel
|
|
75
|
+
PRIORITY 4 - getByText (use exact: true if possible)
|
|
76
|
+
NARROWING - Use filter({ hasText: "..." }) to isolate an element within a repeated container.
|
|
77
|
+
|
|
78
|
+
### PROXY SELECTOR CONSTRUCTION (segments field)
|
|
79
|
+
The "segments[0].selector" string is used for an immediate "live" retry.
|
|
80
|
+
- AVOID generic selectors like "internal:role=button".
|
|
81
|
+
- PREFER specific selectors: ".user-item >> internal:has-text=\"Bob\" >> internal:role=button[name=\"Edit\"i]"
|
|
82
|
+
- This prevents "Strict Mode" violations where Playwright finds multiple matches and times out.
|
|
83
|
+
|
|
84
|
+
### DEVELOPER INTENT PRESERVATION
|
|
85
|
+
Preserve the original code's structural pattern from "originalChainHint":
|
|
86
|
+
- If the original used a chain, return a chain.
|
|
87
|
+
- If the original was specific, stay specific.
|
|
88
|
+
|
|
89
|
+
### TEMPLATE LITERAL / FUNCTION SELECTOR PRESERVATION
|
|
90
|
+
When the failing selector originates from a Template Literal or a function returning a template,
|
|
91
|
+
preserve the attribute structure - update the value inside the attribute rather than removing it.
|
|
92
|
+
For chain selectors: if the original was "#section-B >> input[data-role='user_legacy']"
|
|
93
|
+
and the DNA shows data-role changed to "user_active", return "#section-B >> input[data-role='user_active']".
|
|
94
|
+
NEVER simplify to "#section-B >> input" - that loses the attribute and breaks automated source healing.
|
|
95
|
+
The healing system depends on the attribute value being present in your response to patch the template variable.
|
|
96
|
+
|
|
97
|
+
### TYPE SAFETY RULES
|
|
98
|
+
- A chain MUST end on a Locator (not FrameLocator).
|
|
99
|
+
- Never call frameLocator() on a Locator.
|
|
100
|
+
|
|
101
|
+
### CONTENT CHANGE DETECTION
|
|
102
|
+
If the element's text changed, you MUST include:
|
|
103
|
+
"contentChange": { "oldText": "<previous>", "newText": "<current>" }
|
|
104
|
+
|
|
105
|
+
### CONFIDENCE SCORE (MANDATORY)
|
|
106
|
+
You MUST include a numeric "confidence" field - an INTEGER in the range 0-100 -
|
|
107
|
+
representing how certain you are that the returned chain will resolve the
|
|
108
|
+
intended element on the next test run. Use this rubric:
|
|
109
|
+
- 90-100: stable id, data-testid, or unique role+name - zero ambiguity in the DOM.
|
|
110
|
+
- 70-89: strong semantic match, but some structural drift or competing candidates.
|
|
111
|
+
- 50-69: best-guess match - multiple plausible candidates required disambiguation.
|
|
112
|
+
- 1-49: speculative match - return this only when no better candidate exists.
|
|
113
|
+
NEVER omit this field. NEVER return it as a string, boolean, or null.
|
|
114
|
+
Reports rendered downstream will show "n/a" when this field is missing - which
|
|
115
|
+
is a clear failure signal to the developer reviewing the heal.
|
|
116
|
+
|
|
117
|
+
### MANDATORY OUTPUT FORMAT
|
|
118
|
+
Return ONLY valid JSON.
|
|
119
|
+
|
|
120
|
+
Example output - Identifying a specific button in a list row:
|
|
121
|
+
{
|
|
122
|
+
"status": "FIXED",
|
|
123
|
+
"confidence": 92,
|
|
124
|
+
"chainSegments": [
|
|
125
|
+
{ "type": "locator", "selector": ".user-item" },
|
|
126
|
+
{ "type": "filter", "hasText": "Bob Smith" },
|
|
127
|
+
{ "type": "getByRole", "role": "button", "name": "Modify Bob" }
|
|
128
|
+
],
|
|
129
|
+
"segments": [
|
|
130
|
+
{
|
|
131
|
+
"type": "element",
|
|
132
|
+
"selector": ".user-item >> internal:has-text=\"Bob Smith\"i >> internal:role=button[name=\"Modify Bob\"i]"
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"explanation": "Updated container to .user-item and targeted the button by its new semantic name 'Modify Bob' to ensure a unique match."
|
|
136
|
+
}`;
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────
|
|
138
|
+
// HELPERS
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────
|
|
140
|
+
/**
|
|
141
|
+
* Reverse-engineer a SmartChainSegment hint from a flat selector
|
|
142
|
+
* string. This gives the AI context about how the original code
|
|
143
|
+
* was structured so it can preserve developer intent.
|
|
144
|
+
*/
|
|
145
|
+
function inferOriginalChainHint(selector) {
|
|
146
|
+
const parts = selector.split(" >> ").map((s) => s.trim());
|
|
147
|
+
const segments = [];
|
|
148
|
+
for (const part of parts) {
|
|
149
|
+
// internal:role=button[name="..."]
|
|
150
|
+
const roleMatch = part.match(/^internal:role=([a-zA-Z]+)(?:\[name="([^"]+)"(?:i)?\])?/);
|
|
151
|
+
if (roleMatch) {
|
|
152
|
+
const seg = {
|
|
153
|
+
type: "getByRole",
|
|
154
|
+
role: roleMatch[1],
|
|
155
|
+
};
|
|
156
|
+
if (roleMatch[2])
|
|
157
|
+
seg.name = roleMatch[2];
|
|
158
|
+
segments.push(seg);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// internal:label=
|
|
162
|
+
const labelMatch = part.match(/^internal:label=["']?([^"'\]]+)["']?/);
|
|
163
|
+
if (labelMatch) {
|
|
164
|
+
segments.push({ type: "getByLabel", text: labelMatch[1].trim() });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
// internal:text=
|
|
168
|
+
const textMatch = part.match(/^internal:text=["']([^"']+)["'](i)?/);
|
|
169
|
+
if (textMatch) {
|
|
170
|
+
segments.push({
|
|
171
|
+
type: "getByText",
|
|
172
|
+
text: textMatch[1],
|
|
173
|
+
exact: !textMatch[2],
|
|
174
|
+
});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// internal:attr=[placeholder=...]
|
|
178
|
+
const placeholderMatch = part.match(/internal:attr=\[placeholder=["']([^"']+)["']\]/);
|
|
179
|
+
if (placeholderMatch) {
|
|
180
|
+
segments.push({ type: "getByPlaceholder", text: placeholderMatch[1] });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// [data-testid=...]
|
|
184
|
+
const testIdMatch = part.match(/\[data-testid=["']([^"']+)["']\]/);
|
|
185
|
+
if (testIdMatch) {
|
|
186
|
+
segments.push({ type: "getByTestId", testId: testIdMatch[1] });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
// iframe / frame selectors
|
|
190
|
+
if (part.includes("frame") ||
|
|
191
|
+
part.startsWith("iframe") ||
|
|
192
|
+
part.match(/^#[a-z-]*frame[a-z-]*/i)) {
|
|
193
|
+
segments.push({ type: "frameLocator", selector: part });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
// Fallback: CSS locator
|
|
197
|
+
segments.push({ type: "locator", selector: part });
|
|
198
|
+
}
|
|
199
|
+
return segments;
|
|
200
|
+
}
|
|
201
|
+
const DEFAULT_LLM_MODEL = "claude-sonnet-4-20250514";
|
|
202
|
+
class LLMService {
|
|
203
|
+
anthropic;
|
|
204
|
+
model;
|
|
205
|
+
constructor(opts = {}) {
|
|
206
|
+
this.model = opts.model ?? DEFAULT_LLM_MODEL;
|
|
207
|
+
if (opts.anthropic) {
|
|
208
|
+
this.anthropic = opts.anthropic;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
212
|
+
if (!apiKey) {
|
|
213
|
+
throw new SelaError_1.SelaError({
|
|
214
|
+
subsystem: "LLMService",
|
|
215
|
+
code: SelaError_1.LLM_SERVICE_CODES.NO_API_KEY,
|
|
216
|
+
reason: "ANTHROPIC_API_KEY not found - pass an `anthropic` client or `apiKey` to LLMService(), or set the env var",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// Route the default client through the PII/secrets sanitizer (T3) so all
|
|
220
|
+
// outbound DOM/DNA context is scrubbed before leaving the process.
|
|
221
|
+
this.anthropic = (0, SanitizingAnthropic_1.createSanitizingAnthropic)({ apiKey });
|
|
222
|
+
}
|
|
223
|
+
cleanJson(text) {
|
|
224
|
+
let cleaned = text
|
|
225
|
+
.replace(/```json/g, "")
|
|
226
|
+
.replace(/```/g, "")
|
|
227
|
+
.trim();
|
|
228
|
+
const firstBrace = cleaned.indexOf("{");
|
|
229
|
+
const lastBrace = cleaned.lastIndexOf("}");
|
|
230
|
+
if (firstBrace !== -1 && lastBrace !== -1) {
|
|
231
|
+
return cleaned.substring(firstBrace, lastBrace + 1);
|
|
232
|
+
}
|
|
233
|
+
return cleaned;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Attempts to infer contentChange when the AI did not return the field.
|
|
237
|
+
*
|
|
238
|
+
* Strategies (in order of confidence):
|
|
239
|
+
*
|
|
240
|
+
* 1. previousState.text vs has-text() in new selector
|
|
241
|
+
* 2. has-text() in old selector vs new selector
|
|
242
|
+
* 3. intentDescription text vs new selector
|
|
243
|
+
*/
|
|
244
|
+
inferContentChange(parsed, request) {
|
|
245
|
+
// Extract new text from either chainSegments or flat new_selector
|
|
246
|
+
let newText = null;
|
|
247
|
+
if (parsed.chainSegments) {
|
|
248
|
+
// Look for text in the last getByText / getByRole with name
|
|
249
|
+
for (const seg of [...parsed.chainSegments].reverse()) {
|
|
250
|
+
if ((seg.type === "getByText" ||
|
|
251
|
+
seg.type === "getByLabel" ||
|
|
252
|
+
seg.type === "filter") &&
|
|
253
|
+
seg.text) {
|
|
254
|
+
newText = seg.text;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
if (seg.type === "getByRole" && seg.name) {
|
|
258
|
+
newText = seg.name;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
if (seg.type === "filter" && seg.hasText) {
|
|
262
|
+
newText = seg.hasText;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!newText) {
|
|
268
|
+
const newSelector = parsed.new_selector ?? "";
|
|
269
|
+
const newHasText = newSelector.match(/has-text\(["']([^"']+)["']\)/);
|
|
270
|
+
if (newHasText)
|
|
271
|
+
newText = newHasText[1];
|
|
272
|
+
}
|
|
273
|
+
if (!newText)
|
|
274
|
+
return undefined;
|
|
275
|
+
// Strategy 1: DNA text
|
|
276
|
+
const prevText = request.previousState?.text;
|
|
277
|
+
if (prevText && prevText !== newText) {
|
|
278
|
+
const prevWords = prevText.split(/\s+/);
|
|
279
|
+
const newWords = newText.split(/\s+/);
|
|
280
|
+
const lenDiff = Math.abs(prevWords.length - newWords.length);
|
|
281
|
+
let diffCount = 0;
|
|
282
|
+
const minLen = Math.min(prevWords.length, newWords.length);
|
|
283
|
+
for (let i = 0; i < minLen; i++) {
|
|
284
|
+
if (prevWords[i] !== newWords[i])
|
|
285
|
+
diffCount++;
|
|
286
|
+
}
|
|
287
|
+
if (lenDiff <= 1 && diffCount <= 3 && diffCount > 0) {
|
|
288
|
+
logger_1.logger.debug(`contentChange inferred from DNA text: "${prevText}" → "${newText}"`);
|
|
289
|
+
return { oldText: prevText, newText };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Strategy 2: has-text in old selector
|
|
293
|
+
const oldHasText = request.failedSelector.match(/has-text\(["']([^"']+)["']\)/);
|
|
294
|
+
if (oldHasText && oldHasText[1] !== newText) {
|
|
295
|
+
logger_1.logger.debug(`contentChange inferred from has-text diff: "${oldHasText[1]}" → "${newText}"`);
|
|
296
|
+
return { oldText: oldHasText[1], newText };
|
|
297
|
+
}
|
|
298
|
+
// Strategy 3: intent description
|
|
299
|
+
const intentTextMatch = request.targetIntent?.match(/with text ["']([^"']+)["']/i);
|
|
300
|
+
if (intentTextMatch && intentTextMatch[1] !== newText) {
|
|
301
|
+
const oldIntentText = intentTextMatch[1];
|
|
302
|
+
const intentWords = oldIntentText.split(/\s+/);
|
|
303
|
+
const newWords = newText.split(/\s+/);
|
|
304
|
+
const lenDiff = Math.abs(intentWords.length - newWords.length);
|
|
305
|
+
let diffCount = 0;
|
|
306
|
+
const minLen = Math.min(intentWords.length, newWords.length);
|
|
307
|
+
for (let i = 0; i < minLen; i++) {
|
|
308
|
+
if (intentWords[i] !== newWords[i])
|
|
309
|
+
diffCount++;
|
|
310
|
+
}
|
|
311
|
+
if (lenDiff <= 1 && diffCount <= 3 && diffCount > 0) {
|
|
312
|
+
logger_1.logger.debug(`contentChange inferred from intent: "${oldIntentText}" → "${newText}"`);
|
|
313
|
+
return { oldText: oldIntentText, newText };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
async getFix(request) {
|
|
319
|
+
const dnaSummary = request.previousState;
|
|
320
|
+
// Reverse-engineer the original chain structure for the AI
|
|
321
|
+
const originalChainHint = inferOriginalChainHint(request.failedSelector);
|
|
322
|
+
const chainHintJson = JSON.stringify(originalChainHint, null, 2);
|
|
323
|
+
const userMessage = `
|
|
324
|
+
### PROBLEM
|
|
325
|
+
A Playwright test failed. Analyze the full selector path and return a Smart Chain fix.
|
|
326
|
+
|
|
327
|
+
### FAILED SELECTOR (Full Path):
|
|
328
|
+
"${request.failedSelector}"
|
|
329
|
+
|
|
330
|
+
### ORIGINAL CHAIN STRUCTURE (reverse-engineered from failed selector):
|
|
331
|
+
${chainHintJson}
|
|
332
|
+
Use this to understand the developer's original intent. Preserve the same structural
|
|
333
|
+
pattern in your "chainSegments" where possible (e.g., if original used a filter + getByRole
|
|
334
|
+
pattern, try to keep filter + getByRole if the element still exists in a list context).
|
|
335
|
+
|
|
336
|
+
### INTENT:
|
|
337
|
+
${request.targetIntent}
|
|
338
|
+
|
|
339
|
+
### PREVIOUS DNA (Element Fingerprint):
|
|
340
|
+
${dnaSummary && Object.keys(dnaSummary).length > 0
|
|
341
|
+
? JSON.stringify(dnaSummary, null, 2)
|
|
342
|
+
: "NO DNA (First run - infer from selector and DOM)"}
|
|
343
|
+
|
|
344
|
+
### CURRENT DOM SNIPPET:
|
|
345
|
+
${request.currentDom}
|
|
346
|
+
|
|
347
|
+
### YOUR TASK
|
|
348
|
+
1. Find the element in the current DOM.
|
|
349
|
+
2. Build a "chainSegments" array using Playwright's semantic API (priority: getByTestId > getByRole > getByLabel > getByText > filter+narrow > locator).
|
|
350
|
+
3. Preserve the developer's chain structure (filter patterns, role patterns) when possible.
|
|
351
|
+
4. Populate "contentChange" if the element's visible text changed.
|
|
352
|
+
5. Also populate "segments" (legacy format) for backward compatibility.
|
|
353
|
+
|
|
354
|
+
Return ONLY a raw JSON object - no markdown, no preamble.`;
|
|
355
|
+
try {
|
|
356
|
+
const msg = await this.anthropic.messages.create({
|
|
357
|
+
model: this.model,
|
|
358
|
+
max_tokens: 1024,
|
|
359
|
+
system: SYSTEM_PROMPT_TEXT +
|
|
360
|
+
"\nIMPORTANT: Respond ONLY with a raw JSON object.",
|
|
361
|
+
messages: [{ role: "user", content: userMessage }],
|
|
362
|
+
});
|
|
363
|
+
const rawText = msg.content[0].type === "text" ? msg.content[0].text : "";
|
|
364
|
+
logger_1.logger.debug(`AI Raw Response: ${rawText}`);
|
|
365
|
+
const jsonString = this.cleanJson(rawText);
|
|
366
|
+
if (!jsonString)
|
|
367
|
+
throw new Error("AI did not return a valid JSON object");
|
|
368
|
+
const parsed = JSON.parse(jsonString);
|
|
369
|
+
// ── Normalise confidence: keep only finite numbers in [0,100] ─────
|
|
370
|
+
// Anything else (undefined, NaN, null, string, out-of-range) collapses
|
|
371
|
+
// to `undefined` so reports render "n/a" instead of synthesising 0.
|
|
372
|
+
const rawConf = parsed.confidence;
|
|
373
|
+
if (typeof rawConf === "number" && Number.isFinite(rawConf)) {
|
|
374
|
+
parsed.confidence = Math.max(0, Math.min(100, Math.round(rawConf)));
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
if (rawConf !== undefined) {
|
|
378
|
+
logger_1.logger.warn(`Discarded non-numeric confidence: ${JSON.stringify(rawConf)}`);
|
|
379
|
+
}
|
|
380
|
+
parsed.confidence = undefined;
|
|
381
|
+
}
|
|
382
|
+
// ── Normalize chainSegments → segments (legacy) ───────────────────
|
|
383
|
+
if (parsed.chainSegments && Array.isArray(parsed.chainSegments)) {
|
|
384
|
+
// Reconstruct a flat new_selector from chain segments for legacy paths
|
|
385
|
+
const flatParts = [];
|
|
386
|
+
for (const seg of parsed.chainSegments) {
|
|
387
|
+
switch (seg.type) {
|
|
388
|
+
case "locator":
|
|
389
|
+
case "frameLocator":
|
|
390
|
+
flatParts.push(seg.selector);
|
|
391
|
+
break;
|
|
392
|
+
case "getByRole":
|
|
393
|
+
flatParts.push(seg.name
|
|
394
|
+
? `[role="${seg.role}"]:has-text("${seg.name}")`
|
|
395
|
+
: `[role="${seg.role}"]`);
|
|
396
|
+
break;
|
|
397
|
+
case "getByLabel":
|
|
398
|
+
flatParts.push(`label:has-text("${seg.text}")`);
|
|
399
|
+
break;
|
|
400
|
+
case "getByText":
|
|
401
|
+
flatParts.push(`:has-text("${seg.text}")`);
|
|
402
|
+
break;
|
|
403
|
+
case "getByTestId":
|
|
404
|
+
flatParts.push(`[data-testid="${seg.testId}"]`);
|
|
405
|
+
break;
|
|
406
|
+
case "getByPlaceholder":
|
|
407
|
+
flatParts.push(`[placeholder="${seg.text}"]`);
|
|
408
|
+
break;
|
|
409
|
+
case "filter":
|
|
410
|
+
if (seg.hasText)
|
|
411
|
+
flatParts.push(`:has-text("${seg.hasText}")`);
|
|
412
|
+
break;
|
|
413
|
+
default:
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (flatParts.length > 0 && !parsed.new_selector) {
|
|
418
|
+
parsed.new_selector = flatParts.join(" >> ");
|
|
419
|
+
}
|
|
420
|
+
// Also inject into legacy segments if missing
|
|
421
|
+
if (!parsed.segments || parsed.segments.length === 0) {
|
|
422
|
+
const frameSegs = parsed.chainSegments.filter((s) => s.type === "frameLocator");
|
|
423
|
+
const elemSegs = parsed.chainSegments.filter((s) => s.type !== "frameLocator");
|
|
424
|
+
parsed.segments = [
|
|
425
|
+
...frameSegs.map((s) => ({
|
|
426
|
+
type: "frame",
|
|
427
|
+
selector: s.selector,
|
|
428
|
+
})),
|
|
429
|
+
{ type: "element", selector: parsed.new_selector ?? "" },
|
|
430
|
+
];
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// ── Normalize legacy new_selector → segments ──────────────────────
|
|
434
|
+
if (!parsed.segments && parsed.new_selector) {
|
|
435
|
+
const parts = parsed.new_selector
|
|
436
|
+
.split(" >> ")
|
|
437
|
+
.map((s) => s.trim());
|
|
438
|
+
parsed.segments = parts.map((s, i) => ({
|
|
439
|
+
type: i === parts.length - 1 ? "element" : "frame",
|
|
440
|
+
selector: s,
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
443
|
+
if (!parsed.segments || !Array.isArray(parsed.segments)) {
|
|
444
|
+
throw new Error("AI response missing 'segments' array");
|
|
445
|
+
}
|
|
446
|
+
parsed.new_selector =
|
|
447
|
+
parsed.new_selector ??
|
|
448
|
+
parsed.segments.map((s) => s.selector).join(" >> ");
|
|
449
|
+
// Attach original chain hint for ASTUpdater's partial healing logic
|
|
450
|
+
parsed.originalChainHint = originalChainHint;
|
|
451
|
+
// ── Ensure contentChange is populated ──────────────────────────────
|
|
452
|
+
if (!parsed.contentChange || typeof parsed.contentChange !== "object") {
|
|
453
|
+
const inferred = this.inferContentChange(parsed, request);
|
|
454
|
+
if (inferred) {
|
|
455
|
+
parsed.contentChange = inferred;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (parsed.contentChange) {
|
|
459
|
+
logger_1.logger.debug(`contentChange: "${parsed.contentChange.oldText}" → "${parsed.contentChange.newText}"`);
|
|
460
|
+
}
|
|
461
|
+
return parsed;
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
logger_1.logger.error("Analysis failed", error.message);
|
|
465
|
+
return {
|
|
466
|
+
status: "NOT_FOUND",
|
|
467
|
+
segments: [],
|
|
468
|
+
new_selector: null,
|
|
469
|
+
confidence: undefined,
|
|
470
|
+
explanation: `AI Error: ${error.message}`,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
exports.LLMService = LLMService;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AnchorKey, SourceSpan } from "./AnchorResolver";
|
|
2
|
+
/**
|
|
3
|
+
* A single edit. `replace` edits carry an `anchor` and are RE-RESOLVED at
|
|
4
|
+
* apply-time (span recomputed from the fresh parse). `insert` edits (forks)
|
|
5
|
+
* carry a literal `span` computed against the same content being applied.
|
|
6
|
+
*/
|
|
7
|
+
export interface AnchoredEdit {
|
|
8
|
+
kind: "replace" | "insert";
|
|
9
|
+
replacement: string;
|
|
10
|
+
/** Replace target — re-resolved via resolveAnchor at apply time. */
|
|
11
|
+
anchor?: AnchorKey;
|
|
12
|
+
/** Literal span for inserts (and as a fallback for anchorless replaces). */
|
|
13
|
+
span?: SourceSpan;
|
|
14
|
+
/**
|
|
15
|
+
* Require the replacement to be a Locator-returning expression. Defaults to
|
|
16
|
+
* true for `replace`. Set false for inserts / non-locator edits.
|
|
17
|
+
*/
|
|
18
|
+
requireLocator?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface FileBatch {
|
|
21
|
+
filePath: string;
|
|
22
|
+
edits: AnchoredEdit[];
|
|
23
|
+
}
|
|
24
|
+
export type ApplyStatus = "applied" | "rolled-back" | "no-op" | "skipped";
|
|
25
|
+
export interface ApplyOutcome {
|
|
26
|
+
filePath: string;
|
|
27
|
+
status: ApplyStatus;
|
|
28
|
+
reason: string;
|
|
29
|
+
written: boolean;
|
|
30
|
+
/** Content after the run — the new content when applied, else the original. */
|
|
31
|
+
finalContent: string;
|
|
32
|
+
/** Edits that could not be applied (stale anchor, type-incompat, …). */
|
|
33
|
+
skipped: Array<{
|
|
34
|
+
reason: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export interface ApplyOptions {
|
|
38
|
+
/** When false, compute + gate but never touch disk. Default true. */
|
|
39
|
+
write?: boolean;
|
|
40
|
+
/** Honour SELA_DRY_RUN by leaving disk untouched. Default true. */
|
|
41
|
+
respectDryRun?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare class MutationApplier {
|
|
44
|
+
/** Apply many batches; each file is processed independently. */
|
|
45
|
+
static applyBatches(batches: FileBatch[], opts?: ApplyOptions): ApplyOutcome[];
|
|
46
|
+
/**
|
|
47
|
+
* Apply a file batch under a per-file cross-process lock (SELA-5).
|
|
48
|
+
*
|
|
49
|
+
* The lock makes the read-modify-write of a single source file atomic across
|
|
50
|
+
* concurrent Playwright workers — the real lost-update risk is two specs that
|
|
51
|
+
* heal a SHARED page-object simultaneously (each reads the same baseline and
|
|
52
|
+
* the second write drops the first's fix). The lock dir is keyed by file
|
|
53
|
+
* path, so heals on DIFFERENT files never serialise — worker scalability is
|
|
54
|
+
* preserved. Read-only / dry-run runs skip the lock entirely (no disk RMW).
|
|
55
|
+
*
|
|
56
|
+
* A stale lock (crashed holder) is reclaimed after 30s so a worker crash can
|
|
57
|
+
* never permanently wedge CI.
|
|
58
|
+
*/
|
|
59
|
+
static applyFileBatch(batch: FileBatch, opts?: ApplyOptions): ApplyOutcome;
|
|
60
|
+
private static applyFileBatchUnlocked;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Cheap return-type pre-check. The replacement must be an expression whose
|
|
64
|
+
* OUTERMOST call is a Locator-returning Playwright method (locator / getBy* /
|
|
65
|
+
* frameLocator / nth / first / last / filter / and / or). Rejects actions
|
|
66
|
+
* (.click()), string queries (.title(), .textContent()), bare literals, etc.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isLocatorReturningExpr(text: string): boolean;
|
|
69
|
+
//# sourceMappingURL=MutationApplier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"AA2BA,OAAO,EACL,SAAS,EAET,UAAU,EACX,MAAM,kBAAkB,CAAC;AAY1B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAeD,qBAAa,eAAe;IAC1B,gEAAgE;IAChE,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,cAAc,CACnB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,YAAiB,GACtB,YAAY;IAqCf,OAAO,CAAC,MAAM,CAAC,sBAAsB;CA2KtC;AA2CD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB5D"}
|