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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/config/ConfigLoader.d.ts +1 -0
  3. package/dist/config/ConfigLoader.d.ts.map +1 -1
  4. package/dist/config/ConfigLoader.js +10 -0
  5. package/dist/config/DryRunGuard.d.ts +14 -0
  6. package/dist/config/DryRunGuard.d.ts.map +1 -0
  7. package/dist/config/DryRunGuard.js +79 -0
  8. package/dist/config/SelaConfig.d.ts +15 -1
  9. package/dist/config/SelaConfig.d.ts.map +1 -1
  10. package/dist/config/SelaConfig.js +27 -1
  11. package/dist/engine/SelaEngine.d.ts +12 -9
  12. package/dist/engine/SelaEngine.d.ts.map +1 -1
  13. package/dist/engine/SelaEngine.js +249 -72
  14. package/dist/errors/SelaError.d.ts +133 -0
  15. package/dist/errors/SelaError.d.ts.map +1 -0
  16. package/dist/errors/SelaError.js +155 -0
  17. package/dist/fixtures/expectProxy.d.ts.map +1 -1
  18. package/dist/fixtures/expectProxy.js +7 -0
  19. package/dist/fixtures/index.d.ts +7 -1
  20. package/dist/fixtures/index.d.ts.map +1 -1
  21. package/dist/fixtures/index.js +15 -0
  22. package/dist/fixtures/proxyTag.d.ts +12 -0
  23. package/dist/fixtures/proxyTag.d.ts.map +1 -0
  24. package/dist/fixtures/proxyTag.js +45 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +6 -1
  28. package/dist/reporter/SelaReporter.d.ts +110 -0
  29. package/dist/reporter/SelaReporter.d.ts.map +1 -0
  30. package/dist/reporter/SelaReporter.js +328 -0
  31. package/dist/services/ASTSourceUpdater.d.ts +19 -0
  32. package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
  33. package/dist/services/ASTSourceUpdater.js +173 -29
  34. package/dist/services/HealReportService.d.ts +42 -0
  35. package/dist/services/HealReportService.d.ts.map +1 -1
  36. package/dist/services/HealReportService.js +80 -1
  37. package/dist/services/HealingCacheService.d.ts +135 -0
  38. package/dist/services/HealingCacheService.d.ts.map +1 -0
  39. package/dist/services/HealingCacheService.js +460 -0
  40. package/dist/services/InitializerUpdater.d.ts.map +1 -1
  41. package/dist/services/InitializerUpdater.js +20 -1
  42. package/dist/services/IntentAuditor.d.ts +18 -1
  43. package/dist/services/IntentAuditor.d.ts.map +1 -1
  44. package/dist/services/IntentAuditor.js +19 -6
  45. package/dist/services/InteractiveReview.d.ts +24 -0
  46. package/dist/services/InteractiveReview.d.ts.map +1 -0
  47. package/dist/services/InteractiveReview.js +218 -0
  48. package/dist/services/LLMService.d.ts +17 -1
  49. package/dist/services/LLMService.d.ts.map +1 -1
  50. package/dist/services/LLMService.js +18 -8
  51. package/dist/services/PRAutomationService.d.ts +14 -1
  52. package/dist/services/PRAutomationService.d.ts.map +1 -1
  53. package/dist/services/PRAutomationService.js +260 -69
  54. package/dist/services/PendingPromptLedger.d.ts +44 -0
  55. package/dist/services/PendingPromptLedger.d.ts.map +1 -0
  56. package/dist/services/PendingPromptLedger.js +180 -0
  57. package/dist/services/ReportGenerator.d.ts +70 -0
  58. package/dist/services/ReportGenerator.d.ts.map +1 -0
  59. package/dist/services/ReportGenerator.js +191 -0
  60. package/dist/services/SafetyGuard.d.ts +27 -1
  61. package/dist/services/SafetyGuard.d.ts.map +1 -1
  62. package/dist/services/SafetyGuard.js +58 -12
  63. package/dist/services/SourceUpdater.d.ts.map +1 -1
  64. package/dist/services/SourceUpdater.js +44 -13
  65. package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
  66. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
  67. package/dist/services/WorkspaceSnapshotService.js +202 -0
  68. package/dist/utils/IsolatedDiff.d.ts +45 -0
  69. package/dist/utils/IsolatedDiff.d.ts.map +1 -0
  70. package/dist/utils/IsolatedDiff.js +379 -0
  71. package/package.json +71 -67
@@ -95,14 +95,27 @@ RESPONSE FORMAT (strict JSON, no other text):
95
95
  "reason": "<one sentence explaining the verdict>",
96
96
  "inversionType": "polarity" | "quantitative" | "state" | "identity" | null
97
97
  }`;
98
- // ═══════════════════════════════════════════════════════════════════
99
- // IntentAuditor
100
- // ═══════════════════════════════════════════════════════════════════
98
+ const DEFAULT_AUDITOR_MODEL = "claude-haiku-4-5-20251001";
101
99
  class IntentAuditor {
102
100
  anthropic;
103
101
  disabled;
104
- constructor() {
105
- const apiKey = process.env.ANTHROPIC_API_KEY;
102
+ model;
103
+ constructor(opts = {}) {
104
+ this.model = opts.model ?? DEFAULT_AUDITOR_MODEL;
105
+ // Explicit `null` from the caller forces disabled mode — used by tests.
106
+ if (opts.anthropic === null) {
107
+ this.anthropic = null;
108
+ this.disabled = true;
109
+ return;
110
+ }
111
+ // Caller supplied a ready-to-use client → use it as-is.
112
+ if (opts.anthropic !== undefined) {
113
+ this.anthropic = opts.anthropic;
114
+ this.disabled = false;
115
+ return;
116
+ }
117
+ // Otherwise fall back to env-based construction (legacy default).
118
+ const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
106
119
  if (!apiKey) {
107
120
  console.warn("[IntentAuditor] ANTHROPIC_API_KEY not set — auditor disabled, will return SUSPICIOUS for all audited heals");
108
121
  this.anthropic = null;
@@ -128,7 +141,7 @@ class IntentAuditor {
128
141
  .join("\n");
129
142
  try {
130
143
  const response = await this.anthropic.messages.create({
131
- model: "claude-haiku-4-5-20251001",
144
+ model: this.model,
132
145
  max_tokens: 256,
133
146
  temperature: 0.1,
134
147
  system: SYSTEM_PROMPT,
@@ -0,0 +1,24 @@
1
+ import type { PendingPromptEntry } from "./PendingPromptLedger";
2
+ export interface InteractiveReviewDeps {
3
+ entries: PendingPromptEntry[];
4
+ /** Override the prompt — tests stub Y/N answers without spinning up readline. */
5
+ prompt?: (question: string) => Promise<string>;
6
+ /** Override the console sink — tests capture output for assertions. */
7
+ log?: (msg: string) => void;
8
+ /** Override the filesystem rollback (rewrites contentBefore). Tests stub. */
9
+ rollback?: (entry: PendingPromptEntry) => void;
10
+ }
11
+ export interface InteractiveReviewOutcome {
12
+ file: string;
13
+ applied: boolean;
14
+ }
15
+ /**
16
+ * Run the post-run interactive review loop sequentially across every
17
+ * pending entry. Returns one outcome per entry so callers can log / test
18
+ * the resulting Apply / Reject decisions.
19
+ *
20
+ * Sequential by design — every prompt `await`s before the next entry is
21
+ * rendered. Concurrent prompts on a single TTY would interleave output.
22
+ */
23
+ export declare function runInteractiveReview(deps: InteractiveReviewDeps): Promise<InteractiveReviewOutcome[]>;
24
+ //# sourceMappingURL=InteractiveReview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InteractiveReview.d.ts","sourceRoot":"","sources":["../../src/services/InteractiveReview.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,uEAAuE;IACvE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAID;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,wBAAwB,EAAE,CAAC,CA+CrC"}
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ // src/services/InteractiveReview.ts
3
+ //
4
+ // Local DX — Interactive CLI review of post-run heals.
5
+ //
6
+ // Runs in the REPORTER (main process) only — workers must never block on
7
+ // stdin. The reporter reads cross-process state from
8
+ // `PendingPromptLedger` and calls `runInteractiveReview()` with the
9
+ // merged entry list.
10
+ //
11
+ // For every entry: prints `[Sela] 🔧 Healed selector in <FileName>`, the
12
+ // isolated unified diff, a clickable `vscode://file/<abs>:<line>`
13
+ // deeplink, then prompts the developer. 'Y' / 'y' / Enter keeps the
14
+ // on-disk fix (the worker already wrote it); 'N' / 'n' rewrites the file
15
+ // back to `contentBefore` (the pre-heal state captured by
16
+ // `WorkspaceSnapshotService.preWrite()` in the worker).
17
+ //
18
+ // All I/O is injectable so this can be unit-tested without a real TTY.
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ var __importDefault = (this && this.__importDefault) || function (mod) {
53
+ return (mod && mod.__esModule) ? mod : { "default": mod };
54
+ };
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.runInteractiveReview = runInteractiveReview;
57
+ const fs = __importStar(require("fs"));
58
+ const path = __importStar(require("path"));
59
+ const readline = __importStar(require("readline"));
60
+ const chalk_1 = __importDefault(require("chalk"));
61
+ const IsolatedDiff_1 = require("../utils/IsolatedDiff");
62
+ const DEFAULT_PROMPT = "[Sela] Apply this fix? (Y/n): ";
63
+ /**
64
+ * Run the post-run interactive review loop sequentially across every
65
+ * pending entry. Returns one outcome per entry so callers can log / test
66
+ * the resulting Apply / Reject decisions.
67
+ *
68
+ * Sequential by design — every prompt `await`s before the next entry is
69
+ * rendered. Concurrent prompts on a single TTY would interleave output.
70
+ */
71
+ async function runInteractiveReview(deps) {
72
+ const log = deps.log ?? ((m) => process.stdout.write(m + "\n"));
73
+ const prompt = deps.prompt ?? defaultPrompt;
74
+ const rollback = deps.rollback ?? defaultRollback;
75
+ const entries = deps.entries ?? [];
76
+ const outcomes = [];
77
+ if (entries.length === 0)
78
+ return outcomes;
79
+ // Headline banner — surfaces even when downstream chalk/readline
80
+ // output is suppressed by the host terminal multiplexer (e.g.
81
+ // Playwright's reporter-level stdio interception).
82
+ log("");
83
+ log(chalk_1.default.bold.bgCyan.black(` [Sela] Local DX Review — ${entries.length} pending fix(es) `));
84
+ for (const entry of entries) {
85
+ renderEntry(entry, log);
86
+ const answer = await prompt(chalk_1.default.cyan(DEFAULT_PROMPT));
87
+ const decision = parseDecision(answer);
88
+ if (decision === "apply") {
89
+ log(chalk_1.default.green(`[Sela] ✅ Fix kept on disk: ${entry.relativePath}`));
90
+ outcomes.push({ file: entry.absolutePath, applied: true });
91
+ }
92
+ else {
93
+ try {
94
+ rollback(entry);
95
+ log(chalk_1.default.yellow(`[Sela] ↩️ Rolled back: ${entry.relativePath} (restored to pre-heal state)`));
96
+ }
97
+ catch (err) {
98
+ log(chalk_1.default.red(`[Sela] ⚠️ Rollback failed for ${entry.relativePath}: ${err.message}`));
99
+ }
100
+ outcomes.push({ file: entry.absolutePath, applied: false });
101
+ }
102
+ }
103
+ log("");
104
+ return outcomes;
105
+ }
106
+ function renderEntry(entry, log) {
107
+ const fileName = path.basename(entry.absolutePath);
108
+ const diff = (0, IsolatedDiff_1.generateUnifiedDiff)(entry.contentBefore, entry.contentAfter, {
109
+ filePath: entry.relativePath,
110
+ });
111
+ log("");
112
+ log(chalk_1.default.bold(`[Sela] 🔧 Healed selector in ${fileName}`));
113
+ log("");
114
+ log(colorizeDiff(diff));
115
+ log("");
116
+ log(`👉 To review and accept this fix directly in VS Code, click here:`);
117
+ log(` ${chalk_1.default.cyan(buildVsCodeLink(entry.absolutePath, entry.lineNumber))}`);
118
+ log("");
119
+ }
120
+ function colorizeDiff(diff) {
121
+ if (!diff)
122
+ return chalk_1.default.dim("(no diff)");
123
+ return diff
124
+ .split("\n")
125
+ .map((row) => {
126
+ if (row.startsWith("+++") ||
127
+ row.startsWith("---") ||
128
+ row.startsWith("diff --git")) {
129
+ return chalk_1.default.bold(row);
130
+ }
131
+ if (row.startsWith("@@"))
132
+ return chalk_1.default.cyan(row);
133
+ if (row.startsWith("+"))
134
+ return chalk_1.default.green(row);
135
+ if (row.startsWith("-"))
136
+ return chalk_1.default.red(row);
137
+ return row;
138
+ })
139
+ .join("\n");
140
+ }
141
+ function buildVsCodeLink(absPath, line) {
142
+ const normalised = absPath.split(path.sep).join("/");
143
+ return line && line > 0
144
+ ? `vscode://file/${normalised}:${line}`
145
+ : `vscode://file/${normalised}`;
146
+ }
147
+ function parseDecision(raw) {
148
+ const trimmed = (raw ?? "").trim().toLowerCase();
149
+ // Empty (Enter) ⇒ apply. 'y' / 'yes' ⇒ apply. 'n' / 'no' ⇒ reject.
150
+ // Anything else ⇒ apply (default-safe — never silently destroy work).
151
+ if (trimmed === "" || trimmed === "y" || trimmed === "yes")
152
+ return "apply";
153
+ if (trimmed === "n" || trimmed === "no")
154
+ return "reject";
155
+ return "apply";
156
+ }
157
+ /**
158
+ * Default rollback — rewrites `contentBefore` to disk. Honours the
159
+ * `createdBySela` flag so files Sela CREATED from nothing get unlinked
160
+ * instead of being replaced with an empty buffer.
161
+ */
162
+ function defaultRollback(entry) {
163
+ if (entry.createdBySela) {
164
+ if (fs.existsSync(entry.absolutePath)) {
165
+ fs.unlinkSync(entry.absolutePath);
166
+ }
167
+ return;
168
+ }
169
+ fs.writeFileSync(entry.absolutePath, entry.contentBefore, "utf8");
170
+ }
171
+ /**
172
+ * Bind `readline` to the live stdin/stdout. Reporter-only — workers must
173
+ * never reach this code path (they have no TTY and would deadlock).
174
+ *
175
+ * Hardening:
176
+ * • If stdin is not a TTY (CI, non-interactive shell, Playwright reporter
177
+ * context that detached stdio), default to Apply ('y') without blocking.
178
+ * • Explicitly `resume()` stdin — Node pauses it by default once any other
179
+ * consumer (readline-from-an-earlier-iteration, raw-mode toggles) lets go.
180
+ * • Write the prompt to `stderr` so Playwright's stdout summary buffer does
181
+ * not overwrite or strip the question line.
182
+ */
183
+ function defaultPrompt(question) {
184
+ const stdinIsTty = process.stdin.isTTY === true;
185
+ if (!stdinIsTty) {
186
+ process.stderr.write(question + "y (no TTY — auto-apply)\n");
187
+ return Promise.resolve("y");
188
+ }
189
+ return new Promise((resolve) => {
190
+ try {
191
+ process.stdin.resume();
192
+ }
193
+ catch {
194
+ // Already resumed / not resumable — readline will still try.
195
+ }
196
+ // `terminal: false` reads stdin line-buffered without raw-mode tricks.
197
+ // This is reliable for both real TTYs AND piped harnesses (CI smoke
198
+ // tests, child-process drivers). `terminal: true` only adds history /
199
+ // arrow-key support, which we don't need for a single Y/N prompt and
200
+ // which mis-parses piped input on Windows.
201
+ process.stderr.write(question);
202
+ const rl = readline.createInterface({
203
+ input: process.stdin,
204
+ output: process.stderr,
205
+ terminal: false,
206
+ });
207
+ let settled = false;
208
+ const finalise = (answer) => {
209
+ if (settled)
210
+ return;
211
+ settled = true;
212
+ rl.close();
213
+ resolve(answer);
214
+ };
215
+ rl.once("line", (line) => finalise(line));
216
+ rl.once("close", () => finalise("y"));
217
+ });
218
+ }
@@ -1,3 +1,4 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
1
2
  import { FixRequest } from "../types";
2
3
  export interface SelectorSegment {
3
4
  type: "frame" | "element";
@@ -86,9 +87,24 @@ export interface FixResponse {
86
87
  new_selector?: string | null;
87
88
  contentChange?: ContentChange;
88
89
  }
90
+ /**
91
+ * Constructor options for LLMService.
92
+ *
93
+ * - `anthropic`: pre-built Anthropic client. Tests use this to inject a
94
+ * mock client without touching env vars or network.
95
+ * - `apiKey`: override the env-based ANTHROPIC_API_KEY when constructing
96
+ * the default client.
97
+ * - `model`: override the Sonnet model id (lets tests pin the SDK call).
98
+ */
99
+ export interface LLMServiceOptions {
100
+ anthropic?: Anthropic;
101
+ apiKey?: string;
102
+ model?: string;
103
+ }
89
104
  export declare class LLMService {
90
105
  private anthropic;
91
- constructor();
106
+ private readonly model;
107
+ constructor(opts?: LLMServiceOptions);
92
108
  private cleanJson;
93
109
  /**
94
110
  * Attempts to infer contentChange when the AI did not return the field.
@@ -1 +1 @@
1
- {"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.LLMService = void 0;
40
40
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
41
41
  const dotenv = __importStar(require("dotenv"));
42
+ const SelaError_1 = require("../errors/SelaError");
42
43
  dotenv.config();
43
44
  // ─────────────────────────────────────────────────────────────────
44
45
  // SYSTEM PROMPT
@@ -199,15 +200,24 @@ function inferOriginalChainHint(selector) {
199
200
  }
200
201
  return segments;
201
202
  }
202
- // ─────────────────────────────────────────────────────────────────
203
- // LLM SERVICE
204
- // ─────────────────────────────────────────────────────────────────
203
+ const DEFAULT_LLM_MODEL = "claude-sonnet-4-20250514";
205
204
  class LLMService {
206
205
  anthropic;
207
- constructor() {
208
- const apiKey = process.env.ANTHROPIC_API_KEY || "";
209
- if (!apiKey)
210
- throw new Error("ANTHROPIC_API_KEY not found in .env");
206
+ model;
207
+ constructor(opts = {}) {
208
+ this.model = opts.model ?? DEFAULT_LLM_MODEL;
209
+ if (opts.anthropic) {
210
+ this.anthropic = opts.anthropic;
211
+ return;
212
+ }
213
+ const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
214
+ if (!apiKey) {
215
+ throw new SelaError_1.SelaError({
216
+ subsystem: "LLMService",
217
+ code: SelaError_1.LLM_SERVICE_CODES.NO_API_KEY,
218
+ reason: "ANTHROPIC_API_KEY not found — pass an `anthropic` client or `apiKey` to LLMService(), or set the env var",
219
+ });
220
+ }
211
221
  this.anthropic = new sdk_1.default({ apiKey });
212
222
  }
213
223
  cleanJson(text) {
@@ -344,7 +354,7 @@ ${request.currentDom}
344
354
  Return ONLY a raw JSON object — no markdown, no preamble.`;
345
355
  try {
346
356
  const msg = await this.anthropic.messages.create({
347
- model: "claude-sonnet-4-20250514",
357
+ model: this.model,
348
358
  max_tokens: 1024,
349
359
  system: SYSTEM_PROMPT_TEXT +
350
360
  "\nIMPORTANT: Respond ONLY with a raw JSON object.",
@@ -1,5 +1,6 @@
1
1
  import type { ResolvedPRAutomation, PRStrategy, BugAction } from "../config/SelaConfig";
2
2
  import type { HealedEvent, ProtectedEvent } from "./HealReportService";
3
+ import { type WorkspaceSnapshotService } from "./WorkspaceSnapshotService";
3
4
  export interface BranchInfo {
4
5
  branch: string | null;
5
6
  isPR: boolean;
@@ -32,6 +33,18 @@ export interface ExecutionResult {
32
33
  branchPushed: string | null;
33
34
  errors: string[];
34
35
  }
35
- export declare function execute(cfg: ResolvedPRAutomation, decision: StrategyDecision, heals: HealedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext): Promise<ExecutionResult>;
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>;
36
49
  export declare function handleBugDetected(action: BugAction, events: ProtectedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext): Promise<void>;
37
50
  //# sourceMappingURL=PRAutomationService.d.ts.map
@@ -1 +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,EACV,WAAW,EACX,cAAc,EACf,MAAM,qBAAqB,CAAC;AAM7B,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;AA2ED,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CA8EpE;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;AAuFD,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,wBAAsB,OAAO,CAC3B,GAAG,EAAE,oBAAoB,EACzB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,eAAe,CAAC,CA4J1B;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CA0Ff"}
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,EACV,WAAW,EACX,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,4BAA4B,CAAC;AAUpC,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;AA2ED,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CA8EpE;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;AAuFD,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;AA4SD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CA0Ff"}