@glrs-dev/cli 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{chunk-EM4MJBOD.js → chunk-2AZKRWC6.js} +4 -4
  3. package/dist/{chunk-UXBOTMDY.js → chunk-2P3ETOT2.js} +2 -2
  4. package/dist/chunk-2VMFXAJH.js +795 -0
  5. package/dist/chunk-5ZVUFNCP.js +140 -0
  6. package/dist/{chunk-W37UX3U2.js → chunk-6Y27RQQL.js} +2 -2
  7. package/dist/{chunk-RZWOWTKF.js → chunk-EKNRKZWR.js} +4 -4
  8. package/dist/{chunk-YGNDPKIW.js → chunk-HQUCVJ4G.js} +3 -1
  9. package/dist/{chunk-OABVEBWW.js → chunk-MBEVC327.js} +1 -1
  10. package/dist/{chunk-MIWZLETC.js → chunk-MCM47HH4.js} +1 -1
  11. package/dist/{chunk-F3AFRUT2.js → chunk-PTIO556V.js} +2 -2
  12. package/dist/{chunk-E2UNZIZT.js → chunk-R2WXQ54P.js} +1 -1
  13. package/dist/{chunk-I2KUXY3I.js → chunk-SMDIOB5B.js} +2 -2
  14. package/dist/{chunk-SPULDN7P.js → chunk-YY7EWHMA.js} +5 -3
  15. package/dist/cli.js +31 -20
  16. package/dist/commands/autopilot-interactive.d.ts +89 -0
  17. package/dist/commands/autopilot-interactive.js +248 -0
  18. package/dist/commands/autopilot-raw.d.ts +1 -0
  19. package/dist/commands/autopilot-raw.js +368 -0
  20. package/dist/commands/autopilot-tui.d.ts +7 -0
  21. package/dist/commands/autopilot-tui.js +7 -0
  22. package/dist/commands/autopilot.d.ts +39 -0
  23. package/dist/commands/autopilot.js +395 -0
  24. package/dist/commands/cleanup.js +3 -3
  25. package/dist/commands/create.js +4 -4
  26. package/dist/commands/dashboard.d.ts +3 -0
  27. package/dist/commands/dashboard.js +1549 -0
  28. package/dist/commands/debrief.d.ts +57 -0
  29. package/dist/commands/debrief.js +9 -0
  30. package/dist/commands/delete.js +3 -3
  31. package/dist/commands/go.js +2 -2
  32. package/dist/commands/list.js +3 -3
  33. package/dist/commands/loop.d.ts +42 -0
  34. package/dist/commands/loop.js +133 -0
  35. package/dist/commands/plan-picker.d.ts +15 -0
  36. package/dist/commands/plan-picker.js +76 -0
  37. package/dist/commands/scoper.d.ts +54 -0
  38. package/dist/{vendor/harness-opencode/dist/scoper-S77SOK7X.js → commands/scoper.js} +30 -15
  39. package/dist/commands/switch.js +3 -3
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/lib/auto-update.js +1 -1
  43. package/dist/lib/config.d.ts +3 -2
  44. package/dist/lib/config.js +1 -1
  45. package/dist/lib/registry.d.ts +2 -0
  46. package/dist/lib/registry.js +1 -1
  47. package/dist/lib/worktree.js +3 -3
  48. package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.d.ts +261 -0
  49. package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.js +488 -0
  50. package/dist/node_modules/@glrs-dev/adapter-opencode/package.json +8 -0
  51. package/dist/node_modules/@glrs-dev/autopilot/dist/auto-ship-LCT6LIH7.js +7 -0
  52. package/dist/node_modules/@glrs-dev/autopilot/dist/changeset-generator-DG3MVWVV.js +15 -0
  53. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-7OSEI5TF.js +249 -0
  54. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-E7PWTRFO.js +91 -0
  55. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-M2ZVBPWL.js +101 -0
  56. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-Q4ULU6ER.js +68 -0
  57. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-VITL2Z45.js +2772 -0
  58. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-ZNJWARTM.js +449 -0
  59. package/dist/node_modules/@glrs-dev/autopilot/dist/index.d.ts +1765 -0
  60. package/dist/node_modules/@glrs-dev/autopilot/dist/index.js +688 -0
  61. package/dist/node_modules/@glrs-dev/autopilot/dist/logger-UITJGIZE.js +8 -0
  62. package/dist/node_modules/@glrs-dev/autopilot/dist/loop-session-XKL3NHUA.js +8 -0
  63. package/dist/node_modules/@glrs-dev/autopilot/dist/plan-enrichment-D3RPJR2J.js +14 -0
  64. package/dist/node_modules/@glrs-dev/autopilot/package.json +8 -0
  65. package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +7 -0
  66. package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
  67. package/dist/vendor/harness-opencode/dist/cli.js +335 -639
  68. package/dist/vendor/harness-opencode/dist/index.js +35 -8
  69. package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
  70. package/dist/vendor/harness-opencode/package.json +1 -1
  71. package/package.json +14 -6
  72. package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -104
  73. package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +0 -259
  74. package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +0 -87
  75. package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +0 -544
  76. package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +0 -30
  77. package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +0 -22
  78. package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +0 -6
  79. package/dist/vendor/harness-opencode/dist/plan-session-7VS32P52.js +0 -117
@@ -0,0 +1,1765 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { execFile } from 'node:child_process';
3
+ import { Logger } from 'pino';
4
+
5
+ /**
6
+ * AgentAdapter — abstract interface for driving an AI agent programmatically.
7
+ *
8
+ * Decouples the autopilot loop engine from any specific agent implementation
9
+ * (OpenCode, Claude Code, etc.). The concrete adapter is injected at runtime
10
+ * by the CLI command.
11
+ */
12
+ interface AgentHandle {
13
+ readonly id: string;
14
+ }
15
+ interface SessionResult$1 {
16
+ kind: "idle" | "error" | "stall" | "abort" | "question_rejected";
17
+ message?: string;
18
+ stallMs?: number;
19
+ title?: string;
20
+ }
21
+ interface AgentAdapter {
22
+ readonly name: string;
23
+ start(opts: {
24
+ cwd: string;
25
+ }): Promise<AgentHandle>;
26
+ createSession(handle: AgentHandle, opts: {
27
+ agentName?: string;
28
+ }): Promise<string>;
29
+ sendAndWait(handle: AgentHandle, opts: {
30
+ sessionId: string;
31
+ message: string;
32
+ stallMs?: number;
33
+ abortSignal?: AbortSignal;
34
+ onToolCall?: (name: string, arg?: string) => void;
35
+ onTextDelta?: (chars: number) => void;
36
+ onCostUpdate?: (cost: number, tokens: {
37
+ input: number;
38
+ output: number;
39
+ }) => void;
40
+ }): Promise<SessionResult$1>;
41
+ getLastResponse(handle: AgentHandle, sessionId: string): Promise<string>;
42
+ getSessionCost(handle: AgentHandle, sessionId: string): Promise<number>;
43
+ /** Return cost + token totals for a session. Optional — callers fall back to getSessionCost when absent. */
44
+ getSessionStats?(handle: AgentHandle, sessionId: string): Promise<{
45
+ cost: number;
46
+ tokensIn: number;
47
+ tokensOut: number;
48
+ }>;
49
+ shutdown(handle: AgentHandle): Promise<void>;
50
+ enhanceError?(message: string): Promise<string>;
51
+ }
52
+
53
+ /**
54
+ * Typed SessionEvent discriminated union for the autopilot event stream.
55
+ *
56
+ * Every event has `type` (the discriminant) and `timestamp` (ISO 8601 string).
57
+ * Consumers narrow to a specific variant via the exported type guards.
58
+ *
59
+ * Channel 1: EventEmitter (in-process, consumed by CLI renderer)
60
+ * Channel 2: NDJSON file at .agent/autopilot-events.jsonl (consumed by TUI / --status)
61
+ */
62
+ interface SessionStartEvent {
63
+ type: "session:start";
64
+ timestamp: string;
65
+ planPath: string;
66
+ cwd: string;
67
+ fast: boolean;
68
+ resume: boolean;
69
+ enrichModel?: string;
70
+ executeModel?: string;
71
+ }
72
+ interface SessionDoneEvent {
73
+ type: "session:done";
74
+ timestamp: string;
75
+ exitReason: string;
76
+ iterations: number;
77
+ cumulativeCostUsd?: number;
78
+ message: string;
79
+ }
80
+ interface EnrichStartEvent {
81
+ type: "enrich:start";
82
+ timestamp: string;
83
+ planPath: string;
84
+ fileCount: number;
85
+ }
86
+ interface EnrichFileStartEvent {
87
+ type: "enrich:file:start";
88
+ timestamp: string;
89
+ file: string;
90
+ }
91
+ interface EnrichFileDoneEvent {
92
+ type: "enrich:file:done";
93
+ timestamp: string;
94
+ file: string;
95
+ toolCalls: number;
96
+ specFile?: string;
97
+ }
98
+ interface EnrichFileSkipEvent {
99
+ type: "enrich:file:skip";
100
+ timestamp: string;
101
+ file: string;
102
+ reason: string;
103
+ }
104
+ interface EnrichFileErrorEvent {
105
+ type: "enrich:file:error";
106
+ timestamp: string;
107
+ file: string;
108
+ error: string;
109
+ }
110
+ interface EnrichDoneEvent {
111
+ type: "enrich:done";
112
+ timestamp: string;
113
+ filesProcessed: number;
114
+ }
115
+ interface PhaseStartEvent {
116
+ type: "phase:start";
117
+ timestamp: string;
118
+ phase: string;
119
+ laneId: string;
120
+ current: number;
121
+ total: number;
122
+ }
123
+ interface PhaseDoneEvent {
124
+ type: "phase:done";
125
+ timestamp: string;
126
+ phase: string;
127
+ laneId: string;
128
+ completed: boolean;
129
+ iterations: number;
130
+ costUsd: number;
131
+ }
132
+ interface IterationStartEvent {
133
+ type: "iteration:start";
134
+ timestamp: string;
135
+ iteration: number;
136
+ maxIterations: number;
137
+ laneId?: string;
138
+ }
139
+ interface IterationDoneEvent {
140
+ type: "iteration:done";
141
+ timestamp: string;
142
+ iteration: number;
143
+ durationMs: number;
144
+ madeProgress: boolean;
145
+ filesChanged?: number;
146
+ commitSubject?: string;
147
+ costUsd?: number;
148
+ cumulativeCostUsd?: number;
149
+ laneId?: string;
150
+ }
151
+ interface ToolCallEvent {
152
+ type: "tool:call";
153
+ timestamp: string;
154
+ toolName: string;
155
+ firstArg?: string;
156
+ iteration: number;
157
+ laneId?: string;
158
+ }
159
+ interface CostUpdateEvent {
160
+ type: "cost:update";
161
+ timestamp: string;
162
+ cumulativeCostUsd: number;
163
+ isEstimated: boolean;
164
+ iteration: number;
165
+ tokensIn?: number;
166
+ tokensOut?: number;
167
+ }
168
+ interface ThinkingEvent {
169
+ type: "thinking";
170
+ timestamp: string;
171
+ iteration: number;
172
+ /** Cumulative characters streamed in the current reasoning block. */
173
+ chars: number;
174
+ /** Seconds since the last tool call (or iteration start). */
175
+ elapsedSec: number;
176
+ laneId?: string;
177
+ }
178
+ interface ErrorEvent {
179
+ type: "error";
180
+ timestamp: string;
181
+ message: string;
182
+ iteration?: number;
183
+ phase?: string;
184
+ }
185
+ interface CredentialExpiredEvent {
186
+ type: "credential:expired";
187
+ timestamp: string;
188
+ provider: string;
189
+ message: string;
190
+ iteration: number;
191
+ }
192
+ interface VerifyStartEvent {
193
+ type: "verify:start";
194
+ timestamp: string;
195
+ phase: string;
196
+ itemCount: number;
197
+ }
198
+ interface VerifyResultEvent {
199
+ type: "verify:result";
200
+ timestamp: string;
201
+ phase: string;
202
+ itemId: string;
203
+ command: string;
204
+ passed: boolean;
205
+ stderr?: string;
206
+ }
207
+ interface VerifyDoneEvent {
208
+ type: "verify:done";
209
+ timestamp: string;
210
+ phase: string;
211
+ passed: number;
212
+ failed: number;
213
+ }
214
+ type SessionEvent = SessionStartEvent | SessionDoneEvent | EnrichStartEvent | EnrichFileStartEvent | EnrichFileDoneEvent | EnrichFileSkipEvent | EnrichFileErrorEvent | EnrichDoneEvent | PhaseStartEvent | PhaseDoneEvent | IterationStartEvent | IterationDoneEvent | ToolCallEvent | CostUpdateEvent | ThinkingEvent | ErrorEvent | CredentialExpiredEvent | VerifyStartEvent | VerifyResultEvent | VerifyDoneEvent;
215
+
216
+ /**
217
+ * NDJSON event stream writer and reader for the autopilot event stream.
218
+ *
219
+ * Channel 2: .agent/autopilot-events.jsonl
220
+ *
221
+ * Writer: append-only, sync writes (crash-safe).
222
+ * Reader: full read or tail mode (byte offset).
223
+ */
224
+
225
+ /**
226
+ * Append-only NDJSON writer. Opens the file for append on construction,
227
+ * writes each event as a JSON line synchronously (crash-safe), and closes
228
+ * the file descriptor on `close()`.
229
+ */
230
+ declare class EventStreamWriter {
231
+ private fd;
232
+ constructor(filePath: string);
233
+ /**
234
+ * Serialize `event` as a JSON line and write it synchronously.
235
+ * Sync write ensures the line is on disk even if the process crashes
236
+ * immediately after.
237
+ */
238
+ emit(event: SessionEvent): void;
239
+ /**
240
+ * Close the underlying file descriptor. Safe to call multiple times
241
+ * (subsequent calls are no-ops).
242
+ */
243
+ close(): void;
244
+ }
245
+ /**
246
+ * NDJSON reader for the autopilot event stream.
247
+ *
248
+ * Supports full reads and tail mode (byte offset). Truncated or malformed
249
+ * last lines are silently skipped — they indicate a crash mid-write.
250
+ */
251
+ declare class EventStreamReader {
252
+ private readonly filePath;
253
+ constructor(filePath: string);
254
+ /**
255
+ * Read all events from the file. Returns an empty array if the file
256
+ * does not exist or is empty.
257
+ */
258
+ readAll(): SessionEvent[];
259
+ /**
260
+ * Read events starting at `byteOffset`. Returns the parsed events and
261
+ * the new byte offset (= file size after reading). Useful for polling
262
+ * (tail mode): pass the returned `newOffset` on the next call to read
263
+ * only new events.
264
+ *
265
+ * Truncated or malformed last lines are skipped.
266
+ */
267
+ readFrom(byteOffset: number): {
268
+ events: SessionEvent[];
269
+ newOffset: number;
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Plan-parser module for the autopilot engine.
275
+ *
276
+ * Parses both single-file plans (one .md file with checkboxes or a
277
+ * plan-state fence) and multi-file plans (a directory containing
278
+ * main.md + phase_N.md files). Returns structured progress data.
279
+ *
280
+ * Never throws — all errors degrade to zero-count results so the
281
+ * loop's heartbeat can fall back to plan-blind mode safely.
282
+ */
283
+ interface PlanPhase {
284
+ file: string;
285
+ totalItems: number;
286
+ checkedItems: number;
287
+ }
288
+ interface PlanState {
289
+ type: "single" | "multi";
290
+ totalItems: number;
291
+ checkedItems: number;
292
+ phaseCount: number;
293
+ phasesCompleted: number;
294
+ phases: PlanPhase[];
295
+ }
296
+ interface PlanFileEntry {
297
+ path: string;
298
+ isNew: boolean;
299
+ change: string;
300
+ }
301
+ interface PlanItem {
302
+ id: string;
303
+ intent: string;
304
+ files: PlanFileEntry[];
305
+ tests: string[];
306
+ verify: string;
307
+ checked: boolean;
308
+ }
309
+ /**
310
+ * Parse a plan at the given path and return structured progress data.
311
+ *
312
+ * - If `planPath` is a directory containing `main.md`, it's treated as
313
+ * a multi-file plan.
314
+ * - If `planPath` is a `.md` file, it's treated as a single-file plan.
315
+ * - Any error (missing file, parse failure, etc.) returns a degraded
316
+ * result with zero counts — never throws.
317
+ */
318
+ declare function parsePlanState(planPath: string): PlanState;
319
+ /**
320
+ * Parse individual plan items from a plan-state fence block.
321
+ *
322
+ * Each item starts with `- [ ] id:` or `- [x] id:` and contains
323
+ * optional `intent:`, `files:`, `tests:`, and `verify:` fields.
324
+ *
325
+ * Returns an empty array if no fence is found or the content is malformed.
326
+ * Never throws.
327
+ */
328
+ declare function parseItems(content: string): PlanItem[];
329
+
330
+ /**
331
+ * Verify-command runner for the post-phase test gate (item 4.1).
332
+ *
333
+ * After a phase reports `phaseComplete === true` (every checkbox checked),
334
+ * the orchestrator runs each plan-state item's `verify:` command in a
335
+ * child process and reports pass/fail. Phases whose verify commands
336
+ * fail are treated as incomplete — the failure is fed back into the
337
+ * next iteration's prompt so the agent can fix it.
338
+ *
339
+ * Each command runs via `/bin/sh -c` so users can write shell features
340
+ * (pipes, redirects, env vars) into a `verify:` field. Per-command
341
+ * timeout = 5 minutes; on timeout we synthesize a stderr message and
342
+ * return `passed: false` rather than throwing — `runVerifyCommands`
343
+ * always returns a `VerifyResult[]` so callers can build a result table
344
+ * without wrapping every call in try/catch.
345
+ */
346
+
347
+ declare const execFileDefault$3: typeof execFile.__promisify__;
348
+ /** Result of running a single `verify:` command. */
349
+ interface VerifyResult {
350
+ /** Plan-state item id (e.g. "4.1"). */
351
+ itemId: string;
352
+ /** The exact command string that was run. */
353
+ command: string;
354
+ /** True iff the child process exited with code 0 within the timeout. */
355
+ passed: boolean;
356
+ stdout: string;
357
+ stderr: string;
358
+ /** Wall-clock duration of the command in milliseconds. */
359
+ durationMs: number;
360
+ }
361
+ interface RunVerifyCommandsOptions {
362
+ /** Per-command timeout in milliseconds (default: 5 minutes). */
363
+ timeoutMs?: number;
364
+ /**
365
+ * Test-only: inject execFile replacement.
366
+ * @internal
367
+ */
368
+ _deps?: {
369
+ execFile?: typeof execFileDefault$3;
370
+ };
371
+ }
372
+ /**
373
+ * Run each item's `verify:` command in `cwd` and return the results.
374
+ *
375
+ * Items without a `verify:` field are skipped (no result emitted). The
376
+ * function never throws — every executed command yields exactly one
377
+ * `VerifyResult`, including timeouts and spawn failures.
378
+ */
379
+ declare function runVerifyCommands(items: PlanItem[], cwd: string, opts?: RunVerifyCommandsOptions): Promise<VerifyResult[]>;
380
+
381
+ /**
382
+ * Structured logger for the autopilot CLI.
383
+ *
384
+ * Single sink: **file** at trace level (captures everything, always).
385
+ * JSON — files are for grep/telemetry/debugging, not eyes.
386
+ * Default path: `<cwd>/.agent/autopilot-logs/<ISO-timestamp>.log`.
387
+ * Override via GLRS_AUTOPILOT_LOG_FILE=<path> or "off" to disable.
388
+ *
389
+ * User-facing output goes to stderr via plain `process.stderr.write`
390
+ * in the calling code (TUI-style). Pino does NOT write to stderr —
391
+ * the two channels are separate by design.
392
+ *
393
+ * Environment variables:
394
+ * - GLRS_AUTOPILOT_LOG_FILE: explicit path, or "off" to disable file sink
395
+ */
396
+
397
+ /**
398
+ * Per-run logger state. Created once per autopilot invocation.
399
+ */
400
+ interface AutopilotLogger {
401
+ /** The pino root logger. Call .child({ component }) for module-level loggers. */
402
+ root: Logger;
403
+ /** Path to the file log sink, if enabled. */
404
+ logFilePath: string | null;
405
+ /** Flush all streams. Call before process exit. */
406
+ flush: () => Promise<void>;
407
+ }
408
+ /**
409
+ * Create the autopilot logger with file sink only.
410
+ * Call ONCE per autopilot run. Pass `cwd` so the default file path
411
+ * resolves relative to the user's project.
412
+ *
413
+ * User-facing output goes to stderr via process.stderr.write in the
414
+ * calling code. Pino is for structured file logging only.
415
+ *
416
+ * @deprecated The autopilot code path now uses the typed SessionEvent stream
417
+ * (session-events.ts + event-stream.ts) instead of pino for user-visible output.
418
+ * This function is kept for non-autopilot consumers and as a verbose debug
419
+ * channel inside the loop engine. New code should use SessionRunner + EventStreamWriter.
420
+ */
421
+ declare function createAutopilotLogger(opts: {
422
+ cwd: string;
423
+ }): AutopilotLogger;
424
+ /**
425
+ * Convenience factory for module-level loggers when the root logger is
426
+ * already created. Use this inside modules that receive a root logger
427
+ * from the caller:
428
+ *
429
+ * const log = childLogger(root, "autopilot.loop");
430
+ * log.info("hello"); // emits with component: "autopilot.loop"
431
+ */
432
+ declare function childLogger(root: Logger, component: string): Logger;
433
+
434
+ type LoopExitReason = "sentinel" | "max-iterations" | "timeout" | "struggle" | "kill-switch" | "stall" | "error" | "aborted";
435
+ interface LoopResult {
436
+ exitReason: LoopExitReason;
437
+ iterations: number;
438
+ message: string;
439
+ /** The OpenCode session ID for the loop session. Used by the debrief module. */
440
+ sessionId?: string;
441
+ /** Cumulative cost in USD for the loop session. Used by the debrief module. */
442
+ cumulativeCostUsd?: number;
443
+ /**
444
+ * Per-phase cost breakdown for multi-phase plans.
445
+ * Populated by the multi-phase runner (loop-session.ts) when available.
446
+ */
447
+ phaseBreakdown?: Array<{
448
+ phaseFile: string;
449
+ iterations: number;
450
+ costUsd: number;
451
+ }>;
452
+ /**
453
+ * Per-lane cost breakdown for parallel-lane runs (item 3.5).
454
+ * Populated by `loop-session.ts` only when `--parallel > 1` produced
455
+ * a real parallel run. `Map<laneId, costUsd>`. Optional to preserve
456
+ * backward compatibility for callers that never enabled parallelism.
457
+ */
458
+ laneCosts?: Map<string, number>;
459
+ /**
460
+ * Surviving worktree paths from a partially-failed parallel run
461
+ * (item 3.6). When a lane's merge failed, its worktree is left on
462
+ * disk and the path is reported here so the user can manually
463
+ * `git worktree remove --force <path>`. Empty/undefined on clean runs.
464
+ */
465
+ orphanedWorktrees?: string[];
466
+ /**
467
+ * Per-phase verify-command results from the post-phase test gate
468
+ * (item 4.1). Populated by `loop-session.ts` after each phase runs
469
+ * its `verify:` commands (one per plan-state item). When any verify
470
+ * command fails the phase is treated as incomplete (its main.md
471
+ * checkbox is NOT marked) and the failures are surfaced here so the
472
+ * debrief can render a results table. Optional to preserve back-
473
+ * compat for callers that never enabled verify gating.
474
+ */
475
+ verifyResults?: Array<{
476
+ phaseFile: string;
477
+ results: VerifyResult[];
478
+ }>;
479
+ /**
480
+ * Path to the changeset file generated after all phases passed
481
+ * (item 4.6). Absent when generation was skipped (test deps in
482
+ * use, no phases run, or a prior failure short-circuited the run).
483
+ */
484
+ changesetPath?: string;
485
+ /**
486
+ * URL of the PR opened by --ship (item 4.7). Absent when --ship was
487
+ * not passed or auto-ship failed. The hard-rule guards (no force-
488
+ * push, no main/master push, no merge) live inside `auto-ship.ts`.
489
+ */
490
+ prUrl?: string;
491
+ /**
492
+ * The agent adapter + handle, kept alive for the caller to reuse
493
+ * (e.g., for the debrief). The caller is responsible for calling
494
+ * `adapter.shutdown(handle)` when done. Present only when `keepAlive` is
495
+ * true in RalphLoopOptions (default: false — adapter is shut down
496
+ * in the finally block).
497
+ */
498
+ agentHandle?: {
499
+ adapter: AgentAdapter;
500
+ handle: AgentHandle;
501
+ };
502
+ }
503
+ interface RalphLoopOptions {
504
+ /** The prompt to send to PRIME each iteration. */
505
+ prompt: string;
506
+ /** Working directory for the OpenCode server. */
507
+ cwd: string;
508
+ /** Agent name for the session (default: "autopilot-prime"). */
509
+ agentName?: string;
510
+ /** Maximum number of iterations (default: MAX_ITERATIONS). */
511
+ maxIterations?: number;
512
+ /** Total wall-clock timeout in ms (default: TIMEOUT_MS). */
513
+ timeoutMs?: number;
514
+ /** Per-iteration stall timeout in ms (default: STALL_MS). */
515
+ stallMs?: number;
516
+ /** Struggle threshold — consecutive zero-progress iterations (default: STRUGGLE_THRESHOLD). */
517
+ struggleThreshold?: number;
518
+ /**
519
+ * Optional lane id for parallel-lane execution (item 3.4). When set,
520
+ * all childLogger names are scoped under `autopilot.loop.lane.<id>`
521
+ * (and tool/stream/status equivalents) so pino emits a `name` field
522
+ * the user-facing formatter can prefix as `[lane-N]`. Status snapshots
523
+ * also include this id so status.ts can render multi-lane state.
524
+ * When unset (default), logging is scoped at `autopilot.loop` as before.
525
+ */
526
+ laneId?: string;
527
+ /**
528
+ * Webhook URL to POST lifecycle events to (optional).
529
+ * Supports plain webhooks and Slack incoming webhooks (auto-detected).
530
+ */
531
+ notifyUrl?: string;
532
+ /**
533
+ * When true, the agent adapter is NOT shut down in the finally block.
534
+ * Instead, the adapter + handle are exposed on the LoopResult so the
535
+ * caller can reuse them (e.g., for the debrief). The caller is
536
+ * responsible for calling `adapter.shutdown(handle)`.
537
+ * Default: false.
538
+ */
539
+ keepAlive?: boolean;
540
+ /**
541
+ * Pre-created logger to reuse. When provided, the loop skips creating
542
+ * its own `createAutopilotLogger` and uses this one instead. This lets
543
+ * the entire autopilot session (enrichment + loop + debrief) share a
544
+ * single log file.
545
+ */
546
+ logger?: AutopilotLogger;
547
+ /**
548
+ * Optional event emitter for typed SessionEvents (Channel 1).
549
+ * When provided, the loop emits iteration:start, iteration:done,
550
+ * tool:call, cost:update, error, and credential:expired events.
551
+ * The pino logger is kept as a verbose debug channel.
552
+ */
553
+ emitter?: SessionEventEmitter;
554
+ /**
555
+ * Agent adapter to use for driving the AI agent.
556
+ * Required in production — the CLI injects the OpenCode adapter.
557
+ * Optional only when _deps.startServer is provided (legacy test path).
558
+ */
559
+ adapter?: AgentAdapter;
560
+ }
561
+ /**
562
+ * Run the Ralph loop: send prompt → wait for idle → inspect response →
563
+ * retry or exit.
564
+ */
565
+ declare function runRalphLoop(opts: RalphLoopOptions): Promise<LoopResult>;
566
+
567
+ /**
568
+ * Shared type definitions for the loop session orchestrator and plan session.
569
+ * Defined here so both loop-session.ts, plan-session.ts (in autopilot) and
570
+ * the CLI interactive command can import them without circular dependencies.
571
+ */
572
+
573
+ interface PlanSessionOptions {
574
+ scopePath: string;
575
+ planDir: string;
576
+ slug: string;
577
+ }
578
+ interface PlanSessionResult {
579
+ planPath: string;
580
+ }
581
+ interface LoopSessionOptions {
582
+ planPath: string;
583
+ cwd: string;
584
+ /** When true, use the fast executor agent (mid-execute tier). */
585
+ fast?: boolean;
586
+ /**
587
+ * When true, read `<cwd>/.agent/autopilot-checkpoint.json` and skip
588
+ * phases already listed in `completedPhases` (provided the checkpoint's
589
+ * `planPath` matches the current `--plan`). On full completion the
590
+ * checkpoint is deleted.
591
+ */
592
+ resume?: boolean;
593
+ /**
594
+ * Per-phase iteration budget override. When unset, defaults are
595
+ * picked by tier (see MAX_ITERATIONS_PER_PHASE_BY_TIER):
596
+ * - deep: 5
597
+ * - mid-execute / autopilot-execute: 10
598
+ * - fast: 10
599
+ * A phase that hits `max-iterations` is treated as a soft failure:
600
+ * checkpoint is written, a warning is logged, and the run continues
601
+ * to the next phase rather than terminating.
602
+ */
603
+ maxIterationsPerPhase?: number;
604
+ /**
605
+ * Maximum number of retry attempts per phase when verify fails.
606
+ * Default: 3. Set to 1 to disable retries (single attempt per phase).
607
+ */
608
+ maxPhaseRetries?: number;
609
+ /**
610
+ * Number of parallel lanes for phase execution (item 3.3).
611
+ * Default: 1 (sequential — preserves the original semantics exactly).
612
+ * When > 1 AND the conflict graph reveals at least one independent
613
+ * pair, phases are dispatched to per-lane git worktrees and merged
614
+ * back on completion (items 3.2 + 3.6). When ≤ 1 OR no parallelism
615
+ * is possible (every phase conflicts with every other), the runner
616
+ * falls back to the sequential path (item 3.7) — no worktree overhead.
617
+ */
618
+ parallel?: number;
619
+ /**
620
+ * Auto-ship after all phases pass (item 4.7). When true, the runner
621
+ * pushes the current branch and opens a PR via `gh pr create` after
622
+ * verify passes and a changeset has been generated. When false (the
623
+ * default), the runner stops at "all phases complete, run `/ship` to
624
+ * finalize." Push targets and the no-force/no-merge invariants are
625
+ * enforced inside `auto-ship.ts`.
626
+ */
627
+ ship?: boolean;
628
+ /**
629
+ * Pre-created logger shared across the entire autopilot session
630
+ * (enrichment + loop + debrief). When provided, the loop reuses
631
+ * this logger instead of creating its own.
632
+ */
633
+ logger?: AutopilotLogger;
634
+ /**
635
+ * Optional event emitter for typed SessionEvents (Channel 1).
636
+ * When provided, loop-session.ts emits phase:start, phase:done,
637
+ * verify:*, and error events. The emitter is also forwarded to
638
+ * runRalphLoop so iteration-level events flow through.
639
+ */
640
+ emitter?: SessionEventEmitter;
641
+ /**
642
+ * Agent adapter to use for driving the AI agent.
643
+ * Required in production — the CLI injects the OpenCode adapter.
644
+ */
645
+ adapter?: AgentAdapter;
646
+ /**
647
+ * Optional abort signal for graceful shutdown. When the signal fires,
648
+ * the loop writes a checkpoint and returns with exitReason: "aborted"
649
+ * at the next phase boundary. Mid-tool-call iterations are not
650
+ * interrupted — the abort is checked between phases only.
651
+ */
652
+ signal?: AbortSignal;
653
+ }
654
+
655
+ interface SessionRunnerOptions {
656
+ planPath: string;
657
+ cwd: string;
658
+ fast?: boolean;
659
+ resume?: boolean;
660
+ parallel?: number;
661
+ ship?: boolean;
662
+ maxIterationsPerPhase?: number;
663
+ /**
664
+ * The agent adapter (e.g. OpenCodeAdapter). Required for production use.
665
+ * The adapter drives the actual agent CLI — start server, create session,
666
+ * send prompts, wait for idle.
667
+ */
668
+ adapter?: AgentAdapter;
669
+ /**
670
+ * Path for the NDJSON event stream file.
671
+ * Defaults to `<cwd>/.agent/autopilot-events.jsonl`.
672
+ */
673
+ eventStreamPath?: string;
674
+ /**
675
+ * Injectable dependencies for testing.
676
+ * @internal
677
+ */
678
+ _deps?: SessionRunnerDeps;
679
+ }
680
+ interface SessionResult {
681
+ planPath: string;
682
+ loopResult: LoopResult;
683
+ }
684
+ /**
685
+ * Injectable dependencies for testing.
686
+ * @internal
687
+ */
688
+ interface SessionRunnerDeps {
689
+ /** Override enrichPlanForFastModel for testing. */
690
+ enrichPlan?: (cwd: string, planPath: string, logger?: AutopilotLogger) => Promise<void>;
691
+ /** Override runLoopSession for testing. */
692
+ runLoopSession?: (opts: LoopSessionOptions & {
693
+ _deps?: unknown;
694
+ }) => Promise<LoopResult>;
695
+ /** Override createAutopilotLogger for testing. */
696
+ createLogger?: (opts: {
697
+ cwd: string;
698
+ }) => AutopilotLogger;
699
+ /** Override EventStreamWriter constructor for testing. */
700
+ createWriter?: (filePath: string) => EventStreamWriter;
701
+ }
702
+ /**
703
+ * Typed EventEmitter for SessionEvents. Wraps Node's EventEmitter with a
704
+ * typed `emit` method so callers get type-checked event payloads.
705
+ */
706
+ declare class SessionEventEmitter extends EventEmitter {
707
+ emitEvent(event: SessionEvent): void;
708
+ }
709
+ declare class SessionRunner {
710
+ /** Channel 1: in-process event emitter. Subscribe to event types or "event" for all. */
711
+ readonly events: SessionEventEmitter;
712
+ private readonly opts;
713
+ private _abortController;
714
+ private _abortCount;
715
+ constructor(opts: SessionRunnerOptions);
716
+ /**
717
+ * Request graceful shutdown. First call signals the running loop session
718
+ * to stop at the next phase boundary (checkpoint is written). Second call
719
+ * force-exits via process.exit(1).
720
+ */
721
+ abort(): void;
722
+ /**
723
+ * Run the autopilot session:
724
+ * 1. Emit session:start
725
+ * 2. If --fast, run enrichment (emitting enrich:* events)
726
+ * 3. Run the loop session (emitting phase:* events via loop-session.ts)
727
+ * 4. Emit session:done
728
+ *
729
+ * Returns a SessionResult with the final LoopResult.
730
+ */
731
+ run(): Promise<SessionResult>;
732
+ }
733
+
734
+ /**
735
+ * Loop session runner for the interactive autopilot orchestrator.
736
+ *
737
+ * For multi-file plans (directory with main.md + phase_N.md files):
738
+ * - Reads main.md to extract Goal and Constraints sections
739
+ * - Detects unchecked phase files from the ## Phases section
740
+ * - Creates a fresh runRalphLoop session per phase with a prompt
741
+ * containing Goal + Constraints + full phase file contents
742
+ * - After each successful phase, updates main.md's phase checkbox
743
+ * - Stops early if a phase exits with struggle/stall/error/timeout
744
+ *
745
+ * For single-file plans (.md file):
746
+ * - Unchanged behavior: single runRalphLoop call with a direct prompt
747
+ */
748
+
749
+ /**
750
+ * Injectable dependencies for testing.
751
+ * @internal
752
+ */
753
+ interface LoopSessionDeps {
754
+ runRalphLoop?: (opts: RalphLoopOptions) => Promise<LoopResult>;
755
+ /** Override filesystem stat check for testing. */
756
+ isDirectory?: (p: string) => boolean;
757
+ /** Override fs.readFileSync for testing. */
758
+ readFileSync?: (p: string) => string;
759
+ /** Override fs.writeFileSync for testing. */
760
+ writeFileSync?: (p: string, content: string) => void;
761
+ /**
762
+ * Override the post-phase verify-command runner (item 4.1) for tests
763
+ * that don't want to actually shell out. Default: real implementation
764
+ * from `verify-runner.ts`.
765
+ */
766
+ runVerifyCommands?: typeof runVerifyCommands;
767
+ }
768
+ /**
769
+ * Run a headless loop session against a plan.
770
+ *
771
+ * Detects whether planPath is a directory (multi-file plan) or a file
772
+ * (single-file plan) and shapes the prompt accordingly, then delegates
773
+ * to runRalphLoop.
774
+ */
775
+ declare function runLoopSession(opts: LoopSessionOptions & {
776
+ _deps?: LoopSessionDeps;
777
+ }): Promise<LoopResult>;
778
+
779
+ /**
780
+ * Plan enrichment for fast-model execution.
781
+ *
782
+ * When --fast is used, this module reads the markdown plan files and
783
+ * generates `spec/*.yaml` files from them. The spec IS the enriched
784
+ * artifact — one LLM pass per file that reads the markdown + codebase
785
+ * and produces structured YAML with enrichment context included:
786
+ * - mirror: references to similar existing files (pattern-match targets)
787
+ * - context: key function signatures, 10-20 lines of code for modified files
788
+ * - conventions: import style, export pattern, test framework, naming
789
+ *
790
+ * Per-file iteration: each plan file gets its own fresh session and its
791
+ * own short-context prompt. This keeps each enrichment call's context
792
+ * small and lets a SIGINT halfway through preserve the already-generated
793
+ * spec files. Whole-plan single-pass enrichment is gone — the cost
794
+ * savings of context-locality more than compensate for the per-file
795
+ * session overhead.
796
+ *
797
+ * Idempotency (item 4.3): before opening any session, check whether
798
+ * spec/ already exists with all items enriched (100% threshold). If so,
799
+ * skip the entire pass. This saves an Opus call on re-runs of `--fast`
800
+ * against an already-enriched plan.
801
+ */
802
+
803
+ /**
804
+ * Enrich a plan for fast-model execution by generating spec/*.yaml files.
805
+ *
806
+ * For multi-file plans (directory with main.md), generates a spec YAML file
807
+ * for each markdown plan file in its own session. For single-file plans,
808
+ * generates a spec file for that file. Each per-file session is independent —
809
+ * failures in one file are logged and skipped, the loop moves to the next.
810
+ *
811
+ * Idempotency: if spec/ already exists with all items fully enriched,
812
+ * enriched, the entire pass is skipped. Per-file: if a spec YAML already
813
+ * exists and is sufficiently enriched, that file is skipped.
814
+ */
815
+ declare function enrichPlanForFastModel(cwd: string, planPath: string, logger?: AutopilotLogger, emitter?: SessionEventEmitter, adapter?: AgentAdapter): Promise<void>;
816
+
817
+ interface ValidationResult {
818
+ valid: boolean;
819
+ errors: string[];
820
+ }
821
+ /**
822
+ * Validate a parsed main spec object. Returns structured errors rather
823
+ * than throwing so callers can surface clear messages.
824
+ */
825
+ declare function validateMainSpec(raw: unknown): ValidationResult;
826
+ /**
827
+ * Validate a parsed phase spec object. Returns structured errors rather
828
+ * than throwing so callers can surface clear messages.
829
+ */
830
+ declare function validatePhaseSpec(raw: unknown): ValidationResult;
831
+
832
+ /**
833
+ * YAML spec parser for the autopilot engine.
834
+ *
835
+ * Reads spec/main.yaml and spec/<phase>.yaml files, validates them
836
+ * against the schema, and converts to the canonical PlanState/PlanItem
837
+ * types used throughout the autopilot.
838
+ *
839
+ * Never throws — all errors degrade to zero-count results or empty
840
+ * arrays so callers can fall back gracefully.
841
+ */
842
+
843
+ /**
844
+ * Check whether a plan directory has a YAML spec (spec/main.yaml exists).
845
+ * This is the gate that determines whether to use the YAML path or fall
846
+ * back to the markdown parser.
847
+ */
848
+ declare function hasSpec(planDir: string): boolean;
849
+ /**
850
+ * Parse a phase YAML file and return PlanItem[].
851
+ * Returns [] on any error (missing file, invalid YAML, schema failure).
852
+ */
853
+ declare function parseSpecItems(phasePath: string): Array<PlanItem & {
854
+ mirror?: string;
855
+ context?: string;
856
+ conventions?: string;
857
+ }>;
858
+ /**
859
+ * Detect phase files from spec/main.yaml. Returns sorted list of phase
860
+ * filenames (e.g., ["wave_0.yaml", "wave_1.yaml"]).
861
+ */
862
+ declare function detectSpecPhases(planDir: string): string[];
863
+ /**
864
+ * Read the goal field from spec/main.yaml. Returns "" on failure.
865
+ */
866
+ declare function readSpecGoal(planDir: string): string;
867
+ /**
868
+ * Read the constraints field from spec/main.yaml. Returns "" on failure.
869
+ */
870
+ declare function readSpecConstraints(planDir: string): string;
871
+ /**
872
+ * Filter a list of phase filenames to only those not yet completed.
873
+ * Uses yaml.parse() on spec/main.yaml to read the `completed` field
874
+ * on each phase entry — no hand-rolled regex.
875
+ *
876
+ * Returns `phaseFiles` unchanged on any error (fail-open so the caller
877
+ * can still attempt all phases rather than silently skipping them).
878
+ */
879
+ declare function filterUncheckedSpecPhases(phaseFiles: string[], planDir: string): string[];
880
+
881
+ /**
882
+ * Mark a phase as completed in spec/main.yaml.
883
+ *
884
+ * @param planDir - The plan directory (parent of spec/)
885
+ * @param phaseFile - The phase filename to mark completed (e.g., "wave_0.yaml")
886
+ */
887
+ declare function markPhaseCompleted(planDir: string, phaseFile: string): void;
888
+
889
+ /**
890
+ * Autopilot run checkpoint persistence.
891
+ *
892
+ * After each phase completes, the multi-phase loop writes a checkpoint
893
+ * to `<cwd>/.agent/autopilot-checkpoint.json`. On startup with
894
+ * `--resume` (or whenever the checkpoint matches the current `--plan`),
895
+ * the loop skips already-completed phases and continues from the next
896
+ * unchecked one.
897
+ *
898
+ * On successful run completion (all phases done), the checkpoint is
899
+ * deleted.
900
+ *
901
+ * Design notes:
902
+ * - Atomic write via tmp-file + rename (mirrors cost-tracker's rollup
903
+ * pattern at src/plugins/cost-tracker.ts lines 280-305).
904
+ * - Read failures (corrupt JSON, missing file, perm denial) return
905
+ * `null`. This module never throws — failure modes are silent.
906
+ * - The checkpoint is purely advisory: callers must validate the
907
+ * `planPath` matches the current run's plan before trusting any
908
+ * `completedPhases` list.
909
+ */
910
+ interface Checkpoint {
911
+ /**
912
+ * Absolute path to the plan (file or directory) that this checkpoint
913
+ * was created for. Resume only valid when the current --plan matches.
914
+ */
915
+ planPath: string;
916
+ /** Phase filenames that have completed (e.g. ["wave_1.md"]). */
917
+ completedPhases: string[];
918
+ /** Cumulative cost across completed phases, in USD. */
919
+ totalCostUsd: number;
920
+ /** Cumulative iteration count across completed phases. */
921
+ totalIterations: number;
922
+ /** ISO 8601 timestamp of the last write. */
923
+ timestamp: string;
924
+ }
925
+ /**
926
+ * Injectable filesystem deps for testing. Production code should call
927
+ * the exported functions without `_deps`; tests inject mocks.
928
+ *
929
+ * @internal
930
+ */
931
+ interface CheckpointDeps {
932
+ readFileSync?: (p: string) => string;
933
+ writeFileSync?: (p: string, content: string) => void;
934
+ unlinkSync?: (p: string) => void;
935
+ renameSync?: (from: string, to: string) => void;
936
+ existsSync?: (p: string) => boolean;
937
+ mkdirSync?: (p: string, opts: {
938
+ recursive: boolean;
939
+ }) => void;
940
+ }
941
+ /**
942
+ * Atomically write the checkpoint file. Never throws — write errors
943
+ * are swallowed (best-effort persistence; the run continues even if
944
+ * the checkpoint can't be persisted).
945
+ */
946
+ declare function writeCheckpoint(cwd: string, state: Checkpoint, deps?: CheckpointDeps): void;
947
+ /**
948
+ * Read and parse the checkpoint file. Returns `null` if the file is
949
+ * missing, unreadable, or contains corrupt JSON / wrong shape.
950
+ */
951
+ declare function readCheckpoint(cwd: string, deps?: CheckpointDeps): Checkpoint | null;
952
+ /**
953
+ * Delete the checkpoint file. Silent on missing / permission errors.
954
+ */
955
+ declare function deleteCheckpoint(cwd: string, deps?: CheckpointDeps): void;
956
+
957
+ /**
958
+ * Default budgets and constants for the Ralph loop autopilot engine.
959
+ */
960
+ /** Maximum number of loop iterations before giving up. */
961
+ declare const MAX_ITERATIONS = 50;
962
+ /**
963
+ * Number of consecutive zero-progress iterations before the loop
964
+ * declares the agent is struggling and exits.
965
+ */
966
+ declare const STRUGGLE_THRESHOLD = 3;
967
+ /** Total wall-clock timeout for one `glrs oc autopilot` invocation (4 hours). */
968
+ declare const TIMEOUT_MS: number;
969
+ /**
970
+ * Per-iteration stall timeout, keyed by model tier. If a single
971
+ * iteration produces no idle signal within the tier's window,
972
+ * something is broken.
973
+ *
974
+ * Deep models (Opus, Sonnet thinking) get the longest window — they're
975
+ * permitted to chew on a hard problem for a while. Mid-tier executors
976
+ * are faster and a long stall there is much more likely to be a stuck
977
+ * call than a productive think. Fast models are the most responsive
978
+ * and warrant the shortest window.
979
+ *
980
+ * The CLI accepts `--stall-timeout <ms>` to override the tier-default.
981
+ */
982
+ declare const STALL_MS_BY_TIER: {
983
+ readonly deep: number;
984
+ readonly mid: number;
985
+ readonly "mid-execute": number;
986
+ readonly "autopilot-execute": number;
987
+ readonly fast: number;
988
+ };
989
+ /** Backwards-compatible default (used when no tier is resolved). */
990
+ declare const STALL_MS: number;
991
+ /** Phase-level iteration budgets, keyed by model tier (item 2.7). */
992
+ declare const MAX_ITERATIONS_PER_PHASE_BY_TIER: {
993
+ readonly deep: 5;
994
+ readonly mid: 8;
995
+ readonly "mid-execute": 10;
996
+ readonly "autopilot-execute": 10;
997
+ readonly fast: 10;
998
+ };
999
+ /**
1000
+ * Status-heartbeat interval. Every N milliseconds the loop emits an
1001
+ * info-level "working" log line summarizing elapsed time, iteration
1002
+ * count, and cumulative cost. Default: 5 minutes.
1003
+ * Override via GLRS_AUTOPILOT_STATUS_INTERVAL_MS (parsed as integer,
1004
+ * clamped to [1000, 1h]).
1005
+ */
1006
+ declare const STATUS_INTERVAL_MS: number;
1007
+
1008
+ /**
1009
+ * Status-heartbeat helper for the autopilot loop.
1010
+ *
1011
+ * Fires a periodic "still working" summary log at info level every N
1012
+ * milliseconds (see STATUS_INTERVAL_MS in config.ts). The summary
1013
+ * reports:
1014
+ *
1015
+ * - elapsed wall time since loop start
1016
+ * - iteration count (completed iterations, not current)
1017
+ * - cumulative session cost in USD (sampled via session.get().data.cost)
1018
+ * - a 1-line "working successfully" signal (or a warning if the last
1019
+ * iteration hit an error)
1020
+ *
1021
+ * Decoupled from the Ralph loop's iteration accounting — the heartbeat
1022
+ * reads a shared state snapshot that the loop updates between iterations.
1023
+ * Fires on a setInterval timer, not per-iteration, so the user sees
1024
+ * activity even during long single-iteration sessions (PRIME streaming
1025
+ * text / running a slow test suite / waiting on an LLM response).
1026
+ */
1027
+
1028
+ interface StatusState {
1029
+ /** Wall-clock timestamp when the loop started. */
1030
+ startedAt: number;
1031
+ /** Completed iteration count (not the current in-flight iteration). */
1032
+ iterationsCompleted: number;
1033
+ /** Latest cumulative session cost in USD, sampled at iteration boundaries. */
1034
+ cumulativeCostUsd: number;
1035
+ /** Whether the most recent iteration made filesystem progress. */
1036
+ lastIterationProgress: boolean;
1037
+ /** Whether the most recent iteration errored. */
1038
+ lastIterationErrored: boolean;
1039
+ /**
1040
+ * When true, `cumulativeCostUsd` is an estimate from token counts
1041
+ * (not API-reported). Displayed as "~$X.XXX est".
1042
+ */
1043
+ costIsEstimated?: boolean;
1044
+ /** Total number of phases in the plan (multi-file plans only). */
1045
+ phaseCount?: number;
1046
+ /** Number of phases completed so far. */
1047
+ phasesCompleted?: number;
1048
+ /** Total checkbox items in main.md (multi-file plans only). */
1049
+ mainCheckboxesTotal?: number;
1050
+ /** Checked checkbox items in main.md. */
1051
+ mainCheckboxesCompleted?: number;
1052
+ /**
1053
+ * Active parallel lanes (item 3.4). Only set when the orchestrator is
1054
+ * running > 1 lane; the sequential path leaves this undefined and
1055
+ * `composeStatusMessage` falls back to the single-stream output.
1056
+ */
1057
+ lanes?: Record<string, LaneState>;
1058
+ }
1059
+ interface LaneState {
1060
+ /** Phase filename currently running on this lane. */
1061
+ phaseFile: string;
1062
+ /** Iteration number within the lane's current phase (1-based). */
1063
+ iteration: number;
1064
+ /** Most-recent tool name observed on the lane (optional). */
1065
+ lastTool?: string;
1066
+ }
1067
+ interface StatusHeartbeat {
1068
+ /** Start the heartbeat timer. Idempotent — safe to call multiple times. */
1069
+ start(): void;
1070
+ /** Stop the timer. Safe to call if not started. */
1071
+ stop(): void;
1072
+ /** Update the shared state. Call between iterations. */
1073
+ update(patch: Partial<StatusState>): void;
1074
+ /** Read the current state snapshot (for testing). */
1075
+ getState(): StatusState;
1076
+ }
1077
+ interface StatusHeartbeatOptions {
1078
+ logger: Logger;
1079
+ intervalMs: number;
1080
+ /** Optional async function to poll current session cost. Called on
1081
+ * each heartbeat tick. If provided, the returned cost replaces
1082
+ * the heartbeat's cumulativeCostUsd on each tick. */
1083
+ pollCost?: () => Promise<number>;
1084
+ /**
1085
+ * Optional path to write the status snapshot as JSON on each tick.
1086
+ * Written atomically via tmp-file-then-rename. Non-fatal on error.
1087
+ * Default: undefined (no file write).
1088
+ */
1089
+ statusFilePath?: string;
1090
+ /** Test-only: inject clock and timer functions. */
1091
+ _deps?: {
1092
+ now?: () => number;
1093
+ setInterval?: (handler: () => void, ms: number) => ReturnType<typeof setInterval>;
1094
+ clearInterval?: (id: ReturnType<typeof setInterval>) => void;
1095
+ };
1096
+ }
1097
+ /**
1098
+ * Format elapsed milliseconds as "Xh Ym Zs" (Xh is omitted when 0).
1099
+ * Public for testing.
1100
+ */
1101
+ declare function formatElapsed(ms: number): string;
1102
+ /**
1103
+ * Format cost as USD with 3 decimal places, or "$0.00" for zero.
1104
+ * When `estimated` is true, prepends "~" and appends " est" to indicate
1105
+ * the cost is an estimate (e.g., from token counts when API cost is unavailable).
1106
+ * Public for testing.
1107
+ */
1108
+ declare function formatCost(usd: number, estimated?: boolean): string;
1109
+ declare function createStatusHeartbeat(opts: StatusHeartbeatOptions): StatusHeartbeat;
1110
+
1111
+ /**
1112
+ * Sentinel detection for the Ralph loop autopilot engine.
1113
+ *
1114
+ * The agent emits `<autopilot-done>` as a standalone tag (not inside a
1115
+ * code fence or inline backtick) to signal that all work is complete.
1116
+ */
1117
+ /**
1118
+ * Returns true if the text contains the `<autopilot-done>` sentinel tag
1119
+ * outside of any code fence (``` ... ```) or inline backtick span.
1120
+ *
1121
+ * Detection rules:
1122
+ * - Case-sensitive: `<AUTOPILOT-DONE>` does NOT match.
1123
+ * - Tag inside a fenced code block (``` ... ```) does NOT match.
1124
+ * - Tag inside an inline backtick span (` ... `) does NOT match.
1125
+ * - Partial tags (`<autopilot-done` or `autopilot-done>`) do NOT match.
1126
+ */
1127
+ declare function detectSentinel(text: string): boolean;
1128
+
1129
+ /**
1130
+ * Struggle detection for the Ralph loop autopilot engine.
1131
+ *
1132
+ * Tracks consecutive zero-progress iterations. "Progress" is defined as
1133
+ * at least one filesystem write (non-empty `git diff --stat` output)
1134
+ * during the iteration.
1135
+ */
1136
+ /**
1137
+ * Tracks consecutive zero-progress iterations and signals when the
1138
+ * agent is struggling (no progress for `threshold` consecutive iterations).
1139
+ */
1140
+ declare class StruggleDetector {
1141
+ private _consecutiveStalls;
1142
+ private readonly _threshold;
1143
+ constructor(threshold: number);
1144
+ /** Number of consecutive stall iterations recorded so far. */
1145
+ get consecutiveStalls(): number;
1146
+ /**
1147
+ * Record the result of one iteration.
1148
+ * @param madeProgress - true if the agent made filesystem changes this iteration.
1149
+ */
1150
+ record(madeProgress: boolean): void;
1151
+ /**
1152
+ * Returns true if the agent has stalled for `threshold` consecutive
1153
+ * iterations without making progress.
1154
+ */
1155
+ isStruggling(): boolean;
1156
+ }
1157
+ /**
1158
+ * Returns true if the kill-switch file exists at `.agent/autopilot-disable`
1159
+ * relative to `cwd`. The loop should exit immediately when this returns true.
1160
+ */
1161
+ declare function checkKillSwitch(cwd: string): boolean;
1162
+
1163
+ /**
1164
+ * Phase-level git safety: record-and-soft-reset helpers.
1165
+ *
1166
+ * Used by the multi-phase loop runner (loop-session.ts) to capture
1167
+ * HEAD before each phase. If a phase fails (struggle / stall / error)
1168
+ * and the user requested it (or running in --fast mode where there's
1169
+ * no human to ask), `resetSoft` rolls the worktree back to that SHA,
1170
+ * preserving any changes in the index so nothing is lost.
1171
+ *
1172
+ * Safety invariants (per AGENTS.md and Wave 2 plan):
1173
+ * - NEVER use `git reset --hard`. Soft reset only.
1174
+ * - Any git failure here is non-fatal — log and continue.
1175
+ * - Helpers use `execFile` from node:child_process via promisify.
1176
+ */
1177
+ /**
1178
+ * Injectable execFile for testing.
1179
+ * @internal
1180
+ */
1181
+ interface GitSafetyDeps {
1182
+ /**
1183
+ * Mock the git invocation. Receives the args array; returns
1184
+ * { stdout, stderr } or throws to simulate a git failure.
1185
+ */
1186
+ execGit?: (args: string[], cwd: string) => Promise<{
1187
+ stdout: string;
1188
+ stderr: string;
1189
+ }>;
1190
+ }
1191
+ /**
1192
+ * Record the current HEAD SHA. Returns "HEAD" on any failure
1193
+ * (matches the existing getHeadSha shape in loop.ts so callers can
1194
+ * pass the result to git operations without crashing).
1195
+ */
1196
+ declare function recordHead(cwd: string, deps?: GitSafetyDeps): Promise<string>;
1197
+ /**
1198
+ * Soft-reset the worktree to the given SHA. Changes since `sha` move
1199
+ * to the staging area; nothing is destroyed. Any failure is logged
1200
+ * via the optional `onWarn` callback and swallowed.
1201
+ *
1202
+ * Returns true on success, false on failure.
1203
+ */
1204
+ declare function resetSoft(cwd: string, sha: string, opts?: GitSafetyDeps & {
1205
+ onWarn?: (msg: string) => void;
1206
+ }): Promise<boolean>;
1207
+
1208
+ /**
1209
+ * Conflict-graph builder for parallel phase scheduling.
1210
+ *
1211
+ * Two phases conflict if they share any file path in their `files:` lists
1212
+ * (parsed via `plan-parser`'s `parseItems` from each phase's plan-state
1213
+ * fence). Phases with no shared files are independent and can run in
1214
+ * parallel lanes.
1215
+ *
1216
+ * Conservative fallback: when a phase yields zero parsed items (e.g., no
1217
+ * fenced plan-state block, or the phase is written as plain markdown
1218
+ * checkboxes with `mirror:` / `files:` as prose) it is treated as
1219
+ * conflicting with EVERY other phase. The runtime then collapses to
1220
+ * sequential execution for that phase — which preserves correctness at
1221
+ * the cost of opportunistic parallelism. Explicit, machine-parseable
1222
+ * `files:` entries inside a plan-state fence are required to unlock
1223
+ * parallel scheduling.
1224
+ *
1225
+ * Pure module — no I/O, no logging. Inputs are already-parsed PlanItems.
1226
+ */
1227
+
1228
+ interface PhaseInput {
1229
+ /** Phase filename (e.g., "wave_1.md"). */
1230
+ file: string;
1231
+ /** Items parsed from this phase's plan-state fence. */
1232
+ items: PlanItem[];
1233
+ }
1234
+ interface ConflictGraph {
1235
+ /** Phase filenames in input order. */
1236
+ phases: string[];
1237
+ /** Map: phase -> set of phases it conflicts with. */
1238
+ conflicts: Map<string, Set<string>>;
1239
+ }
1240
+ /**
1241
+ * Build a conflict graph from a list of phases.
1242
+ *
1243
+ * Two phases A and B conflict iff:
1244
+ * - either phase has unknown file footprint (null from collectPhaseFiles), OR
1245
+ * - their file sets share at least one path.
1246
+ *
1247
+ * Self-conflicts are not recorded (a phase doesn't conflict with itself).
1248
+ */
1249
+ declare function buildConflictGraph(phases: PhaseInput[]): ConflictGraph;
1250
+ /**
1251
+ * Convenience: returns true if the graph has any independent pair —
1252
+ * i.e., at least one group from `findIndependentPhases` has size > 1.
1253
+ * Used by the orchestrator to decide whether parallel scheduling can
1254
+ * yield any speedup, vs. falling back to sequential to skip overhead.
1255
+ */
1256
+ declare function hasParallelism(graph: ConflictGraph): boolean;
1257
+
1258
+ /**
1259
+ * Parallel-lane orchestrator (item 3.3).
1260
+ *
1261
+ * Replaces the sequential per-phase loop in `loop-session.ts` with an
1262
+ * N-lane scheduler. Each lane runs one phase at a time. When a lane
1263
+ * finishes, it picks up the next phase in the queue that does NOT
1264
+ * conflict (per the conflict graph from item 3.1) with any currently-
1265
+ * running phase. Phases that conflict with all currently-running phases
1266
+ * wait in the queue.
1267
+ *
1268
+ * Pure scheduler — no I/O, no git, no worktree creation. The caller
1269
+ * provides a `runPhase` callback that takes a phase filename plus a
1270
+ * lane id and returns a `PhaseResult`. Worktree create/merge/cleanup
1271
+ * (item 3.2) lives at the call site, not here.
1272
+ *
1273
+ * Sequential fallback (item 3.7): when `laneCount === 1`, the
1274
+ * orchestrator processes phases strictly in the input order, one at a
1275
+ * time — semantically identical to the original `for (const phase of
1276
+ * uncheckedPhases)` loop.
1277
+ *
1278
+ * Cancellation: an injected `AbortSignal` aborts pending phases (no
1279
+ * new phases are dispatched) but does not interrupt phases already in
1280
+ * flight — that's the runPhase callback's responsibility.
1281
+ */
1282
+
1283
+ interface PhaseResult {
1284
+ /** Phase filename (e.g., "wave_1.md"). */
1285
+ phaseFile: string;
1286
+ /** Lane id this phase ran on. */
1287
+ laneId: string;
1288
+ /** Whether the phase completed successfully (all items checked). */
1289
+ ok: boolean;
1290
+ /**
1291
+ * Iterations consumed by this phase. Aggregated by the caller into
1292
+ * a session total.
1293
+ */
1294
+ iterations: number;
1295
+ /** Cost in USD attributed to this phase. */
1296
+ costUsd: number;
1297
+ /** Optional structured payload returned by `runPhase`. */
1298
+ payload?: unknown;
1299
+ /**
1300
+ * When `ok === false`, this signals that the orchestrator should
1301
+ * stop scheduling new phases (the caller's contract is "first failure
1302
+ * stops the run", matching the sequential loop's behavior).
1303
+ */
1304
+ fatal?: boolean;
1305
+ }
1306
+ interface RunLanesOptions {
1307
+ /** Phases to run, in queue order. */
1308
+ phases: string[];
1309
+ /** Conflict graph from item 3.1. */
1310
+ conflictGraph: ConflictGraph;
1311
+ /**
1312
+ * Number of parallel lanes. `1` falls back to sequential semantics
1313
+ * (no overlap, phases dispatched in `phases` order).
1314
+ */
1315
+ laneCount: number;
1316
+ /**
1317
+ * Run a single phase. Receives the phase filename plus the lane id
1318
+ * the caller should use for logging / worktree naming. Must return a
1319
+ * `PhaseResult`. Errors thrown here propagate up to `runLanes`'s
1320
+ * caller — the orchestrator does not catch them.
1321
+ */
1322
+ runPhase: (phaseFile: string, laneId: string) => Promise<PhaseResult>;
1323
+ /**
1324
+ * Optional cancellation. When aborted, no new phases are dispatched.
1325
+ * In-flight phases continue until their own runPhase returns.
1326
+ */
1327
+ abortSignal?: AbortSignal;
1328
+ /**
1329
+ * Optional structured logger for scheduling decisions. Tests pass a
1330
+ * recording function; production passes a pino childLogger.
1331
+ */
1332
+ logger?: {
1333
+ info?: (objOrMsg: unknown, msg?: string) => void;
1334
+ debug?: (objOrMsg: unknown, msg?: string) => void;
1335
+ };
1336
+ }
1337
+ interface RunLanesResult {
1338
+ /** Per-phase results, in completion order. */
1339
+ results: PhaseResult[];
1340
+ /** Phases that were skipped because the run aborted. */
1341
+ skipped: string[];
1342
+ }
1343
+ /**
1344
+ * Run phases through N lanes with conflict-aware scheduling.
1345
+ *
1346
+ * Algorithm:
1347
+ * - Maintain a queue of pending phases (input order).
1348
+ * - Maintain a set of currently-running phases.
1349
+ * - Each scheduling step: fill idle lanes by picking the first queue
1350
+ * entry that doesn't conflict with anything running.
1351
+ * - Wait for any lane to finish (Promise.race), record its result,
1352
+ * re-evaluate the queue, repeat.
1353
+ * - Stop scheduling new phases when (a) the abort signal fires, OR
1354
+ * (b) any phase returns `fatal: true`.
1355
+ *
1356
+ * Sequential fallback (`laneCount === 1`): the queue is drained in
1357
+ * order, one phase at a time — `Promise.race` over a single promise
1358
+ * is just `await` semantics.
1359
+ */
1360
+ declare function runLanes(opts: RunLanesOptions): Promise<RunLanesResult>;
1361
+
1362
+ /**
1363
+ * Scope-validator module for the autopilot (item 4.2).
1364
+ *
1365
+ * After each iteration, the orchestrator compares the files the agent
1366
+ * actually touched (`git diff --name-only <baseRef>`) against the
1367
+ * union of files declared in the current phase's plan-state items
1368
+ * (`PlanItem.files[]`). Mismatches are logged as warnings:
1369
+ *
1370
+ * - extra: agent edited a file NOT in the plan → "Scope drift"
1371
+ * - missing: plan expects a file but the agent didn't touch it → "Incomplete"
1372
+ *
1373
+ * Validation is informational — it never blocks the loop. Both arrays
1374
+ * may be empty on a clean iteration.
1375
+ */
1376
+
1377
+ declare const execFileDefault$2: typeof execFile.__promisify__;
1378
+ interface ScopeValidation {
1379
+ /** Files the agent edited that are NOT in the expected list (scope drift). */
1380
+ extra: string[];
1381
+ /** Files the expected list contains but the agent did NOT edit (incomplete). */
1382
+ missing: string[];
1383
+ }
1384
+ /**
1385
+ * Compare two file lists and return the symmetric differences.
1386
+ *
1387
+ * Both inputs are normalized — duplicates removed, sorted alphabetically.
1388
+ * Path comparison is exact (no canonicalization); callers should pass
1389
+ * paths in the same form (e.g. all relative-to-cwd).
1390
+ */
1391
+ declare function validateScope(expected: string[], actual: string[]): ScopeValidation;
1392
+ interface GetChangedFilesOptions {
1393
+ /**
1394
+ * Test-only: inject execFile replacement.
1395
+ * @internal
1396
+ */
1397
+ _deps?: {
1398
+ execFile?: typeof execFileDefault$2;
1399
+ };
1400
+ }
1401
+ /**
1402
+ * Return the list of files changed in `cwd` since `baseRef` (a git SHA
1403
+ * or ref). Uses `git diff --name-only` so committed AND uncommitted
1404
+ * working-tree changes both count.
1405
+ *
1406
+ * Never throws — degrades to `[]` on git failure so callers can treat
1407
+ * "git unavailable" the same as "no files changed".
1408
+ */
1409
+ declare function getChangedFiles(cwd: string, baseRef: string, opts?: GetChangedFilesOptions): Promise<string[]>;
1410
+
1411
+ /**
1412
+ * Plan-validator module for the autopilot (item 4.5).
1413
+ *
1414
+ * Validates the structural integrity of a plan before execution starts:
1415
+ * - main.md exists for directory plans
1416
+ * - every phase file referenced in main.md exists on disk
1417
+ * - every phase file has at least one item with `intent:`
1418
+ * - soft warnings for items missing `files:`, `tests:`, or `verify:`
1419
+ *
1420
+ * Errors block execution (the orchestrator returns early). Warnings are
1421
+ * informational — the run continues so the agent can still patch the
1422
+ * plan in-flight.
1423
+ *
1424
+ * Pure function: only reads `fs` synchronously. Never throws — degrades
1425
+ * to `{ errors: [], warnings: [...] }` on parse failure so callers can
1426
+ * always render the report.
1427
+ */
1428
+ interface ValidationError {
1429
+ /** Stable machine-readable code (e.g., "missing-main", "missing-phase-file"). */
1430
+ code: string;
1431
+ /** Human-readable description of what's wrong. */
1432
+ message: string;
1433
+ /** Plan file path (relative to plan dir or absolute) when applicable. */
1434
+ file?: string;
1435
+ /** Plan-state item id (e.g., "4.1") when applicable. */
1436
+ itemId?: string;
1437
+ }
1438
+ interface ValidationWarning {
1439
+ code: string;
1440
+ message: string;
1441
+ file?: string;
1442
+ itemId?: string;
1443
+ }
1444
+ interface ValidationReport {
1445
+ errors: ValidationError[];
1446
+ warnings: ValidationWarning[];
1447
+ }
1448
+ /**
1449
+ * Validate the plan at `planPath`. Returns a `ValidationReport`.
1450
+ *
1451
+ * For directory plans (path is a directory), checks main.md + each
1452
+ * referenced phase file. For single-file plans, only the soft per-item
1453
+ * checks apply.
1454
+ *
1455
+ * Never throws — any I/O failure becomes a warning rather than a thrown
1456
+ * exception so callers can degrade gracefully.
1457
+ */
1458
+ declare function validatePlan(planPath: string): ValidationReport;
1459
+
1460
+ /**
1461
+ * Git worktree helpers for parallel-lane execution (item 3.2).
1462
+ *
1463
+ * Each parallel lane runs against its own worktree branched from the
1464
+ * current HEAD. On phase completion, the worktree's branch is merged
1465
+ * back into the main repo with `--no-ff` to preserve per-phase commits.
1466
+ *
1467
+ * All git invocations follow the same `execFile + try/catch` shape used
1468
+ * by `loop.ts` (lines 126-146 — see file-level comment in that module).
1469
+ *
1470
+ * Cleanup is best-effort: failures log warnings via the injected logger
1471
+ * but never throw. Orphaned worktrees on disk surface in the debrief
1472
+ * (item 3.6 wiring) so the user can manually `git worktree remove --force`.
1473
+ *
1474
+ * Pure runtime — no test pollution. Tests inject `_deps.execFile` to mock
1475
+ * git invocations.
1476
+ */
1477
+
1478
+ declare const execFileDefault$1: typeof execFile.__promisify__;
1479
+ /**
1480
+ * Minimal logger surface — accepts any subset of pino-like methods so
1481
+ * tests can pass a no-op or a recording array. The real call sites pass
1482
+ * pino childLoggers.
1483
+ */
1484
+ interface WorktreeLogger {
1485
+ warn?: (objOrMsg: unknown, msg?: string) => void;
1486
+ info?: (objOrMsg: unknown, msg?: string) => void;
1487
+ debug?: (objOrMsg: unknown, msg?: string) => void;
1488
+ }
1489
+ interface WorktreeHandle {
1490
+ /** Filesystem path of the new worktree. */
1491
+ path: string;
1492
+ /** Branch name created for this worktree. */
1493
+ branch: string;
1494
+ /**
1495
+ * Best-effort cleanup: `git worktree remove` then `git branch -D`.
1496
+ * Failures are logged via the logger passed to createWorktree and never
1497
+ * thrown. Safe to call multiple times (subsequent calls no-op).
1498
+ */
1499
+ cleanup: () => Promise<void>;
1500
+ }
1501
+ interface CreateWorktreeOptions {
1502
+ /** Slug for the lane — used in branch and path naming. */
1503
+ laneSlug: string;
1504
+ /** Optional logger for cleanup-warning emission. */
1505
+ logger?: WorktreeLogger;
1506
+ /** Test-only: inject execFile replacement. */
1507
+ _deps?: {
1508
+ execFile?: typeof execFileDefault$1;
1509
+ };
1510
+ }
1511
+ interface MergeWorktreeOptions {
1512
+ /** Branch name to merge back into the current HEAD of repoRoot. */
1513
+ branch: string;
1514
+ /** Test-only: inject execFile replacement. */
1515
+ _deps?: {
1516
+ execFile?: typeof execFileDefault$1;
1517
+ };
1518
+ }
1519
+ interface MergeResult {
1520
+ ok: boolean;
1521
+ /** Files reported as conflicting by `git merge`, when ok=false. */
1522
+ conflicts?: string[];
1523
+ }
1524
+ /**
1525
+ * Create a new git worktree branched from the current HEAD of `repoRoot`.
1526
+ *
1527
+ * Path: `<repoRoot>/.agent/worktrees/<laneSlug>-<epoch>`
1528
+ * Branch: `autopilot/<laneSlug>`
1529
+ *
1530
+ * The `.agent/worktrees/` parent matches the existing `.agent/` convention
1531
+ * for kill-switch and status state — keeps lane artefacts grouped with
1532
+ * other autopilot runtime files.
1533
+ *
1534
+ * Throws on failure (the caller decides whether to fall back to sequential).
1535
+ * Cleanup-via-handle is best-effort and logs only.
1536
+ */
1537
+ declare function createWorktree(repoRoot: string, opts: CreateWorktreeOptions): Promise<WorktreeHandle>;
1538
+ /**
1539
+ * Merge a worktree's branch back into the current HEAD of repoRoot.
1540
+ * Uses `git merge --no-ff` so each phase keeps an explicit merge commit
1541
+ * in history, making it trivial to bisect across parallel lanes.
1542
+ *
1543
+ * Returns `{ ok: true }` on success. On merge conflict, returns
1544
+ * `{ ok: false, conflicts: [...] }` and aborts the merge so the
1545
+ * working tree is restored — the caller falls back to sequential
1546
+ * execution for that phase per item 3.2's conventions.
1547
+ */
1548
+ declare function mergeWorktree(repoRoot: string, opts: MergeWorktreeOptions): Promise<MergeResult>;
1549
+
1550
+ /**
1551
+ * Auto-ship module for the autopilot (item 4.7).
1552
+ *
1553
+ * After all phases pass and a changeset has been generated, --ship
1554
+ * pushes the current branch upstream and opens a PR via `gh pr create`.
1555
+ *
1556
+ * Hard rules (mirrored from AGENTS.md and the SPEAR Resolve stage):
1557
+ * - Never `git push --force` or `git push -f`
1558
+ * - Never push to `main` or `master`
1559
+ * - Never `--no-verify`
1560
+ * - Never merge a PR — only open one
1561
+ *
1562
+ * The PR title is the plan's H1 (verbatim). The PR body is the literal
1563
+ * contents of main.md (or the single-file plan), passed to gh via
1564
+ * `--body-file` to dodge shell-escaping bugs.
1565
+ *
1566
+ * Failure modes (all surface as thrown errors so the caller can log
1567
+ * and degrade gracefully):
1568
+ * - Branch is `main`/`master`/detached HEAD → ABORT
1569
+ * - `git push` returns non-zero → ABORT
1570
+ * - `gh` is not installed or not authenticated → ABORT
1571
+ * - PR already exists for the branch → ABORT (call /ship to update)
1572
+ */
1573
+
1574
+ declare const execFileDefault: typeof execFile.__promisify__;
1575
+ interface AutoShipOptions {
1576
+ /** Plan path (file or directory). The PR title comes from the plan's H1. */
1577
+ planPath: string;
1578
+ /** Repo root (used as cwd for git/gh invocations). */
1579
+ repoRoot: string;
1580
+ /**
1581
+ * Test-only: inject execFile replacement.
1582
+ * @internal
1583
+ */
1584
+ _deps?: {
1585
+ execFile?: typeof execFileDefault;
1586
+ };
1587
+ }
1588
+ interface AutoShipResult {
1589
+ prUrl: string;
1590
+ branch: string;
1591
+ /** Title used for the PR. */
1592
+ title: string;
1593
+ }
1594
+ /**
1595
+ * Push the current branch and open a PR. Returns the PR URL on success;
1596
+ * throws on any of the abort conditions documented at the module top.
1597
+ */
1598
+ declare function autoShip(opts: AutoShipOptions): Promise<AutoShipResult>;
1599
+
1600
+ /**
1601
+ * Session state derivation from event streams.
1602
+ *
1603
+ * `deriveState` is a pure function: given a sequence of SessionEvents,
1604
+ * it returns a `SessionHandle` describing the current state of the session.
1605
+ * It handles partial streams (process died mid-session) and all event types.
1606
+ */
1607
+
1608
+ type SessionStatus = "running" | "enriching" | "verifying" | "complete" | "error" | "stale";
1609
+ interface SessionHandle {
1610
+ /** Derived from planPath + startedAt — stable across polls. */
1611
+ id: string;
1612
+ planPath: string;
1613
+ cwd: string;
1614
+ /** fast-model flag from session:start */
1615
+ fast: boolean;
1616
+ /** resume flag from session:start */
1617
+ resume: boolean;
1618
+ status: SessionStatus;
1619
+ currentPhase?: {
1620
+ phase: string;
1621
+ current: number;
1622
+ total: number;
1623
+ };
1624
+ currentIteration?: {
1625
+ iteration: number;
1626
+ max: number;
1627
+ };
1628
+ totalIterations: number;
1629
+ cost: number;
1630
+ startedAt: string;
1631
+ lastEventAt: string;
1632
+ error?: string;
1633
+ exitReason?: string;
1634
+ enrichProgress?: {
1635
+ done: number;
1636
+ total: number;
1637
+ };
1638
+ verifyProgress?: {
1639
+ passed: number;
1640
+ total: number;
1641
+ };
1642
+ }
1643
+ /**
1644
+ * Pure reduce over a sequence of SessionEvents.
1645
+ *
1646
+ * Returns `null` if no `session:start` event is found (empty or pre-start stream).
1647
+ * Handles partial streams: a stream without `session:done` is treated as "running"
1648
+ * (or "enriching"/"verifying" depending on the last active event).
1649
+ */
1650
+ declare function deriveState(events: SessionEvent[]): SessionHandle | null;
1651
+
1652
+ /**
1653
+ * Changeset generator for the autopilot (item 4.6).
1654
+ *
1655
+ * After all phases complete successfully, the autopilot generates a
1656
+ * changeset file in the repo's `.changeset/` directory. The file follows
1657
+ * Changesets v2 format:
1658
+ *
1659
+ * ---
1660
+ * "@glrs-dev/harness-plugin-opencode": <bump-level>
1661
+ * ---
1662
+ *
1663
+ * <description>
1664
+ *
1665
+ * Bump-level inference (per the wave_4 spec):
1666
+ * - "fix"/"bug" in title → patch
1667
+ * - "remove"/"break"/"v2" in title → major
1668
+ * - otherwise → minor (default)
1669
+ *
1670
+ * The package name is hard-coded to `@glrs-dev/harness-plugin-opencode`
1671
+ * — the autopilot only ships changesets for that package today.
1672
+ *
1673
+ * Filename format: `<slug>-<random6>.md` to avoid collisions when the
1674
+ * same plan re-runs.
1675
+ */
1676
+ type BumpLevel = "patch" | "minor" | "major";
1677
+ interface GenerateChangesetResult {
1678
+ /** Absolute path of the generated changeset file. */
1679
+ path: string;
1680
+ /** The contents written to the file. */
1681
+ content: string;
1682
+ /** Bump level chosen. */
1683
+ bumpLevel: BumpLevel;
1684
+ }
1685
+ interface GenerateChangesetOptions {
1686
+ /** Override the package name (default: "@glrs-dev/harness-plugin-opencode"). */
1687
+ packageName?: string;
1688
+ /**
1689
+ * Test-only: deterministic random suffix for filename collision avoidance.
1690
+ * @internal
1691
+ */
1692
+ _randomSuffix?: () => string;
1693
+ }
1694
+ /**
1695
+ * Generate a Changesets v2 changeset file for a completed plan.
1696
+ *
1697
+ * Writes to `<repoRoot>/.changeset/<slug>-<random6>.md`. Creates the
1698
+ * `.changeset/` directory if missing (Changesets ships with one but
1699
+ * this is defensive in case a worktree drops it).
1700
+ */
1701
+ declare function generateChangeset(planPath: string, repoRoot: string, opts?: GenerateChangesetOptions): Promise<GenerateChangesetResult>;
1702
+
1703
+ /**
1704
+ * Plan session runner for the interactive autopilot orchestrator.
1705
+ *
1706
+ * Runs a headless opencode session with the @plan agent, sends the
1707
+ * scope path as the prompt, and detects the plan output path from
1708
+ * the filesystem (multi-file directory or single-file .md).
1709
+ */
1710
+
1711
+ /**
1712
+ * Injectable server dependencies for testing.
1713
+ * @internal
1714
+ */
1715
+ interface PlanSessionDeps {
1716
+ /** Override filesystem existence checks for testing. */
1717
+ existsSync?: (p: string) => boolean;
1718
+ }
1719
+ /**
1720
+ * Run a headless @plan session.
1721
+ *
1722
+ * Starts an opencode server, creates a session with the @plan agent,
1723
+ * sends a prompt instructing it to read the scope and produce a plan,
1724
+ * then detects the plan output path from the filesystem.
1725
+ *
1726
+ * @plan is headless — autoRejectPermissions is true so it can't
1727
+ * deadlock waiting on a human response.
1728
+ */
1729
+ declare function runPlanSession(opts: PlanSessionOptions & {
1730
+ timeoutMs?: number;
1731
+ _deps?: PlanSessionDeps;
1732
+ adapter: AgentAdapter;
1733
+ }): Promise<PlanSessionResult>;
1734
+
1735
+ /**
1736
+ * Shared type definitions for the scoper session.
1737
+ * Defined here (in autopilot) so both the autopilot orchestrator
1738
+ * (interactive.ts) and the CLI scoper command can import them without
1739
+ * creating circular dependencies.
1740
+ */
1741
+ interface ScoperSessionOptions {
1742
+ /** Directory where scope.md will be written. */
1743
+ planDir: string;
1744
+ /** Slug for the plan (used to name the subdirectory). */
1745
+ slug: string;
1746
+ /** The user's initial goal text (embedded in the first prompt). */
1747
+ initialGoal: string;
1748
+ /** Timeout in milliseconds per turn (default: 5 minutes). */
1749
+ timeoutMs?: number;
1750
+ /**
1751
+ * When provided, the scoper's initial prompt includes this plan content
1752
+ * so the scoper can ground its questions in the existing plan.
1753
+ */
1754
+ existingPlanContent?: string;
1755
+ /**
1756
+ * Injectable dependencies for testing.
1757
+ * @internal — concrete types are defined in the CLI's scoper.ts
1758
+ */
1759
+ _deps?: Record<string, unknown>;
1760
+ }
1761
+ interface ScoperSessionResult {
1762
+ scopePath: string;
1763
+ }
1764
+
1765
+ export { type SessionResult$1 as AdapterSessionResult, type AgentAdapter, type AgentHandle, type AutopilotLogger, type Checkpoint, type CostUpdateEvent, type CredentialExpiredEvent, type EnrichDoneEvent, type EnrichFileDoneEvent, type EnrichFileErrorEvent, type EnrichFileSkipEvent, type EnrichFileStartEvent, type EnrichStartEvent, type ErrorEvent, EventStreamReader, EventStreamWriter, type IterationDoneEvent, type IterationStartEvent, type LoopExitReason, type LoopResult, type LoopSessionDeps, type LoopSessionOptions, MAX_ITERATIONS, MAX_ITERATIONS_PER_PHASE_BY_TIER, type PhaseDoneEvent, type PhaseResult, type PhaseStartEvent, type PlanItem, type PlanSessionOptions, type PlanSessionResult, type PlanState, type RalphLoopOptions, STALL_MS, STALL_MS_BY_TIER, STATUS_INTERVAL_MS, STRUGGLE_THRESHOLD, type ScoperSessionOptions, type ScoperSessionResult, type SessionDoneEvent, type SessionEvent, SessionEventEmitter, type SessionHandle, type SessionResult, SessionRunner, type SessionRunnerDeps, type SessionRunnerOptions, type SessionStartEvent, type SessionStatus, StruggleDetector, TIMEOUT_MS, type ThinkingEvent, type ToolCallEvent, type VerifyDoneEvent, type VerifyResult, type VerifyResultEvent, type VerifyStartEvent, type WorktreeHandle, autoShip, buildConflictGraph, checkKillSwitch, childLogger, createAutopilotLogger, createStatusHeartbeat, createWorktree, deleteCheckpoint, deriveState, detectSentinel, detectSpecPhases, enrichPlanForFastModel, filterUncheckedSpecPhases, formatCost, formatElapsed, generateChangeset, getChangedFiles, hasParallelism, hasSpec, markPhaseCompleted, mergeWorktree, parseItems, parsePlanState, parseSpecItems, readCheckpoint, readSpecConstraints, readSpecGoal, recordHead, resetSoft, runLanes, runLoopSession, runPlanSession, runRalphLoop, runVerifyCommands, validateMainSpec, validatePhaseSpec, validatePlan, validateScope, writeCheckpoint };