sela-core 1.0.6 → 1.0.7
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 +1 -1
- package/dist/config/ConfigLoader.d.ts +1 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +10 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +79 -0
- package/dist/config/SelaConfig.d.ts +15 -1
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +27 -1
- package/dist/engine/SelaEngine.d.ts +12 -9
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +249 -72
- package/dist/errors/SelaError.d.ts +133 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +155 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -1
- package/dist/fixtures/expectProxy.js +7 -0
- package/dist/fixtures/index.d.ts +7 -1
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +15 -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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/reporter/SelaReporter.d.ts +110 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +328 -0
- package/dist/services/ASTSourceUpdater.d.ts +19 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +173 -29
- package/dist/services/HealReportService.d.ts +42 -0
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +80 -1
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +460 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +20 -1
- package/dist/services/IntentAuditor.d.ts +18 -1
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +19 -6
- package/dist/services/InteractiveReview.d.ts +24 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +218 -0
- package/dist/services/LLMService.d.ts +17 -1
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +18 -8
- package/dist/services/PRAutomationService.d.ts +14 -1
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +260 -69
- package/dist/services/PendingPromptLedger.d.ts +44 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +180 -0
- package/dist/services/ReportGenerator.d.ts +70 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +191 -0
- package/dist/services/SafetyGuard.d.ts +27 -1
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +58 -12
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +44 -13
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +202 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +379 -0
- package/package.json +71 -67
|
@@ -36,15 +36,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.SelaEngine = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
const child_process_1 = require("child_process");
|
|
40
39
|
const LLMService_1 = require("../services/LLMService");
|
|
40
|
+
const WorkspaceSnapshotService_1 = require("../services/WorkspaceSnapshotService");
|
|
41
|
+
const IsolatedDiff_1 = require("../utils/IsolatedDiff");
|
|
41
42
|
const SnapshotService_1 = require("../services/SnapshotService");
|
|
42
43
|
const DOMUtils_1 = require("../utils/DOMUtils");
|
|
43
44
|
const SourceUpdater_1 = require("../services/SourceUpdater");
|
|
45
|
+
const ASTSourceUpdater_1 = require("../services/ASTSourceUpdater");
|
|
44
46
|
const SafetyGuard_1 = require("../services/SafetyGuard");
|
|
47
|
+
const HealingCacheService_1 = require("../services/HealingCacheService");
|
|
45
48
|
const ConfigLoader_1 = require("../config/ConfigLoader");
|
|
49
|
+
const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
46
50
|
const HealReportService_1 = require("../services/HealReportService");
|
|
47
51
|
const PRAutomationService_1 = require("../services/PRAutomationService");
|
|
52
|
+
const PendingPromptLedger_1 = require("../services/PendingPromptLedger");
|
|
53
|
+
const SelaError_1 = require("../errors/SelaError");
|
|
48
54
|
/**
|
|
49
55
|
* Graceful percentage formatter for engine-side log lines. Mirrors the helper
|
|
50
56
|
* in PRAutomationService / HealReportService so a missing AI/auditor
|
|
@@ -53,10 +59,25 @@ const PRAutomationService_1 = require("../services/PRAutomationService");
|
|
|
53
59
|
function fmtPctEngine(v) {
|
|
54
60
|
return typeof v === "number" && Number.isFinite(v) ? `${v}%` : "n/a";
|
|
55
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Truthy when the current process is a CI runner. We accept the standard
|
|
64
|
+
* `CI=1`/`CI=true` convention used by GitHub Actions, GitLab, CircleCI,
|
|
65
|
+
* Buildkite, etc. Anything else (empty string, undefined, `0`, `false`)
|
|
66
|
+
* is treated as Local DX mode.
|
|
67
|
+
*/
|
|
68
|
+
function isCiEnvironment() {
|
|
69
|
+
const raw = process.env.CI;
|
|
70
|
+
if (raw === undefined || raw === null)
|
|
71
|
+
return false;
|
|
72
|
+
const v = String(raw).trim().toLowerCase();
|
|
73
|
+
return v !== "" && v !== "0" && v !== "false";
|
|
74
|
+
}
|
|
56
75
|
class SelaEngine {
|
|
57
76
|
llmService;
|
|
58
77
|
snapshotService;
|
|
59
78
|
safetyGuard;
|
|
79
|
+
astDirectiveScanner;
|
|
80
|
+
healingCache;
|
|
60
81
|
config;
|
|
61
82
|
dnaBuffer = new Map();
|
|
62
83
|
healMetaBuffer = new Map();
|
|
@@ -66,6 +87,14 @@ class SelaEngine {
|
|
|
66
87
|
this.llmService = new LLMService_1.LLMService();
|
|
67
88
|
this.snapshotService = new SnapshotService_1.SnapshotService();
|
|
68
89
|
this.safetyGuard = new SafetyGuard_1.SafetyGuard(this.config.thresholds);
|
|
90
|
+
// Dedicated lightweight ts-morph project used only for the leading-comment
|
|
91
|
+
// pre-scan. Keeping it separate from the SourceUpdater's project avoids
|
|
92
|
+
// polluting that cache with files we may never actually mutate.
|
|
93
|
+
this.astDirectiveScanner = new ASTSourceUpdater_1.ASTSourceUpdater();
|
|
94
|
+
// Local healing cache — short-circuits the LLM call when an identical
|
|
95
|
+
// (filePath, oldSelector) pair was healed safely in a prior session.
|
|
96
|
+
// Disabled when SELA_NO_CACHE=1; reads/writes skipped during dry runs.
|
|
97
|
+
this.healingCache = HealingCacheService_1.sharedHealingCache;
|
|
69
98
|
}
|
|
70
99
|
// ─────────────────────────────────────────────────────────────
|
|
71
100
|
// healArgument — repairs a wrong action argument (e.g. selectOption)
|
|
@@ -76,10 +105,10 @@ class SelaEngine {
|
|
|
76
105
|
const { dom } = await DOMUtils_1.DOMUtils.getNeighborhoodDom(page, selector);
|
|
77
106
|
const neighborhoodDom = dom;
|
|
78
107
|
const targetIntent = action === "selectOption"
|
|
79
|
-
? `ARGUMENT_HEALING: The selectOption argument "${oldArgument}" does not exist.
|
|
80
|
-
Look at the <select> element in the DOM and find all <option> elements inside it.
|
|
81
|
-
Return in 'new_selector' ONLY the TEXT CONTENT of the closest matching <option>.
|
|
82
|
-
For example: if the options are "Male" and "Female" and the old argument was "Man", return "Male".
|
|
108
|
+
? `ARGUMENT_HEALING: The selectOption argument "${oldArgument}" does not exist.
|
|
109
|
+
Look at the <select> element in the DOM and find all <option> elements inside it.
|
|
110
|
+
Return in 'new_selector' ONLY the TEXT CONTENT of the closest matching <option>.
|
|
111
|
+
For example: if the options are "Male" and "Female" and the old argument was "Man", return "Male".
|
|
83
112
|
CRITICAL: Do NOT return a CSS selector like "#my-select". Return plain text only, exactly as it appears in the option.`
|
|
84
113
|
: `Fix the broken argument "${oldArgument}" for action "${action}". Return the correct value.`;
|
|
85
114
|
const aiFix = await this.llmService.getFix({
|
|
@@ -108,6 +137,37 @@ CRITICAL: Do NOT return a CSS selector like "#my-select". Return plain text only
|
|
|
108
137
|
// Resolve file path for report purposes (project-relative when possible).
|
|
109
138
|
const reportFile = this._toReportFile(filePath);
|
|
110
139
|
const testTitle = this.testTitleBuffer.get(stableId);
|
|
140
|
+
// ── Developer fail-fast directive pre-gate ──────────────────────
|
|
141
|
+
// Scan the failing statement's leading comments for `// sela-fail-fast`
|
|
142
|
+
// BEFORE the expensive LLM round-trip. When present, record a
|
|
143
|
+
// "Skipped by Developer" FailedEvent and propagate the canonical
|
|
144
|
+
// SelaError(AST_HEAL_DISABLED_BY_DIRECTIVE) so retries are skipped too.
|
|
145
|
+
try {
|
|
146
|
+
this.astDirectiveScanner.enforceFailFastDirective({ filePath, line });
|
|
147
|
+
}
|
|
148
|
+
catch (directiveErr) {
|
|
149
|
+
if ((0, SelaError_1.isSelaErrorFrom)(directiveErr, "ASTUpdater") &&
|
|
150
|
+
directiveErr.code === SelaError_1.AST_UPDATER_CODES.HEAL_DISABLED_BY_DIRECTIVE) {
|
|
151
|
+
console.warn(`[Sela] 🛑 Skipped by Developer — ${directiveErr.reason}`);
|
|
152
|
+
const skipEvent = {
|
|
153
|
+
kind: "FAILED",
|
|
154
|
+
stableId,
|
|
155
|
+
sourceFile: reportFile,
|
|
156
|
+
sourceLine: line,
|
|
157
|
+
testTitle,
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
framePath: [],
|
|
160
|
+
inIframe: false,
|
|
161
|
+
inShadowDom: false,
|
|
162
|
+
oldSelector: elementSelectorOnly,
|
|
163
|
+
reason: "Skipped by Developer (sela-fail-fast directive)",
|
|
164
|
+
dnaBefore: null,
|
|
165
|
+
};
|
|
166
|
+
HealReportService_1.sharedHealReport.record(skipEvent);
|
|
167
|
+
throw directiveErr;
|
|
168
|
+
}
|
|
169
|
+
throw directiveErr;
|
|
170
|
+
}
|
|
111
171
|
// Snapshot for report event context (loaded once below, used at all exit paths).
|
|
112
172
|
let lastKnownStateForReport = null;
|
|
113
173
|
let aiFixForReport = null;
|
|
@@ -134,19 +194,19 @@ CRITICAL: Do NOT return a CSS selector like "#my-select". Return plain text only
|
|
|
134
194
|
const intentDescription = lastKnownState
|
|
135
195
|
? `The element that was a ${lastKnownState.tagName} with text "${lastKnownState.text}"`
|
|
136
196
|
: `Element identified by ${stableId}`;
|
|
137
|
-
const contentChangeInstructions = `
|
|
138
|
-
|
|
139
|
-
[CONTENT CHANGE DETECTION]:
|
|
140
|
-
If the element's VISIBLE TEXT has changed compared to its previous value${oldText ? ` ("${oldText}")` : ""}, you MUST include a "contentChange" field.
|
|
141
|
-
NOTE: This change will NOT be applied automatically to the source code to prevent masking potential bugs.
|
|
142
|
-
It will only be presented as a suggested fix in the terminal.
|
|
143
|
-
{
|
|
144
|
-
"contentChange": {
|
|
145
|
-
"oldText": "<the previous visible text>",
|
|
146
|
-
"newText": "<the new visible text as it appears in the current DOM>"
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
This is required so that any expect(...).toHaveText("${oldText ?? "..."}") assertions in the test file can be automatically updated to match the new text.
|
|
197
|
+
const contentChangeInstructions = `
|
|
198
|
+
|
|
199
|
+
[CONTENT CHANGE DETECTION]:
|
|
200
|
+
If the element's VISIBLE TEXT has changed compared to its previous value${oldText ? ` ("${oldText}")` : ""}, you MUST include a "contentChange" field.
|
|
201
|
+
NOTE: This change will NOT be applied automatically to the source code to prevent masking potential bugs.
|
|
202
|
+
It will only be presented as a suggested fix in the terminal.
|
|
203
|
+
{
|
|
204
|
+
"contentChange": {
|
|
205
|
+
"oldText": "<the previous visible text>",
|
|
206
|
+
"newText": "<the new visible text as it appears in the current DOM>"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
This is required so that any expect(...).toHaveText("${oldText ?? "..."}") assertions in the test file can be automatically updated to match the new text.
|
|
150
210
|
If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
151
211
|
// ── Semantic anchor context hint for the LLM ─────────────────────
|
|
152
212
|
// When DNA v2 anchor data exists, tell the AI which label and row
|
|
@@ -171,16 +231,56 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
171
231
|
"\n" +
|
|
172
232
|
"Prioritize selectors that preserve these relationships (e.g. filter by row key, then target by label).");
|
|
173
233
|
})();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
234
|
+
// ── Cache pre-check ────────────────────────────────────────────
|
|
235
|
+
// When a prior session healed this exact (filePath, oldSelector)
|
|
236
|
+
// pair AND SafetyGuard cleared it, replay the cached fix instead
|
|
237
|
+
// of paying the LLM round-trip. Disabled when SELA_NO_CACHE=1.
|
|
238
|
+
// The synthetic aiFix is fed through SafetyGuard exactly like a
|
|
239
|
+
// fresh LLM response — cache is a latency optimisation, never a
|
|
240
|
+
// safety bypass.
|
|
241
|
+
let aiFix = null;
|
|
242
|
+
let cacheHit = false;
|
|
243
|
+
// Fingerprint inputs the cache key depends on. Recomputed every
|
|
244
|
+
// heal so a DNA rewrite — which changes `dnaContentHash` —
|
|
245
|
+
// automatically invalidates every prior entry without an explicit
|
|
246
|
+
// purge.
|
|
247
|
+
const dnaContentHash = (0, HealingCacheService_1.hashDnaContent)(lastKnownState ?? null);
|
|
248
|
+
const cacheKeyInput = {
|
|
249
|
+
filePath,
|
|
250
|
+
line,
|
|
251
|
+
dnaContentHash,
|
|
252
|
+
brokenSelector: fullSelector,
|
|
253
|
+
mode: healMode,
|
|
254
|
+
};
|
|
255
|
+
if (!this.config.noCache) {
|
|
256
|
+
const cached = this.healingCache.get(cacheKeyInput);
|
|
257
|
+
if (cached) {
|
|
258
|
+
console.log(`[Sela] 🧊 Cache hit (0ms LLM) — replaying healed selector "${cached.healedSelector}"`);
|
|
259
|
+
aiFix = {
|
|
260
|
+
status: "FIXED",
|
|
261
|
+
new_selector: cached.healedSelector,
|
|
262
|
+
chainSegments: cached.chainSegments,
|
|
263
|
+
segments: cached.segments ??
|
|
264
|
+
[{ type: "element", selector: cached.healedSelector }],
|
|
265
|
+
confidence: cached.confidence,
|
|
266
|
+
contentChange: cached.contentChange,
|
|
267
|
+
explanation: cached.explanation ?? "Replayed from local healing cache",
|
|
268
|
+
};
|
|
269
|
+
cacheHit = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!cacheHit) {
|
|
273
|
+
console.log(`[Sela] 🔍 Step 3: Requesting AI fix from LLM...`);
|
|
274
|
+
aiFix = await this.llmService.getFix({
|
|
275
|
+
targetIntent: intentDescription,
|
|
276
|
+
failedSelector: fullSelector,
|
|
277
|
+
previousState: lastKnownState || {},
|
|
278
|
+
currentDom: neighborhoodDom +
|
|
279
|
+
contextHint +
|
|
280
|
+
anchorContextHint +
|
|
281
|
+
contentChangeInstructions,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
184
284
|
aiFixForReport = aiFix;
|
|
185
285
|
if (aiFix && aiFix.status === "FIXED") {
|
|
186
286
|
console.log(`[Sela] 🔍 Step 3: AI responded ✅`);
|
|
@@ -278,7 +378,24 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
278
378
|
contentChange: aiFix.contentChange,
|
|
279
379
|
};
|
|
280
380
|
HealReportService_1.sharedHealReport.record(protectedEvent);
|
|
281
|
-
throw new
|
|
381
|
+
throw new SelaError_1.SelaError({
|
|
382
|
+
subsystem: "SafetyGuard",
|
|
383
|
+
code: safetyDecision.code ?? SelaError_1.SAFETY_GUARD_CODES.GENERIC_REJECTION,
|
|
384
|
+
reason: safetyDecision.reason,
|
|
385
|
+
context: {
|
|
386
|
+
filePath: reportFile,
|
|
387
|
+
line,
|
|
388
|
+
selector: elementSelectorOnly,
|
|
389
|
+
healedSelector: newFullSelector,
|
|
390
|
+
stableId,
|
|
391
|
+
testTitle,
|
|
392
|
+
branch,
|
|
393
|
+
safetyLevel: safetyDecision.level,
|
|
394
|
+
auditorVerdict: safetyDecision.meta?.auditorVerdict,
|
|
395
|
+
auditorConfidence: safetyDecision.meta?.auditorConfidence,
|
|
396
|
+
aiConfidence: aiFix.confidence,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
282
399
|
}
|
|
283
400
|
// ── Heal-completion score summary ─────────────────────────────────
|
|
284
401
|
// Emits a single consolidated line showing AI confidence plus the
|
|
@@ -312,13 +429,14 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
312
429
|
(sameFileDef ? defSite?.line : undefined) ??
|
|
313
430
|
line;
|
|
314
431
|
const mutatedFilePath = !sameFileDef && defSite ? defSite.file : filePath;
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
|
|
432
|
+
// Clean-Room isolated diff. We diff the in-memory snapshot recorded
|
|
433
|
+
// by WorkspaceSnapshotService (contentBefore — disk state right
|
|
434
|
+
// before Sela's first write to this file) against the post-write
|
|
435
|
+
// disk state. The result contains EXCLUSIVELY Sela's selector swap;
|
|
436
|
+
// any developer dirty edits sit identically in both buffers and
|
|
437
|
+
// therefore cancel out. Falls back to "" when the snapshot is
|
|
438
|
+
// missing (cross-file healing into an untracked path, etc.).
|
|
439
|
+
const gitUnifiedDiff = this._captureIsolatedDiff(mutatedFilePath);
|
|
322
440
|
const firstChange = this._extractFirstChangeFromDiff(gitUnifiedDiff);
|
|
323
441
|
// Backward-compat: keep oldCodeLine/newCodeLine populated from the
|
|
324
442
|
// first -/+ pair inside the diff. When git produced no diff (file
|
|
@@ -368,6 +486,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
368
486
|
framePath: successfulPath,
|
|
369
487
|
inIframe: successfulPath.length > 0,
|
|
370
488
|
inShadowDom: liveSnapshot?.isInShadowDom ?? false,
|
|
489
|
+
dryRun: DryRunGuard_1.DryRunGuard.active() ? true : undefined,
|
|
371
490
|
oldSelector: elementSelectorOnly,
|
|
372
491
|
newSelector: canonicalSelector,
|
|
373
492
|
oldCodeLine,
|
|
@@ -396,6 +515,25 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
396
515
|
contentChange: aiFix.contentChange,
|
|
397
516
|
};
|
|
398
517
|
HealReportService_1.sharedHealReport.record(healedEvent);
|
|
518
|
+
// ── Cache write — persist this safe heal for future sessions ──
|
|
519
|
+
// Best-effort: failures are logged inside the service and never
|
|
520
|
+
// block the heal return. Skipped automatically during dry runs
|
|
521
|
+
// and when SELA_NO_CACHE=1.
|
|
522
|
+
if (!this.config.noCache && !cacheHit) {
|
|
523
|
+
this.healingCache
|
|
524
|
+
.set({
|
|
525
|
+
...cacheKeyInput,
|
|
526
|
+
healedSelector: canonicalSelector,
|
|
527
|
+
chainSegments: aiFix.chainSegments,
|
|
528
|
+
segments: aiFix.segments,
|
|
529
|
+
confidence: aiFix.confidence,
|
|
530
|
+
contentChange: aiFix.contentChange,
|
|
531
|
+
explanation: aiFix.explanation,
|
|
532
|
+
alternatives: this._buildAlternatives(aiFix.chainSegments, aiFix.segments),
|
|
533
|
+
safe: true,
|
|
534
|
+
})
|
|
535
|
+
.catch((err) => console.warn(`[HealingCache] ⚠️ set() failed: ${err?.message ?? err}`));
|
|
536
|
+
}
|
|
399
537
|
return canonicalSelector;
|
|
400
538
|
}
|
|
401
539
|
// AI did not return FIXED — record as FAILED.
|
|
@@ -421,7 +559,11 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
421
559
|
catch (error) {
|
|
422
560
|
console.error(`[Sela] ❌ Healing Failure:`, error.message);
|
|
423
561
|
// Avoid double-recording when a SafetyGuard PROTECTED event was already pushed.
|
|
424
|
-
|
|
562
|
+
// Prefer the structured instanceof check; fall back to message-substring
|
|
563
|
+
// detection for any legacy throw sites that have not yet been migrated.
|
|
564
|
+
const isSafetyBlock = (0, SelaError_1.isSelaErrorFrom)(error, "SafetyGuard") ||
|
|
565
|
+
(typeof error?.message === "string" && error.message.includes("[Sela-SafetyGuard]")) ||
|
|
566
|
+
(typeof error?.message === "string" && error.message.includes("[SafetyGuard]"));
|
|
425
567
|
const isNotFoundAlreadyRecorded = typeof error?.message === "string" && error.message === "AI could not provide a fix";
|
|
426
568
|
if (!isSafetyBlock && !isNotFoundAlreadyRecorded) {
|
|
427
569
|
const failedEvent = {
|
|
@@ -471,36 +613,37 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
471
613
|
}
|
|
472
614
|
}
|
|
473
615
|
/**
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
616
|
+
* Clean-Room isolated diff: render a unified diff between the file's
|
|
617
|
+
* pre-Sela disk snapshot (`contentBefore`) and the post-write disk state
|
|
618
|
+
* (`contentAfter`) captured by `WorkspaceSnapshotService`. Returns "" when
|
|
619
|
+
* the file was never tracked (no snapshot) or the AST mutation produced
|
|
620
|
+
* no net change.
|
|
479
621
|
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
622
|
+
* This is what makes the HTML report immune to developer dirty edits: any
|
|
623
|
+
* pre-existing uncommitted changes live in BOTH buffers identically and
|
|
624
|
+
* are therefore filtered out by the diff. The output contains only Sela's
|
|
625
|
+
* selector swap.
|
|
483
626
|
*/
|
|
484
|
-
|
|
627
|
+
_captureIsolatedDiff(filePath) {
|
|
485
628
|
try {
|
|
486
629
|
const cleaned = this._toReportFile(filePath);
|
|
487
630
|
const abs = path.isAbsolute(cleaned)
|
|
488
631
|
? cleaned
|
|
489
632
|
: path.resolve(process.cwd(), cleaned);
|
|
490
|
-
if (!abs
|
|
633
|
+
if (!abs)
|
|
634
|
+
return "";
|
|
635
|
+
const snap = WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.getSnapshot(abs);
|
|
636
|
+
if (!snap || snap.contentAfter === null)
|
|
491
637
|
return "";
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
windowsHide: true,
|
|
638
|
+
if (snap.contentBefore === snap.contentAfter)
|
|
639
|
+
return "";
|
|
640
|
+
return (0, IsolatedDiff_1.generateUnifiedDiff)(snap.contentBefore, snap.contentAfter, {
|
|
641
|
+
filePath: snap.relativePath,
|
|
642
|
+
context: 1,
|
|
498
643
|
});
|
|
499
|
-
return out.toString();
|
|
500
644
|
}
|
|
501
645
|
catch (err) {
|
|
502
|
-
|
|
503
|
-
console.debug?.(`[Sela] git diff capture skipped: ${err?.message ?? err}`);
|
|
646
|
+
console.debug?.(`[Sela] isolated diff capture skipped: ${err?.message ?? err}`);
|
|
504
647
|
return "";
|
|
505
648
|
}
|
|
506
649
|
}
|
|
@@ -681,26 +824,54 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
681
824
|
console.warn(`[Sela] ⚠️ Failed to write insights report: ${err.message}`);
|
|
682
825
|
}
|
|
683
826
|
}
|
|
684
|
-
// ──
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
827
|
+
// ── Dual-Mode post-run lifecycle ─────────────────────────────────
|
|
828
|
+
//
|
|
829
|
+
// CI (process.env.CI truthy) → Clean-Room PR automation (existing flow).
|
|
830
|
+
// Local DX (CI falsy) → Interactive CLI review of each mutated
|
|
831
|
+
// file; Apply keeps the on-disk fix, Reject
|
|
832
|
+
// rewinds via WorkspaceSnapshotService.
|
|
833
|
+
//
|
|
834
|
+
// The interactive branch deliberately bypasses PR automation because:
|
|
835
|
+
// • AST fixes are ALREADY persisted to disk (the test passed on them).
|
|
836
|
+
// • Local devs want to inspect + cherry-pick, not auto-commit.
|
|
837
|
+
if (isCiEnvironment()) {
|
|
838
|
+
if (this.config.prAutomation.enabled) {
|
|
839
|
+
try {
|
|
840
|
+
const branchInfo = (0, PRAutomationService_1.detectBranch)(process.cwd());
|
|
841
|
+
const decision = (0, PRAutomationService_1.decideEffectiveStrategy)(this.config.prAutomation, branchInfo.branch, healedEvents);
|
|
842
|
+
if (decision.downgradeReason) {
|
|
843
|
+
console.warn(`[Sela PR] ⚠️ Strategy downgraded: '${decision.configured}' → '${decision.effective}' ` +
|
|
844
|
+
`(reason: ${decision.downgradeReason}, ` +
|
|
845
|
+
`minAI: ${fmtPctEngine(decision.minAiConfidence)}, ` +
|
|
846
|
+
`minAuditor: ${fmtPctEngine(decision.minAuditorConfidence)})`);
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
console.log(`[Sela PR] 🎯 Effective strategy: '${decision.effective}' on branch '${branchInfo.branch ?? "unknown"}'`);
|
|
850
|
+
}
|
|
851
|
+
const ctx = { cwd: process.cwd(), reportHtmlPath };
|
|
852
|
+
await (0, PRAutomationService_1.execute)(this.config.prAutomation, decision, healedEvents, branchInfo, ctx);
|
|
853
|
+
await (0, PRAutomationService_1.handleBugDetected)(this.config.prAutomation.onBugDetected, protectedEvents, branchInfo, ctx);
|
|
694
854
|
}
|
|
695
|
-
|
|
696
|
-
console.
|
|
855
|
+
catch (err) {
|
|
856
|
+
console.warn(`[Sela PR] ⚠️ PR automation step failed: ${err.message}`);
|
|
697
857
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else if (healedEvents.length > 0) {
|
|
861
|
+
// Local DX: do NOT block this process on stdin — workers have no
|
|
862
|
+
// TTY and would deadlock until Playwright's per-test timeout fires.
|
|
863
|
+
// Serialise the mutated snapshots to a cross-process ledger; the
|
|
864
|
+
// SelaReporter drains it from `onEnd()` in the main process where
|
|
865
|
+
// an interactive `readline` is safe.
|
|
866
|
+
try {
|
|
867
|
+
(0, PendingPromptLedger_1.writePendingPromptsLedger)({
|
|
868
|
+
cwd: process.cwd(),
|
|
869
|
+
workspace: WorkspaceSnapshotService_1.sharedWorkspaceSnapshot,
|
|
870
|
+
healedEvents,
|
|
871
|
+
});
|
|
701
872
|
}
|
|
702
873
|
catch (err) {
|
|
703
|
-
console.warn(`[Sela
|
|
874
|
+
console.warn(`[Sela] ⚠️ Pending-prompts ledger write failed: ${err.message}`);
|
|
704
875
|
}
|
|
705
876
|
}
|
|
706
877
|
// Clear the report buffer last so any downstream consumers (e.g. test
|
|
@@ -708,6 +879,12 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
708
879
|
if (HealReportService_1.sharedHealReport.hasContent()) {
|
|
709
880
|
HealReportService_1.sharedHealReport.clear();
|
|
710
881
|
}
|
|
882
|
+
// Drop the Clean-Room workspace snapshot ledger so the next session
|
|
883
|
+
// starts with an empty `contentBefore` map. Without this, a long-lived
|
|
884
|
+
// Node process (`vitest --watch`, IDE test runner) would carry stale
|
|
885
|
+
// pre-Sela snapshots from one session into the next, causing the
|
|
886
|
+
// isolated-diff layer to compare against the wrong baseline.
|
|
887
|
+
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.clear();
|
|
711
888
|
}
|
|
712
889
|
// ─────────────────────────────────────────────────────────────
|
|
713
890
|
// fetchLiveText — frame-aware innerText fetch (zero-trust probe)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export type SelaSubsystem = "SafetyGuard" | "IntentAuditor" | "ASTUpdater" | "Proxy" | "LLMService" | "Registry" | "ChainValidator" | "Engine" | "Report";
|
|
2
|
+
export interface SelaErrorContext {
|
|
3
|
+
filePath?: string;
|
|
4
|
+
line?: number;
|
|
5
|
+
column?: number;
|
|
6
|
+
selector?: string;
|
|
7
|
+
healedSelector?: string;
|
|
8
|
+
stableId?: string;
|
|
9
|
+
testTitle?: string;
|
|
10
|
+
testFile?: string;
|
|
11
|
+
branch?: string;
|
|
12
|
+
/** SafetyGuard verdict level when the error originated in SafetyGuard. */
|
|
13
|
+
safetyLevel?: "BLOCKED" | "FAIL_HARD" | "REQUIRES_REVIEW" | "SAFE";
|
|
14
|
+
/** Auditor verdict surfaced in the context for SafetyGuard rejections. */
|
|
15
|
+
auditorVerdict?: "CONSISTENT" | "INCONSISTENT" | "SUSPICIOUS";
|
|
16
|
+
/** Raw + adjusted LLM confidence so downstream telemetry can plot drift. */
|
|
17
|
+
aiConfidence?: number;
|
|
18
|
+
auditorConfidence?: number;
|
|
19
|
+
/** Free-form extension slot. Prefer adding a named field above when possible. */
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface SelaErrorJSON {
|
|
23
|
+
name: string;
|
|
24
|
+
subsystem: SelaSubsystem;
|
|
25
|
+
code: string;
|
|
26
|
+
reason: string;
|
|
27
|
+
message: string;
|
|
28
|
+
context: SelaErrorContext;
|
|
29
|
+
stack?: string;
|
|
30
|
+
cause?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SelaErrorInit {
|
|
33
|
+
subsystem: SelaSubsystem;
|
|
34
|
+
/**
|
|
35
|
+
* Stable machine-readable error code. Must match `[A-Z][A-Z0-9_]+`.
|
|
36
|
+
* Recommended convention: short subsystem-prefix + verb/noun.
|
|
37
|
+
* See SELA_ERROR_CODES below for the canonical catalogue.
|
|
38
|
+
*/
|
|
39
|
+
code: string;
|
|
40
|
+
/** Human-readable explanation. Becomes the tail of the formatted message. */
|
|
41
|
+
reason: string;
|
|
42
|
+
context?: SelaErrorContext;
|
|
43
|
+
/** Original error wrapped by this SelaError, if any. */
|
|
44
|
+
cause?: unknown;
|
|
45
|
+
}
|
|
46
|
+
export declare class SelaError extends Error {
|
|
47
|
+
readonly subsystem: SelaSubsystem;
|
|
48
|
+
readonly code: string;
|
|
49
|
+
readonly reason: string;
|
|
50
|
+
readonly context: SelaErrorContext;
|
|
51
|
+
constructor(init: SelaErrorInit);
|
|
52
|
+
static formatMessage(subsystem: SelaSubsystem, code: string, reason: string): string;
|
|
53
|
+
toJSON(): SelaErrorJSON;
|
|
54
|
+
}
|
|
55
|
+
export declare function isSelaError(err: unknown): err is SelaError;
|
|
56
|
+
export declare function isSelaErrorFrom(err: unknown, subsystem: SelaSubsystem): err is SelaError;
|
|
57
|
+
export declare const SAFETY_GUARD_CODES: {
|
|
58
|
+
readonly FAIL_HARD_VISIBILITY_GHOST: "SG_FAIL_HARD_VISIBILITY_GHOST";
|
|
59
|
+
readonly FAIL_HARD_VISIBILITY_OCCLUDED: "SG_FAIL_HARD_VISIBILITY_OCCLUDED";
|
|
60
|
+
readonly BLOCKED_LOW_CONFIDENCE: "SG_BLOCKED_LOW_CONFIDENCE";
|
|
61
|
+
readonly FAIL_HARD_SEMANTIC_INVERSION: "SG_FAIL_HARD_SEMANTIC_INVERSION";
|
|
62
|
+
readonly FAIL_HARD_STATUS_INVERSION: "SG_FAIL_HARD_STATUS_INVERSION";
|
|
63
|
+
readonly BLOCKED_ROLE_SWAP_ASSERTION: "SG_BLOCKED_ROLE_SWAP_ASSERTION";
|
|
64
|
+
readonly FAIL_HARD_AUDITOR_INCONSISTENT: "SG_FAIL_HARD_AUDITOR_INCONSISTENT";
|
|
65
|
+
readonly BLOCKED_AUDITOR_SUSPICIOUS: "SG_BLOCKED_AUDITOR_SUSPICIOUS";
|
|
66
|
+
readonly BLOCKED_SIMILARITY_FLOOR: "SG_BLOCKED_SIMILARITY_FLOOR";
|
|
67
|
+
/** Generic dispatcher when SafetyDecision arrives without an explicit code. */
|
|
68
|
+
readonly GENERIC_REJECTION: "SG_GENERIC_REJECTION";
|
|
69
|
+
};
|
|
70
|
+
export type SafetyGuardCode = (typeof SAFETY_GUARD_CODES)[keyof typeof SAFETY_GUARD_CODES];
|
|
71
|
+
export declare const INTENT_AUDITOR_CODES: {
|
|
72
|
+
/** Caller asked for an audit but no Anthropic client was configured. */
|
|
73
|
+
readonly DISABLED: "IA_DISABLED";
|
|
74
|
+
readonly INVALID_JSON: "IA_INVALID_JSON";
|
|
75
|
+
readonly CALL_FAILED: "IA_CALL_FAILED";
|
|
76
|
+
};
|
|
77
|
+
export type IntentAuditorCode = (typeof INTENT_AUDITOR_CODES)[keyof typeof INTENT_AUDITOR_CODES];
|
|
78
|
+
export declare const LLM_SERVICE_CODES: {
|
|
79
|
+
readonly NO_API_KEY: "LLM_NO_API_KEY";
|
|
80
|
+
readonly INVALID_JSON: "LLM_INVALID_JSON";
|
|
81
|
+
readonly MISSING_SEGMENTS: "LLM_MISSING_SEGMENTS";
|
|
82
|
+
readonly NOT_FOUND: "LLM_NOT_FOUND";
|
|
83
|
+
readonly API_CALL_FAILED: "LLM_API_CALL_FAILED";
|
|
84
|
+
};
|
|
85
|
+
export type LLMServiceCode = (typeof LLM_SERVICE_CODES)[keyof typeof LLM_SERVICE_CODES];
|
|
86
|
+
export declare const AST_UPDATER_CODES: {
|
|
87
|
+
readonly UNSUPPORTED_NODE: "AST_UNSUPPORTED_NODE";
|
|
88
|
+
readonly TEMPLATE_LITERAL_SKIP: "AST_TEMPLATE_LITERAL_SKIP";
|
|
89
|
+
readonly CIRCULAR_RECEIVER: "AST_CIRCULAR_RECEIVER";
|
|
90
|
+
readonly DEFINITION_NOT_FOUND: "AST_DEFINITION_NOT_FOUND";
|
|
91
|
+
readonly MAX_DEPTH_EXCEEDED: "AST_MAX_DEPTH_EXCEEDED";
|
|
92
|
+
readonly SAVE_FAILED: "AST_SAVE_FAILED";
|
|
93
|
+
/**
|
|
94
|
+
* Developer pinned the failing statement with `// sela-fail-fast`.
|
|
95
|
+
* Heal pipeline is aborted before any LLM call; FailedEvent is
|
|
96
|
+
* recorded with category "Skipped by Developer".
|
|
97
|
+
*/
|
|
98
|
+
readonly HEAL_DISABLED_BY_DIRECTIVE: "AST_HEAL_DISABLED_BY_DIRECTIVE";
|
|
99
|
+
};
|
|
100
|
+
export type ASTUpdaterCode = (typeof AST_UPDATER_CODES)[keyof typeof AST_UPDATER_CODES];
|
|
101
|
+
export declare const PROXY_CODES: {
|
|
102
|
+
readonly FRAME_SCOPED: "PRX_FRAME_SCOPED";
|
|
103
|
+
readonly REBUILD_FAILED: "PRX_REBUILD_FAILED";
|
|
104
|
+
readonly NON_LOCATOR_TARGET: "PRX_NON_LOCATOR_TARGET";
|
|
105
|
+
};
|
|
106
|
+
export type ProxyCode = (typeof PROXY_CODES)[keyof typeof PROXY_CODES];
|
|
107
|
+
export declare const REGISTRY_CODES: {
|
|
108
|
+
readonly CONTEXT_MISMATCH: "REG_CONTEXT_MISMATCH";
|
|
109
|
+
readonly FINGERPRINT_COLLISION: "REG_FINGERPRINT_COLLISION";
|
|
110
|
+
};
|
|
111
|
+
export type RegistryCode = (typeof REGISTRY_CODES)[keyof typeof REGISTRY_CODES];
|
|
112
|
+
export declare const REPORT_CODES: {
|
|
113
|
+
/** Event passed to the validator was null, undefined, or not an object. */
|
|
114
|
+
readonly MALFORMED_EVENT: "REP_MALFORMED_EVENT";
|
|
115
|
+
/** Event.kind is not one of HEALED|FAILED|PROTECTED. */
|
|
116
|
+
readonly UNKNOWN_KIND: "REP_UNKNOWN_KIND";
|
|
117
|
+
/** Event is missing a field declared mandatory for its kind. */
|
|
118
|
+
readonly MISSING_FIELD: "REP_MISSING_FIELD";
|
|
119
|
+
/** Event contains a field of the wrong type. */
|
|
120
|
+
readonly INVALID_FIELD_TYPE: "REP_INVALID_FIELD_TYPE";
|
|
121
|
+
/** Disk write failed during flushToDisk(). */
|
|
122
|
+
readonly FLUSH_FAILED: "REP_FLUSH_FAILED";
|
|
123
|
+
};
|
|
124
|
+
export type ReportCode = (typeof REPORT_CODES)[keyof typeof REPORT_CODES];
|
|
125
|
+
export declare const CHAIN_VALIDATOR_CODES: {
|
|
126
|
+
readonly TYPE_FLOW: "CV_TYPE_FLOW";
|
|
127
|
+
readonly SCHEMA: "CV_SCHEMA";
|
|
128
|
+
readonly RECEIVER: "CV_RECEIVER";
|
|
129
|
+
readonly PLACEMENT: "CV_PLACEMENT";
|
|
130
|
+
readonly SEMANTIC_DOWNGRADE: "CV_SEMANTIC_DOWNGRADE";
|
|
131
|
+
};
|
|
132
|
+
export type ChainValidatorCode = (typeof CHAIN_VALIDATOR_CODES)[keyof typeof CHAIN_VALIDATOR_CODES];
|
|
133
|
+
//# sourceMappingURL=SelaError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelaError.d.ts","sourceRoot":"","sources":["../../src/errors/SelaError.ts"],"names":[],"mappings":"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"}
|