sela-core 1.0.7 → 1.0.9

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 (146) hide show
  1. package/README.md +27 -17
  2. package/dist/cli/ErrorHandler.d.ts +1 -1
  3. package/dist/cli/ErrorHandler.js +14 -14
  4. package/dist/cli/commands/bulk.d.ts +1 -1
  5. package/dist/cli/commands/bulk.d.ts.map +1 -1
  6. package/dist/cli/commands/bulk.js +51 -33
  7. package/dist/cli/commands/init.d.ts +1 -1
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +26 -18
  10. package/dist/cli/commands/merge.d.ts +13 -0
  11. package/dist/cli/commands/merge.d.ts.map +1 -0
  12. package/dist/cli/commands/merge.js +119 -0
  13. package/dist/cli/commands/showReport.d.ts +1 -1
  14. package/dist/cli/commands/showReport.d.ts.map +1 -1
  15. package/dist/cli/commands/showReport.js +12 -10
  16. package/dist/cli/commands/status.d.ts +1 -1
  17. package/dist/cli/commands/status.d.ts.map +1 -1
  18. package/dist/cli/commands/status.js +8 -8
  19. package/dist/cli/commands/sync.d.ts +1 -1
  20. package/dist/cli/commands/sync.d.ts.map +1 -1
  21. package/dist/cli/commands/sync.js +38 -30
  22. package/dist/cli/index.js +12 -9
  23. package/dist/cli/ui/DnaTable.d.ts +1 -1
  24. package/dist/cli/ui/DnaTable.d.ts.map +1 -1
  25. package/dist/cli/ui/DnaTable.js +23 -16
  26. package/dist/cli/ui/RefactorWizard.d.ts +3 -3
  27. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
  28. package/dist/cli/ui/RefactorWizard.js +108 -82
  29. package/dist/config/ConfigLoader.d.ts +2 -2
  30. package/dist/config/ConfigLoader.d.ts.map +1 -1
  31. package/dist/config/ConfigLoader.js +15 -15
  32. package/dist/config/DryRunGuard.d.ts +1 -1
  33. package/dist/config/DryRunGuard.js +7 -7
  34. package/dist/config/SelaConfig.d.ts +13 -17
  35. package/dist/config/SelaConfig.d.ts.map +1 -1
  36. package/dist/config/SelaConfig.js +32 -26
  37. package/dist/engine/HealingRegistry.d.ts +1 -1
  38. package/dist/engine/HealingRegistry.js +3 -3
  39. package/dist/engine/SelaEngine.d.ts +1 -1
  40. package/dist/engine/SelaEngine.d.ts.map +1 -1
  41. package/dist/engine/SelaEngine.js +117 -51
  42. package/dist/errors/SelaError.d.ts +8 -1
  43. package/dist/errors/SelaError.d.ts.map +1 -1
  44. package/dist/errors/SelaError.js +14 -7
  45. package/dist/fixtures/expectProxy.d.ts +2 -2
  46. package/dist/fixtures/expectProxy.js +16 -16
  47. package/dist/fixtures/index.d.ts +18 -2
  48. package/dist/fixtures/index.d.ts.map +1 -1
  49. package/dist/fixtures/index.js +47 -27
  50. package/dist/fixtures/moduleExpect.js +7 -7
  51. package/dist/fixtures/proxyTag.d.ts +1 -1
  52. package/dist/fixtures/proxyTag.js +4 -4
  53. package/dist/index.d.ts +8 -8
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +4 -4
  56. package/dist/reporter/SelaReporter.d.ts +45 -12
  57. package/dist/reporter/SelaReporter.d.ts.map +1 -1
  58. package/dist/reporter/SelaReporter.js +139 -39
  59. package/dist/services/ASTSourceUpdater.d.ts +48 -3
  60. package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
  61. package/dist/services/ASTSourceUpdater.js +283 -105
  62. package/dist/services/AnchorResolver.d.ts +157 -0
  63. package/dist/services/AnchorResolver.d.ts.map +1 -0
  64. package/dist/services/AnchorResolver.js +289 -0
  65. package/dist/services/ArgumentTypeAnalyzer.js +7 -7
  66. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
  67. package/dist/services/BlastRadiusAnalyzer.js +5 -2
  68. package/dist/services/ChainValidator.js +22 -22
  69. package/dist/services/CrossFileHealer.js +7 -7
  70. package/dist/services/DecisionEngine.d.ts +51 -0
  71. package/dist/services/DecisionEngine.d.ts.map +1 -0
  72. package/dist/services/DecisionEngine.js +260 -0
  73. package/dist/services/DefinitionTracer.d.ts +1 -1
  74. package/dist/services/DefinitionTracer.d.ts.map +1 -1
  75. package/dist/services/DefinitionTracer.js +28 -13
  76. package/dist/services/DnaEditorService.d.ts +2 -2
  77. package/dist/services/DnaEditorService.d.ts.map +1 -1
  78. package/dist/services/DnaEditorService.js +81 -62
  79. package/dist/services/DnaIndexService.d.ts +1 -1
  80. package/dist/services/DnaIndexService.d.ts.map +1 -1
  81. package/dist/services/DnaIndexService.js +14 -14
  82. package/dist/services/HealReportService.d.ts +31 -55
  83. package/dist/services/HealReportService.d.ts.map +1 -1
  84. package/dist/services/HealReportService.js +864 -1240
  85. package/dist/services/HealingAdvisory.d.ts +9 -1
  86. package/dist/services/HealingAdvisory.d.ts.map +1 -1
  87. package/dist/services/HealingAdvisory.js +9 -1
  88. package/dist/services/HealingCacheService.d.ts +8 -8
  89. package/dist/services/HealingCacheService.d.ts.map +1 -1
  90. package/dist/services/HealingCacheService.js +24 -24
  91. package/dist/services/HealthReportService.d.ts +1 -1
  92. package/dist/services/HealthReportService.d.ts.map +1 -1
  93. package/dist/services/HealthReportService.js +25 -25
  94. package/dist/services/InitializerUpdater.d.ts.map +1 -1
  95. package/dist/services/InitializerUpdater.js +12 -2
  96. package/dist/services/IntentAuditor.d.ts.map +1 -1
  97. package/dist/services/IntentAuditor.js +32 -21
  98. package/dist/services/InteractiveReview.d.ts +8 -17
  99. package/dist/services/InteractiveReview.d.ts.map +1 -1
  100. package/dist/services/InteractiveReview.js +34 -124
  101. package/dist/services/LLMService.d.ts +1 -1
  102. package/dist/services/LLMService.js +16 -16
  103. package/dist/services/MutationApplier.d.ts +55 -0
  104. package/dist/services/MutationApplier.d.ts.map +1 -0
  105. package/dist/services/MutationApplier.js +322 -0
  106. package/dist/services/PRAutomationService.d.ts +2 -2
  107. package/dist/services/PRAutomationService.d.ts.map +1 -1
  108. package/dist/services/PRAutomationService.js +43 -39
  109. package/dist/services/PendingPromptLedger.d.ts +9 -2
  110. package/dist/services/PendingPromptLedger.d.ts.map +1 -1
  111. package/dist/services/PendingPromptLedger.js +31 -6
  112. package/dist/services/ReportGenerator.d.ts +116 -30
  113. package/dist/services/ReportGenerator.d.ts.map +1 -1
  114. package/dist/services/ReportGenerator.js +149 -62
  115. package/dist/services/ReportMergeService.d.ts +95 -0
  116. package/dist/services/ReportMergeService.d.ts.map +1 -0
  117. package/dist/services/ReportMergeService.js +0 -0
  118. package/dist/services/SafetyGuard.d.ts +4 -7
  119. package/dist/services/SafetyGuard.d.ts.map +1 -1
  120. package/dist/services/SafetyGuard.js +29 -106
  121. package/dist/services/SelectorSanitizer.d.ts +52 -0
  122. package/dist/services/SelectorSanitizer.d.ts.map +1 -0
  123. package/dist/services/SelectorSanitizer.js +318 -0
  124. package/dist/services/SnapshotService.js +6 -6
  125. package/dist/services/SourceLinkService.d.ts +2 -2
  126. package/dist/services/SourceLinkService.d.ts.map +1 -1
  127. package/dist/services/SourceLinkService.js +31 -22
  128. package/dist/services/SourceUpdater.d.ts +1 -1
  129. package/dist/services/SourceUpdater.d.ts.map +1 -1
  130. package/dist/services/SourceUpdater.js +44 -26
  131. package/dist/services/TemplateDiffService.d.ts.map +1 -1
  132. package/dist/services/TemplateDiffService.js +18 -15
  133. package/dist/services/TraceBackEngine.d.ts +67 -0
  134. package/dist/services/TraceBackEngine.d.ts.map +1 -0
  135. package/dist/services/TraceBackEngine.js +672 -0
  136. package/dist/services/WorkspaceSnapshotService.d.ts +6 -6
  137. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -1
  138. package/dist/services/WorkspaceSnapshotService.js +10 -12
  139. package/dist/types/index.d.ts +1 -1
  140. package/dist/utils/DOMUtils.d.ts +18 -2
  141. package/dist/utils/DOMUtils.d.ts.map +1 -1
  142. package/dist/utils/DOMUtils.js +335 -49
  143. package/dist/utils/IsolatedDiff.d.ts +2 -2
  144. package/dist/utils/IsolatedDiff.d.ts.map +1 -1
  145. package/dist/utils/IsolatedDiff.js +13 -10
  146. package/package.json +2 -2
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ // src/services/MutationApplier.ts
3
+ //
4
+ // Ticket #2 — Stage 4: Write-Discipline & the Drain Loop.
5
+ //
6
+ // Applies MutationPlans to disk under three hard contracts:
7
+ //
8
+ // 1. ZERO LAYOUT NOISE (§4.1) — surgical string splice over the EXACT node
9
+ // span [getStart(), getEnd()). getStart() excludes leading trivia, so
10
+ // comments / indentation / Prettier layout outside the span are byte-
11
+ // identical. We never call sourceFile.formatText() / ts.Printer.
12
+ //
13
+ // 2. TYPE-SAFETY GATE + ROLLBACK (§4.3) — after splicing in memory we
14
+ // re-parse and baseline-diff the diagnostics. A mutation that introduces
15
+ // a NEW real error (syntax break, non-Locator return) is rolled back and
16
+ // the file is left byte-for-byte untouched. A cheap Locator-shape pre-
17
+ // check rejects obviously wrong return types before the splice.
18
+ //
19
+ // 3. BATCH APPLICATION (§3.3) — mutations are grouped by file, anchors are
20
+ // RE-RESOLVED against the fresh parse (never trusting flag-time offsets),
21
+ // edits applied highest-offset-first, then the file is written atomically
22
+ // (temp + rename).
23
+ //
24
+ // Stage 1 (resolveAnchor) provides line-shift-immune relocation; Stage-1
25
+ // applyEdits provides the reverse-offset splice + overlap guard.
26
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ var desc = Object.getOwnPropertyDescriptor(m, k);
29
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
30
+ desc = { enumerable: true, get: function() { return m[k]; } };
31
+ }
32
+ Object.defineProperty(o, k2, desc);
33
+ }) : (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ o[k2] = m[k];
36
+ }));
37
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
38
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
39
+ }) : function(o, v) {
40
+ o["default"] = v;
41
+ });
42
+ var __importStar = (this && this.__importStar) || (function () {
43
+ var ownKeys = function(o) {
44
+ ownKeys = Object.getOwnPropertyNames || function (o) {
45
+ var ar = [];
46
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
+ return ar;
48
+ };
49
+ return ownKeys(o);
50
+ };
51
+ return function (mod) {
52
+ if (mod && mod.__esModule) return mod;
53
+ var result = {};
54
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
+ __setModuleDefault(result, mod);
56
+ return result;
57
+ };
58
+ })();
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.MutationApplier = void 0;
61
+ exports.isLocatorReturningExpr = isLocatorReturningExpr;
62
+ const fs = __importStar(require("fs"));
63
+ const ts_morph_1 = require("ts-morph");
64
+ const AnchorResolver_1 = require("./AnchorResolver");
65
+ const DecisionEngine_1 = require("./DecisionEngine");
66
+ const TraceBackEngine_1 = require("./TraceBackEngine");
67
+ const SelectorSanitizer_1 = require("./SelectorSanitizer");
68
+ // Diagnostic codes that are pure "missing types" noise in an in-memory parse
69
+ // without node_modules — identical allowlist to ASTSourceUpdater's validator.
70
+ // Syntax/binder errors (codes 1000–1999) are NEVER allowlisted.
71
+ const NOISE_CODES = new Set([
72
+ 2304, 2339, 2345, 2554, 2571, 2580, 2503, 7006, 7016, 7031, 18004,
73
+ ]);
74
+ const LOCATOR_RETURNING_CATEGORIES = new Set(["selector", "frame", "modifier"]);
75
+ // ═══════════════════════════════════════════════════════════════════
76
+ // MutationApplier
77
+ // ═══════════════════════════════════════════════════════════════════
78
+ class MutationApplier {
79
+ /** Apply many batches; each file is processed independently. */
80
+ static applyBatches(batches, opts = {}) {
81
+ return batches.map((b) => MutationApplier.applyFileBatch(b, opts));
82
+ }
83
+ static applyFileBatch(batch, opts = {}) {
84
+ const write = opts.write ?? true;
85
+ const respectDryRun = opts.respectDryRun ?? true;
86
+ const dryRun = respectDryRun && process.env.SELA_DRY_RUN === "1";
87
+ const { filePath } = batch;
88
+ const skipped = [];
89
+ let original;
90
+ try {
91
+ original = fs.readFileSync(filePath, "utf8");
92
+ }
93
+ catch (e) {
94
+ return {
95
+ filePath,
96
+ status: "skipped",
97
+ reason: `cannot read file: ${e.message}`,
98
+ written: false,
99
+ finalContent: "",
100
+ skipped,
101
+ };
102
+ }
103
+ // Fresh parse — the single source of truth for span resolution.
104
+ const project = newGateProject();
105
+ const sf = project.createSourceFile(filePath, original, { overwrite: true });
106
+ const baseline = realDiagnosticCounts(sf);
107
+ // ── Resolve each edit to a concrete span ─────────────────────────
108
+ const concrete = [];
109
+ for (const e of batch.edits) {
110
+ let replacement = e.replacement;
111
+ // ABSOLUTE GUARD: translate any internal engine selector to its public
112
+ // method form before writing; refuse if it cannot be translated. No
113
+ // `internal:role=…` string ever reaches the developer's source.
114
+ if (e.kind === "replace") {
115
+ const san = (0, SelectorSanitizer_1.sanitizeLocatorExpression)(replacement);
116
+ if (!san.safe) {
117
+ skipped.push({
118
+ reason: `internal-engine-selector: cannot translate "${truncate(replacement)}" to a public Playwright method`,
119
+ });
120
+ continue;
121
+ }
122
+ replacement = san.text;
123
+ }
124
+ const requireLocator = e.requireLocator ?? e.kind === "replace";
125
+ if (requireLocator && !isLocatorReturningExpr(replacement)) {
126
+ skipped.push({
127
+ reason: `type-incompat: replacement is not a Locator expression ("${truncate(replacement)}")`,
128
+ });
129
+ continue;
130
+ }
131
+ if (e.kind === "replace" && e.anchor) {
132
+ const r = (0, AnchorResolver_1.resolveAnchor)(sf, e.anchor);
133
+ if (r.status !== "resolved") {
134
+ skipped.push({
135
+ reason: `anchor ${r.status}: ${"reason" in r ? r.reason : ""}`,
136
+ });
137
+ continue;
138
+ }
139
+ concrete.push({
140
+ kind: "replace",
141
+ span: { start: r.node.getStart(), end: r.node.getEnd() },
142
+ replacement,
143
+ });
144
+ }
145
+ else if (e.span) {
146
+ concrete.push({
147
+ kind: e.kind,
148
+ span: e.span,
149
+ replacement,
150
+ });
151
+ }
152
+ else {
153
+ skipped.push({ reason: "edit has neither anchor nor span" });
154
+ }
155
+ }
156
+ if (concrete.length === 0) {
157
+ return {
158
+ filePath,
159
+ status: "no-op",
160
+ reason: "no applicable edits after resolution",
161
+ written: false,
162
+ finalContent: original,
163
+ skipped,
164
+ };
165
+ }
166
+ // ── Splice (reverse-offset; overlap-guarded) ─────────────────────
167
+ let newContent;
168
+ try {
169
+ newContent = (0, DecisionEngine_1.applyEdits)(original, concrete);
170
+ }
171
+ catch (e) {
172
+ return {
173
+ filePath,
174
+ status: "rolled-back",
175
+ reason: `splice failed: ${e.message}`,
176
+ written: false,
177
+ finalContent: original,
178
+ skipped,
179
+ };
180
+ }
181
+ if (newContent === original) {
182
+ return {
183
+ filePath,
184
+ status: "no-op",
185
+ reason: "edits produced identical content",
186
+ written: false,
187
+ finalContent: original,
188
+ skipped,
189
+ };
190
+ }
191
+ // ── Type-safety gate: baseline-diff diagnostics ──────────────────
192
+ const afterSf = project.createSourceFile(filePath, newContent, {
193
+ overwrite: true,
194
+ });
195
+ const after = realDiagnosticCounts(afterSf);
196
+ const regression = diagnosticRegression(baseline, after);
197
+ if (regression) {
198
+ return {
199
+ filePath,
200
+ status: "rolled-back",
201
+ reason: `type-safety gate: mutation introduced a new error (${regression}) — file left untouched`,
202
+ written: false,
203
+ finalContent: original,
204
+ skipped,
205
+ };
206
+ }
207
+ // ── Atomic write ─────────────────────────────────────────────────
208
+ if (write && !dryRun) {
209
+ try {
210
+ atomicWrite(filePath, newContent);
211
+ }
212
+ catch (e) {
213
+ return {
214
+ filePath,
215
+ status: "rolled-back",
216
+ reason: `atomic write failed: ${e.message}`,
217
+ written: false,
218
+ finalContent: original,
219
+ skipped,
220
+ };
221
+ }
222
+ }
223
+ return {
224
+ filePath,
225
+ status: "applied",
226
+ reason: `${concrete.length} edit(s) applied${dryRun ? " (dry-run, not written)" : ""}`,
227
+ written: write && !dryRun,
228
+ finalContent: newContent,
229
+ skipped,
230
+ };
231
+ }
232
+ }
233
+ exports.MutationApplier = MutationApplier;
234
+ // ═══════════════════════════════════════════════════════════════════
235
+ // TYPE-SAFETY HELPERS
236
+ // ═══════════════════════════════════════════════════════════════════
237
+ function newGateProject() {
238
+ return new ts_morph_1.Project({
239
+ useInMemoryFileSystem: true,
240
+ compilerOptions: {
241
+ allowJs: true,
242
+ noEmit: true,
243
+ skipLibCheck: true,
244
+ noResolve: true,
245
+ },
246
+ });
247
+ }
248
+ /** Map of real (non-noise) diagnostic code → count. */
249
+ function realDiagnosticCounts(sf) {
250
+ const counts = new Map();
251
+ for (const d of sf.getPreEmitDiagnostics()) {
252
+ if (d.getCategory() !== 1 /* Error */)
253
+ continue;
254
+ const code = d.getCode();
255
+ if (NOISE_CODES.has(code))
256
+ continue;
257
+ counts.set(code, (counts.get(code) ?? 0) + 1);
258
+ }
259
+ return counts;
260
+ }
261
+ /** Returns a description of the first NEW/elevated error, or null. */
262
+ function diagnosticRegression(baseline, after) {
263
+ for (const [code, n] of after) {
264
+ if (n > (baseline.get(code) ?? 0)) {
265
+ return `TS${code}`;
266
+ }
267
+ }
268
+ return null;
269
+ }
270
+ /**
271
+ * Cheap return-type pre-check. The replacement must be an expression whose
272
+ * OUTERMOST call is a Locator-returning Playwright method (locator / getBy* /
273
+ * frameLocator / nth / first / last / filter / and / or). Rejects actions
274
+ * (.click()), string queries (.title(), .textContent()), bare literals, etc.
275
+ */
276
+ function isLocatorReturningExpr(text) {
277
+ const project = newGateProject();
278
+ let sf;
279
+ try {
280
+ sf = project.createSourceFile("_probe.ts", `const __x = ${text};`, {
281
+ overwrite: true,
282
+ });
283
+ }
284
+ catch {
285
+ return false;
286
+ }
287
+ const decl = sf.getVariableDeclaration("__x");
288
+ const init = decl?.getInitializer();
289
+ if (!init)
290
+ return false;
291
+ // Outermost expression must be a call whose method is Locator-returning.
292
+ if (!ts_morph_1.Node.isCallExpression(init))
293
+ return false;
294
+ const callee = init.getExpression();
295
+ if (!ts_morph_1.Node.isPropertyAccessExpression(callee))
296
+ return false;
297
+ return LOCATOR_RETURNING_CATEGORIES.has((0, TraceBackEngine_1.categorizeMethod)(callee.getName()));
298
+ }
299
+ // ═══════════════════════════════════════════════════════════════════
300
+ // ATOMIC WRITE
301
+ // ═══════════════════════════════════════════════════════════════════
302
+ function atomicWrite(filePath, content) {
303
+ const tmp = `${filePath}.sela-tmp-${process.pid}`;
304
+ fs.writeFileSync(tmp, content, "utf8");
305
+ try {
306
+ fs.renameSync(tmp, filePath); // atomic on POSIX; replace-on-Windows
307
+ }
308
+ catch {
309
+ // Windows can reject rename-over-existing in rare cases — fall back to a
310
+ // direct write, then best-effort remove the temp file.
311
+ fs.writeFileSync(filePath, content, "utf8");
312
+ try {
313
+ fs.unlinkSync(tmp);
314
+ }
315
+ catch {
316
+ /* best effort */
317
+ }
318
+ }
319
+ }
320
+ function truncate(s, n = 48) {
321
+ return s.length > n ? `${s.slice(0, n)}…` : s;
322
+ }
@@ -37,14 +37,14 @@ export interface ExecutionResult {
37
37
  * Clean-Room execution lifecycle.
38
38
  *
39
39
  * The legacy flow committed the developer's whole dirty working tree onto
40
- * the PR branch polluting reviewers with `test.only`, commented debug
40
+ * the PR branch - polluting reviewers with `test.only`, commented debug
41
41
  * blocks, etc. This refactor isolates Sela's specific AST changes onto an
42
42
  * ephemeral branch, leaves the developer's local workspace byte-identical
43
43
  * to its pre-Sela state, and is wrapped in a try/finally that restores
44
44
  * the workspace even on mid-flight crashes.
45
45
  */
46
46
  export declare function execute(cfg: ResolvedPRAutomation, decision: StrategyDecision, heals: HealedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext,
47
- /** Injectable for tests defaults to the module singleton. */
47
+ /** Injectable for tests - defaults to the module singleton. */
48
48
  workspace?: WorkspaceSnapshotService): Promise<ExecutionResult>;
49
49
  export declare function handleBugDetected(action: BugAction, events: ProtectedEvent[], branchInfo: BranchInfo, ctx: ExecuteContext): Promise<void>;
50
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;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"}
1
+ {"version":3,"file":"PRAutomationService.d.ts","sourceRoot":"","sources":["../../src/services/PRAutomationService.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,SAAS,EACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,4BAA4B,CAAC;AAOpC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,UAAU,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,aAAa,GAAG,IAAI,CAAC;IAC9E;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAiFD,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,CA+EpE;AAMD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,WAAW,EAAE,GACnB,gBAAgB,CAgElB;AAMD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAEzC;AA6FD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,oBAAoB,EACzB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc;AACnB,+DAA+D;AAC/D,SAAS,GAAE,wBAAkD,GAC5D,OAAO,CAAC,eAAe,CAAC,CAkG1B;AAwTD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,cAAc,EAAE,EACxB,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CA4Gf"}
@@ -93,7 +93,7 @@ function isSelaHealBranch(branch) {
93
93
  * Resolve the repo's default integration branch.
94
94
  * 1. origin/HEAD symbolic-ref (set by `git clone`).
95
95
  * 2. First existing local ref among: main, master, develop, dev.
96
- * Returns null if none found caller must handle that.
96
+ * Returns null if none found - caller must handle that.
97
97
  */
98
98
  function findDefaultBranch(cwd) {
99
99
  const headRef = sh("git symbolic-ref refs/remotes/origin/HEAD", cwd);
@@ -124,7 +124,7 @@ function detectBranch(cwd = process.cwd()) {
124
124
  const ghRefName = process.env.GITHUB_REF_NAME; // current branch on push, or "<n>/merge" on PR
125
125
  const ghEventName = process.env.GITHUB_EVENT_NAME;
126
126
  const ghRef = process.env.GITHUB_REF; // "refs/pull/123/merge"
127
- // Prefer HEAD_REF on PRs REF_NAME is the synthetic merge ref.
127
+ // Prefer HEAD_REF on PRs - REF_NAME is the synthetic merge ref.
128
128
  if (ghHeadRef && ghHeadRef.length > 0 && !isSelaHealBranch(ghHeadRef)) {
129
129
  const prNumber = (() => {
130
130
  const m = ghRef?.match(/refs\/pull\/(\d+)\//);
@@ -175,11 +175,11 @@ function detectBranch(cwd = process.cwd()) {
175
175
  if (isSelaHealBranch(localBranch)) {
176
176
  const fallback = findDefaultBranch(cwd);
177
177
  if (fallback) {
178
- console.warn(`[Sela PR] ⚠️ HEAD is on '${localBranch}' (sela patch branch) ` +
178
+ console.warn(`[Sela PR] ⚠️ HEAD is on '${localBranch}' (sela patch branch) - ` +
179
179
  `using '${fallback}' as PR base instead.`);
180
180
  return { branch: fallback, isPR: false, prNumber: null, source: "git" };
181
181
  }
182
- console.warn(`[Sela PR] ⚠️ HEAD is on '${localBranch}' and no default branch found ` +
182
+ console.warn(`[Sela PR] ⚠️ HEAD is on '${localBranch}' and no default branch found - ` +
183
183
  `skipping PR automation to avoid self-targeting.`);
184
184
  return { branch: null, isPR: false, prNumber: null, source: "none" };
185
185
  }
@@ -190,7 +190,7 @@ function detectBranch(cwd = process.cwd()) {
190
190
  // ═══════════════════════════════════════════════════════════════════
191
191
  function decideEffectiveStrategy(cfg, branch, heals) {
192
192
  // Compute MIN across only the heals that reported a finite numeric
193
- // confidence. When *no* heal carried one, the result is `undefined`
193
+ // confidence. When *no* heal carried one, the result is `undefined` -
194
194
  // surfaced as "n/a" in reports rather than synthesised as 100% (which
195
195
  // would silently mask the absence of evidence) or 0% (which would
196
196
  // incorrectly force a downgrade for every clean run on Sonnet).
@@ -219,7 +219,7 @@ function decideEffectiveStrategy(cfg, branch, heals) {
219
219
  downgradeReason: "PROTECTED_BRANCH",
220
220
  };
221
221
  }
222
- // Rule 2: low AI confidence only triggers when the measured min is
222
+ // Rule 2: low AI confidence - only triggers when the measured min is
223
223
  // actually below the threshold. Missing confidence is "unknown", not
224
224
  // "zero", so we do NOT downgrade purely on absence.
225
225
  if (cfg.reviewThresholds.minConfidenceForDirectCommit > 0 &&
@@ -231,7 +231,7 @@ function decideEffectiveStrategy(cfg, branch, heals) {
231
231
  downgradeReason: "LOW_CONFIDENCE",
232
232
  };
233
233
  }
234
- // Rule 3: low auditor score same "absence ≠ zero" rule as Rule 2.
234
+ // Rule 3: low auditor score - same "absence ≠ zero" rule as Rule 2.
235
235
  if (cfg.reviewThresholds.minAuditorScoreForDirectCommit > 0 &&
236
236
  typeof minAud === "number" &&
237
237
  minAud < cfg.reviewThresholds.minAuditorScoreForDirectCommit) {
@@ -285,7 +285,7 @@ function stripDiffPreamble(raw) {
285
285
  }
286
286
  function buildDefaultBody(heals, branchInfo, reportLink, decision) {
287
287
  const lines = [];
288
- lines.push(`# 🤖 Sela Insights Automation Suite Healed!`);
288
+ lines.push(`# 🤖 Sela Insights - Automation Suite Healed!`);
289
289
  lines.push("");
290
290
  lines.push(`Sela kept your pipeline running by healing ${heals.length} broken locator(s).`);
291
291
  lines.push("");
@@ -301,12 +301,12 @@ function buildDefaultBody(heals, branchInfo, reportLink, decision) {
301
301
  lines.push(`## 💻 Per-Heal Diff`);
302
302
  for (const h of heals) {
303
303
  const lineRef = h.newLineNumber ?? h.sourceLine;
304
- lines.push(`### \`${h.sourceFile}:${lineRef}\` ${h.testTitle ?? "(no test title)"}`);
304
+ lines.push(`### \`${h.sourceFile}:${lineRef}\` - ${h.testTitle ?? "(no test title)"}`);
305
305
  lines.push("");
306
306
  lines.push("```diff");
307
307
  if (h.gitUnifiedDiff && h.gitUnifiedDiff.trim().length > 0) {
308
308
  // Emit the real git diff verbatim. This natively handles cross-file
309
- // healing, multi-line edits, and template-literal changes none of
309
+ // healing, multi-line edits, and template-literal changes - none of
310
310
  // which the legacy single-line -/+ pair could represent.
311
311
  lines.push(stripDiffPreamble(h.gitUnifiedDiff));
312
312
  }
@@ -333,14 +333,14 @@ function buildDefaultBody(heals, branchInfo, reportLink, decision) {
333
333
  * Clean-Room execution lifecycle.
334
334
  *
335
335
  * The legacy flow committed the developer's whole dirty working tree onto
336
- * the PR branch polluting reviewers with `test.only`, commented debug
336
+ * the PR branch - polluting reviewers with `test.only`, commented debug
337
337
  * blocks, etc. This refactor isolates Sela's specific AST changes onto an
338
338
  * ephemeral branch, leaves the developer's local workspace byte-identical
339
339
  * to its pre-Sela state, and is wrapped in a try/finally that restores
340
340
  * the workspace even on mid-flight crashes.
341
341
  */
342
342
  async function execute(cfg, decision, heals, branchInfo, ctx,
343
- /** Injectable for tests defaults to the module singleton. */
343
+ /** Injectable for tests - defaults to the module singleton. */
344
344
  workspace = WorkspaceSnapshotService_1.sharedWorkspaceSnapshot) {
345
345
  const result = {
346
346
  effective: decision.effective,
@@ -349,33 +349,33 @@ workspace = WorkspaceSnapshotService_1.sharedWorkspaceSnapshot) {
349
349
  errors: [],
350
350
  };
351
351
  if (decision.effective === "readOnly") {
352
- console.log(`[Sela PR] 🛡️ readOnly no git operations performed.`);
352
+ console.log(`[Sela PR] 🛡️ readOnly - no git operations performed.`);
353
353
  if (ctx.reportHtmlPath) {
354
354
  console.log(`[Sela PR] Report artifact: ${ctx.reportHtmlPath}`);
355
355
  }
356
356
  return result;
357
357
  }
358
358
  if (heals.length === 0) {
359
- console.log(`[Sela PR] No HEALED events skipping git operations.`);
359
+ console.log(`[Sela PR] No HEALED events - skipping git operations.`);
360
360
  return result;
361
361
  }
362
362
  if (!gitAvailable(ctx.cwd)) {
363
363
  result.errors.push("git CLI not available");
364
- console.warn(`[Sela PR] ⚠️ git CLI not available skipping.`);
364
+ console.warn(`[Sela PR] ⚠️ git CLI not available - skipping.`);
365
365
  return result;
366
366
  }
367
367
  if (isDetachedHead(ctx.cwd)) {
368
368
  result.errors.push("detached HEAD");
369
- console.warn(`[Sela PR] ⚠️ Detached HEAD skipping git operations.`);
369
+ console.warn(`[Sela PR] ⚠️ Detached HEAD - skipping git operations.`);
370
370
  return result;
371
371
  }
372
372
  const mutatedSnaps = workspace.getMutatedSnapshots();
373
373
  if (mutatedSnaps.length === 0) {
374
374
  result.errors.push("no mutated files tracked");
375
- console.warn(`[Sela PR] ⚠️ WorkspaceSnapshotService reports no mutated files skipping.`);
375
+ console.warn(`[Sela PR] ⚠️ WorkspaceSnapshotService reports no mutated files - skipping.`);
376
376
  return result;
377
377
  }
378
- // Render title / body up front needed by all branches below.
378
+ // Render title / body up front - needed by all branches below.
379
379
  const first = heals[0];
380
380
  const titleVars = {
381
381
  testName: first.testTitle ?? "automation",
@@ -410,7 +410,7 @@ workspace = WorkspaceSnapshotService_1.sharedWorkspaceSnapshot) {
410
410
  if (decision.effective === "pr" || decision.effective === "draftPR") {
411
411
  if (!ghAvailable(ctx.cwd)) {
412
412
  result.errors.push("gh CLI not available");
413
- console.warn(`[Sela PR] ⚠️ gh CLI not installed cannot open PR. Run \`gh auth login\` after install.`);
413
+ console.warn(`[Sela PR] ⚠️ gh CLI not installed - cannot open PR. Run \`gh auth login\` after install.`);
414
414
  return result;
415
415
  }
416
416
  return runCleanRoom({
@@ -428,25 +428,25 @@ workspace = WorkspaceSnapshotService_1.sharedWorkspaceSnapshot) {
428
428
  return result;
429
429
  }
430
430
  async function runCleanRoom(args) {
431
- const { mode, cfg, ctx, mutatedSnaps, title, body, branchInfo, decision, result } = args;
431
+ const { mode, cfg, ctx, mutatedSnaps, title, body, branchInfo, decision, result, } = args;
432
432
  const id = String(Date.now());
433
433
  const stashLabel = `sela:clean-room-${id}`;
434
434
  const originalBranch = currentBranch(ctx.cwd);
435
435
  const targetBranch = mode === "directCommit"
436
436
  ? originalBranch
437
437
  : render(cfg.branchNameTemplate, { id, timestamp: id });
438
- // Lifecycle state used by the finally{} restoration block.
438
+ // Lifecycle state - used by the finally{} restoration block.
439
439
  let didRestoreOriginal = false;
440
440
  let didStash = false;
441
441
  let didSwitchToTarget = false;
442
442
  let didCommit = false;
443
443
  try {
444
- // 1. State Checkpoint rewind developer's mutated files to pre-Sela
444
+ // 1. State Checkpoint - rewind developer's mutated files to pre-Sela
445
445
  // state on disk. Other dirty files (unrelated to Sela) stay where
446
446
  // they are; the subsequent stash will sweep them up.
447
447
  workspaceRestoreOriginal(mutatedSnaps);
448
448
  didRestoreOriginal = true;
449
- // 2. Branch Isolation stash everything (tracked + untracked) so the
449
+ // 2. Branch Isolation - stash everything (tracked + untracked) so the
450
450
  // worktree mirrors HEAD when we switch / branch off.
451
451
  const stash = sh(`git stash push -u -m ${shellQuote(stashLabel)}`, ctx.cwd);
452
452
  if (stash.ok) {
@@ -463,7 +463,7 @@ async function runCleanRoom(args) {
463
463
  }
464
464
  didSwitchToTarget = true;
465
465
  }
466
- // 3. Targeted Hunk Patching apply ONLY Sela's blocks to each file's
466
+ // 3. Targeted Hunk Patching - apply ONLY Sela's blocks to each file's
467
467
  // clean HEAD content. Refuses to write if dirty edits collide with
468
468
  // Sela's mutation site (block not found / not unique).
469
469
  for (const snap of mutatedSnaps) {
@@ -517,7 +517,7 @@ async function runCleanRoom(args) {
517
517
  console.warn(`[Sela PR] ⚠️ Clean-room flow failed: ${err?.message ?? err}`);
518
518
  }
519
519
  finally {
520
- // 6. Workspace Restoration ALWAYS attempt, even on mid-flight crash.
520
+ // 6. Workspace Restoration - ALWAYS attempt, even on mid-flight crash.
521
521
  // Order matters:
522
522
  // a) Switch back to the developer's original branch (if we left it).
523
523
  // b) Pop the stash to restore the dirty pre-Sela working tree.
@@ -537,7 +537,7 @@ async function runCleanRoom(args) {
537
537
  }
538
538
  }
539
539
  if (didRestoreOriginal) {
540
- // Belt-and-braces every tracked snapshot back to its dirty
540
+ // Belt-and-braces - every tracked snapshot back to its dirty
541
541
  // pre-Sela state. No-op if stash pop already did the job.
542
542
  workspaceRestoreOriginal(mutatedSnaps);
543
543
  }
@@ -565,7 +565,9 @@ function openPullRequest(args) {
565
565
  ? cfg.labels.map((l) => `--label ${shellQuote(l)}`).join(" ")
566
566
  : "";
567
567
  const draftFlag = mode === "draftPR" ? "--draft" : "";
568
- const baseFlag = branchInfo.branch ? `--base ${shellQuote(branchInfo.branch)}` : "";
568
+ const baseFlag = branchInfo.branch
569
+ ? `--base ${shellQuote(branchInfo.branch)}`
570
+ : "";
569
571
  const prCmd = `gh pr create ${draftFlag} ${baseFlag} ` +
570
572
  `--title ${shellQuote(title)} ` +
571
573
  `--body-file ${shellQuote(bodyFile)} ` +
@@ -614,16 +616,16 @@ function workspaceRestoreOriginal(snaps) {
614
616
  function applyMutationToClean(snap) {
615
617
  const after = snap.contentAfter ?? "";
616
618
  // File didn't exist on HEAD (created by Sela or untracked). Write the
617
- // post-mutation content directly there's no clean base to merge into.
619
+ // post-mutation content directly - there's no clean base to merge into.
618
620
  if (snap.headContent === null) {
619
621
  return { ok: true, result: after };
620
622
  }
621
- // No dirty edits `headContent === contentBefore`, so the mutation
623
+ // No dirty edits - `headContent === contentBefore`, so the mutation
622
624
  // applies cleanly as a 1:1 string equivalence.
623
625
  if (snap.headContent === snap.contentBefore) {
624
626
  return { ok: true, result: after };
625
627
  }
626
- // Dirty edits present extract Sela's specific change blocks and
628
+ // Dirty edits present - extract Sela's specific change blocks and
627
629
  // re-apply them against the clean HEAD content via unique substring
628
630
  // replacement. Fails loudly when the dirty edits overlap Sela's site.
629
631
  const blocks = (0, IsolatedDiff_1.extractChangeBlocks)(snap.contentBefore, after);
@@ -636,7 +638,7 @@ function applyMutationToClean(snap) {
636
638
  * CI adapters (GitHub Actions, GitLab, Jenkins) often run with no global
637
639
  * git identity. `git commit` then fails with `Please tell me who you are`,
638
640
  * tanking the whole heal pipeline. Plant a sela bot identity for THIS
639
- * commit only when no `user.email` / `user.name` is configured never
641
+ * commit only when no `user.email` / `user.name` is configured - never
640
642
  * overwrite an existing identity.
641
643
  */
642
644
  function configureGitIdentityForCI(cwd) {
@@ -662,18 +664,18 @@ function configureGitIdentityForCI(cwd) {
662
664
  async function handleBugDetected(action, events, branchInfo, ctx) {
663
665
  if (events.length === 0)
664
666
  return;
665
- console.warn(`\n[Sela Protection] ⚠️ ${events.length} potential regression(s) detected auto-fix blocked.`);
667
+ console.warn(`\n[Sela Protection] ⚠️ ${events.length} potential regression(s) detected - auto-fix blocked.`);
666
668
  for (const e of events) {
667
- console.warn(` • ${e.sourceFile}:${e.sourceLine} ${e.reason} (auditor: ${e.auditor?.verdict ?? "n/a"})`);
669
+ console.warn(` • ${e.sourceFile}:${e.sourceLine} - ${e.reason} (auditor: ${e.auditor?.verdict ?? "n/a"})`);
668
670
  }
669
671
  if (action === "failCI") {
670
- console.warn(`[Sela Protection] 🔴 onBugDetected='failCI' setting process.exitCode = 1`);
672
+ console.warn(`[Sela Protection] 🔴 onBugDetected='failCI' - setting process.exitCode = 1`);
671
673
  process.exitCode = 1;
672
674
  return;
673
675
  }
674
676
  if (action === "createIssue") {
675
677
  if (!ghAvailable(ctx.cwd)) {
676
- console.warn(`[Sela Protection] ⚠️ gh CLI unavailable cannot create issue.`);
678
+ console.warn(`[Sela Protection] ⚠️ gh CLI unavailable - cannot create issue.`);
677
679
  return;
678
680
  }
679
681
  for (const e of events) {
@@ -706,7 +708,9 @@ async function handleBugDetected(action, events, branchInfo, ctx) {
706
708
  try {
707
709
  fs.unlinkSync(bodyFile);
708
710
  }
709
- catch { /* ignore */ }
711
+ catch {
712
+ /* ignore */
713
+ }
710
714
  if (!r.ok) {
711
715
  console.warn(`[Sela Protection] ⚠️ gh issue create failed: ${r.stderr}`);
712
716
  }
@@ -718,11 +722,11 @@ async function handleBugDetected(action, events, branchInfo, ctx) {
718
722
  }
719
723
  if (action === "labelOnly") {
720
724
  if (!ghAvailable(ctx.cwd)) {
721
- console.warn(`[Sela Protection] ⚠️ gh CLI unavailable cannot label PR.`);
725
+ console.warn(`[Sela Protection] ⚠️ gh CLI unavailable - cannot label PR.`);
722
726
  return;
723
727
  }
724
728
  if (!branchInfo.isPR || !branchInfo.prNumber) {
725
- console.warn(`[Sela Protection] ⚠️ Not running on a PR labelOnly skipped.`);
729
+ console.warn(`[Sela Protection] ⚠️ Not running on a PR - labelOnly skipped.`);
726
730
  return;
727
731
  }
728
732
  const labelCmd = `gh pr edit ${branchInfo.prNumber} --add-label sela-bug-detected`;
@@ -742,7 +746,7 @@ async function handleBugDetected(action, events, branchInfo, ctx) {
742
746
  }
743
747
  }
744
748
  // ═══════════════════════════════════════════════════════════════════
745
- // SHELL QUOTING cross-platform (POSIX single-quote escape;
749
+ // SHELL QUOTING - cross-platform (POSIX single-quote escape;
746
750
  // on Windows execSync uses cmd.exe which handles double-quotes natively).
747
751
  // ═══════════════════════════════════════════════════════════════════
748
752
  function shellQuote(s) {