taskplane 0.1.14 → 0.1.16

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.
@@ -1,10 +1,28 @@
1
- /**
2
- * Git command runner
3
- * @module orch/git
4
- */
5
- import { execFileSync } from "child_process";
6
-
7
-
1
+ /**
2
+ * Git command runner
3
+ * @module orch/git
4
+ */
5
+ import { execFileSync } from "child_process";
6
+
7
+
8
+ // ── Branch Helpers ───────────────────────────────────────────────────
9
+
10
+ /**
11
+ * Get the current branch name (the branch checked out in the given directory).
12
+ *
13
+ * Uses `git rev-parse --abbrev-ref HEAD`. Returns the branch name or null
14
+ * if HEAD is detached or git fails.
15
+ *
16
+ * @param cwd - Working directory (defaults to process.cwd())
17
+ */
18
+ export function getCurrentBranch(cwd?: string): string | null {
19
+ const result = runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
20
+ if (!result.ok || !result.stdout.trim() || result.stdout.trim() === "HEAD") {
21
+ return null;
22
+ }
23
+ return result.stdout.trim();
24
+ }
25
+
8
26
  // ── Git Command Runner ───────────────────────────────────────────────
9
27
 
10
28
  /**
@@ -1,22 +1,23 @@
1
- /**
2
- * Task Orchestrator — barrel re-export
3
- *
4
- * Provides a single import point for all orchestrator modules.
5
- * Usage: import { executeOrchBatch, ... } from "./taskplane/index.ts";
6
- */
7
-
8
- export * from "./types.ts";
9
- export * from "./config.ts";
10
- export * from "./git.ts";
11
- export * from "./worktree.ts";
12
- export * from "./discovery.ts";
13
- export * from "./waves.ts";
14
- export * from "./formatting.ts";
15
- export * from "./execution.ts";
16
- export * from "./merge.ts";
17
- export * from "./messages.ts";
18
- export * from "./sessions.ts";
19
- export * from "./persistence.ts";
20
- export * from "./engine.ts";
21
- export * from "./resume.ts";
22
- export * from "./abort.ts";
1
+ /**
2
+ * Task Orchestrator — barrel re-export
3
+ *
4
+ * Provides a single import point for all orchestrator modules.
5
+ * Usage: import { executeOrchBatch, ... } from "./taskplane/index.ts";
6
+ */
7
+
8
+ export * from "./types.ts";
9
+ export * from "./config.ts";
10
+ export * from "./git.ts";
11
+ export * from "./worktree.ts";
12
+ export * from "./discovery.ts";
13
+ export * from "./waves.ts";
14
+ export * from "./formatting.ts";
15
+ export * from "./execution.ts";
16
+ export * from "./merge.ts";
17
+ export * from "./messages.ts";
18
+ export * from "./sessions.ts";
19
+ export * from "./persistence.ts";
20
+ export * from "./engine.ts";
21
+ export * from "./resume.ts";
22
+ export * from "./abort.ts";
23
+ export * from "./workspace.ts";
@@ -1,16 +1,16 @@
1
- /**
2
- * Merge orchestration, merge agents, merge worktree
3
- * @module orch/merge
4
- */
5
- import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
6
- import { spawnSync } from "child_process";
7
- import { join } from "path";
8
-
9
- import { buildLaneEnvVars, buildTmuxSpawnArgs, execLog, tmuxHasSession, tmuxKillSession, toTmuxPath } from "./execution.ts";
10
- import { MERGE_POLL_INTERVAL_MS, MERGE_RESULT_GRACE_MS, MERGE_RESULT_READ_RETRIES, MERGE_RESULT_READ_RETRY_DELAY_MS, MERGE_SPAWN_RETRY_MAX, MERGE_TIMEOUT_MS, MergeError, VALID_MERGE_STATUSES } from "./types.ts";
11
- import type { AllocatedLane, LaneExecutionResult, MergeLaneResult, MergeResult, MergeResultStatus, MergeWaveResult, OrchestratorConfig, WaveExecutionResult } from "./types.ts";
12
- import { sleepSync } from "./worktree.ts";
13
-
1
+ /**
2
+ * Merge orchestration, merge agents, merge worktree
3
+ * @module orch/merge
4
+ */
5
+ import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
6
+ import { spawnSync } from "child_process";
7
+ import { join } from "path";
8
+
9
+ import { buildLaneEnvVars, buildTmuxSpawnArgs, execLog, tmuxHasSession, tmuxKillSession, toTmuxPath } from "./execution.ts";
10
+ import { MERGE_POLL_INTERVAL_MS, MERGE_RESULT_GRACE_MS, MERGE_RESULT_READ_RETRIES, MERGE_RESULT_READ_RETRY_DELAY_MS, MERGE_SPAWN_RETRY_MAX, MERGE_TIMEOUT_MS, MergeError, VALID_MERGE_STATUSES } from "./types.ts";
11
+ import type { AllocatedLane, LaneExecutionResult, MergeLaneResult, MergeResult, MergeResultStatus, MergeWaveResult, OrchestratorConfig, WaveExecutionResult } from "./types.ts";
12
+ import { sleepSync } from "./worktree.ts";
13
+
14
14
  // ── Merge Implementation ─────────────────────────────────────────────
15
15
 
16
16
  /**
@@ -451,7 +451,7 @@ export function waitForMergeResult(
451
451
  }
452
452
 
453
453
  /**
454
- * Merge a completed wave's lane branches into the integration branch.
454
+ * Merge a completed wave's lane branches into the base branch.
455
455
  *
456
456
  * Orchestration flow:
457
457
  * 1. Filter to only succeeded lanes (failed lanes are not merged)
@@ -464,7 +464,7 @@ export function waitForMergeResult(
464
464
  * e. Handle result (continue, log, or pause)
465
465
  * 4. Return MergeWaveResult
466
466
  *
467
- * Sequential execution is mandatory — the integration branch is a shared
467
+ * Sequential execution is mandatory — the base branch is a shared
468
468
  * resource, and each merge must see the prior merge's result.
469
469
  *
470
470
  * On CONFLICT_UNRESOLVED or BUILD_FAILURE: stops merging remaining lanes
@@ -479,6 +479,7 @@ export function waitForMergeResult(
479
479
  * @param config - Orchestrator configuration
480
480
  * @param repoRoot - Main repository root
481
481
  * @param batchId - Batch ID for session naming
482
+ * @param baseBranch - Branch to merge into (captured at batch start)
482
483
  * @returns MergeWaveResult with per-lane outcomes
483
484
  */
484
485
  export function mergeWave(
@@ -488,10 +489,11 @@ export function mergeWave(
488
489
  config: OrchestratorConfig,
489
490
  repoRoot: string,
490
491
  batchId: string,
492
+ baseBranch: string,
491
493
  ): MergeWaveResult {
492
494
  const startTime = Date.now();
493
495
  const tmuxPrefix = config.orchestrator.tmux_prefix;
494
- const targetBranch = config.orchestrator.integration_branch;
496
+ const targetBranch = baseBranch;
495
497
  const laneResults: MergeLaneResult[] = [];
496
498
 
497
499
  // Build lane outcome lookup for merge eligibility checks.
@@ -1,134 +1,146 @@
1
- /**
2
- * User-facing message templates (ORCH_MESSAGES)
3
- * @module orch/messages
4
- */
5
- import type { AbortMode } from "./types.ts";
6
-
7
- // ── Message Templates ────────────────────────────────────────────────
8
-
9
- /**
10
- * Deterministic message templates for user-facing /orch commands.
11
- * Ensures consistent UX across invocations.
12
- */
13
- export const ORCH_MESSAGES = {
14
- // /orch
15
- orchStarting: (batchId: string, waves: number, tasks: number) =>
16
- `🚀 Starting batch ${batchId}: ${waves} wave(s), ${tasks} task(s)`,
17
- orchWaveStart: (waveNum: number, totalWaves: number, tasks: number, lanes: number) =>
18
- `\n🌊 Wave ${waveNum}/${totalWaves}: ${tasks} task(s) across ${lanes} lane(s)`,
19
- orchWaveComplete: (waveNum: number, succeeded: number, failed: number, skipped: number, elapsedSec: number) =>
20
- `✅ Wave ${waveNum} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped (${elapsedSec}s)`,
21
- orchMergeStart: (waveNum: number, laneCount: number) =>
22
- `🔀 [Wave ${waveNum}] Merging ${laneCount} lane(s) into develop...`,
23
- orchMergeLaneSuccess: (laneNum: number, commit: string, durationSec: number) =>
24
- ` ✅ Lane ${laneNum} merged (${commit.slice(0, 8)}, ${durationSec}s)`,
25
- orchMergeLaneConflictResolved: (laneNum: number, conflictCount: number, durationSec: number) =>
26
- ` ⚡ Lane ${laneNum} merged with ${conflictCount} auto-resolved conflict(s) (${durationSec}s)`,
27
- orchMergeLaneFailed: (laneNum: number, reason: string) =>
28
- ` ❌ Lane ${laneNum} merge failed: ${reason}`,
29
- orchMergeComplete: (waveNum: number, mergedCount: number, totalSec: number) =>
30
- `🔀 [Wave ${waveNum}] Merge complete: ${mergedCount} lane(s) merged (${totalSec}s)`,
31
- orchMergeFailed: (waveNum: number, laneNum: number, reason: string) =>
32
- `❌ [Wave ${waveNum}] Merge failed at lane ${laneNum}: ${reason}`,
33
- orchMergeSkipped: (waveNum: number) =>
34
- `📝 [Wave ${waveNum}] No successful lanes to merge`,
35
- orchMergePlaceholder: (waveNum: number) =>
36
- `🔀 [Wave ${waveNum}] Merge: placeholder — Step 3 (TS-008) will replace with mergeWave()`,
37
- orchWorktreeReset: (waveNum: number, lanes: number) =>
38
- `🔄 Resetting ${lanes} worktree(s) to develop HEAD after wave ${waveNum}`,
39
- orchBatchComplete: (batchId: string, succeeded: number, failed: number, skipped: number, blocked: number, elapsedSec: number) =>
40
- `\n🏁 Batch ${batchId} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped, ${blocked} blocked (${elapsedSec}s)`,
41
- orchBatchFailed: (batchId: string, reason: string) =>
42
- `\n❌ Batch ${batchId} failed: ${reason}`,
43
- orchBatchStopped: (batchId: string, policy: string) =>
44
- `\n⛔ Batch ${batchId} stopped by ${policy} policy`,
45
-
46
- // /orch-pause
47
- pauseNoBatch: () => "No active batch is running. Use /orch <areas|all> to start.",
48
- pauseAlreadyPaused: (batchId: string) => `Batch ${batchId} is already paused.`,
49
- pauseActivated: (batchId: string) =>
50
- `⏸️ Pausing batch ${batchId}... lanes will stop after their current tasks complete.`,
51
-
52
- // /orch-sessions
53
- sessionsNone: () => "No orchestrator TMUX sessions found.",
54
- sessionsHeader: (count: number) => `🖥️ ${count} orchestrator session(s):`,
55
-
56
- // /orch orphan detection
57
- orphanDetectionResume: (batchId: string, sessionCount: number) =>
58
- `🔄 Found ${sessionCount} running orchestrator session(s) from batch ${batchId}.\n` +
59
- ` Use /orch-resume to continue, or /orch-abort to clean up.`,
60
- orphanDetectionAbort: (sessionCount: number) =>
61
- `⚠️ Found ${sessionCount} orphan orchestrator session(s) without usable state.\n` +
62
- ` Use /orch-abort to clean up before starting a new batch.`,
63
- orphanDetectionCleanup: () =>
64
- `🧹 Cleaned up stale batch state file. Starting fresh.`,
65
-
66
- // /orch-resume
67
- resumeStarting: (batchId: string, phase: string) =>
68
- `🔄 Resuming batch ${batchId} (was: ${phase})...`,
69
- resumeReconciled: (batchId: string, completed: number, pending: number, failed: number, reconnecting: number, reExecuting: number = 0) =>
70
- `📊 Batch ${batchId} reconciliation: ${completed} completed, ${pending} pending, ${failed} failed, ${reconnecting} reconnecting` +
71
- (reExecuting > 0 ? `, ${reExecuting} re-executing` : ""),
72
- resumeSkippedWaves: (skippedCount: number) =>
73
- `⏭️ Skipping ${skippedCount} completed wave(s)`,
74
- resumeReconnecting: (sessionCount: number) =>
75
- `🔗 Reconnecting to ${sessionCount} alive session(s)...`,
76
- resumeNoState: () =>
77
- `❌ No batch to resume. No batch-state.json file found.\n` +
78
- ` Use /orch <areas|all> to start a new batch.`,
79
- resumeInvalidState: (error: string) =>
80
- `❌ Cannot resume: batch state file is invalid.\n` +
81
- ` Error: ${error}\n` +
82
- ` Delete .pi/batch-state.json and start a new batch.`,
83
- resumePhaseNotResumable: (batchId: string, phase: string, reason: string) =>
84
- `❌ Cannot resume batch ${batchId} (phase: ${phase}).\n` +
85
- ` ${reason}`,
86
- resumeComplete: (batchId: string, succeeded: number, failed: number, skipped: number, blocked: number, elapsedSec: number) =>
87
- `\n🏁 Resumed batch ${batchId} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped, ${blocked} blocked (${elapsedSec}s total)`,
88
-
89
- // /orch-abort
90
- abortGracefulStarting: (batchId: string, sessionCount: number) =>
91
- `⏳ Graceful abort of batch ${batchId}: signaling ${sessionCount} session(s) to checkpoint and exit...`,
92
- abortGracefulWaiting: (batchId: string, graceSec: number) =>
93
- `⏳ Waiting up to ${graceSec}s for sessions to checkpoint and exit...`,
94
- abortGracefulForceKill: (count: number) =>
95
- `⚠️ Force-killing ${count} session(s) that did not exit within timeout`,
96
- abortGracefulComplete: (batchId: string, graceful: number, forceKilled: number, durationSec: number) =>
97
- `✅ Graceful abort complete for batch ${batchId}: ${graceful} exited gracefully, ${forceKilled} force-killed (${durationSec}s)`,
98
- abortHardStarting: (batchId: string, sessionCount: number) =>
99
- `⚡ Hard abort of batch ${batchId}: killing ${sessionCount} session(s) immediately...`,
100
- abortHardComplete: (batchId: string, killed: number, durationSec: number) =>
101
- `✅ Hard abort complete for batch ${batchId}: ${killed} session(s) killed (${durationSec}s)`,
102
- abortPartialFailure: (failureCount: number) =>
103
- `⚠️ ${failureCount} error(s) during abort (see details above)`,
104
- abortNoBatch: () =>
105
- `No active batch to abort. Use /orch <areas|all> to start a batch.`,
106
- abortComplete: (mode: AbortMode, sessionsKilled: number) =>
107
- `🏁 Abort (${mode}) complete: ${sessionsKilled} session(s) terminated. Worktrees and branches preserved.`,
108
- } as const;
109
-
110
-
111
- // ── Resume ORCH_MESSAGES ─────────────────────────────────────────────
112
-
113
- // Note: These are added via extension to the ORCH_MESSAGES object below.
114
-
115
- // ── Resume Orchestration ─────────────────────────────────────────────
116
-
117
- /**
118
- * Resume an interrupted batch from persisted state.
119
- *
120
- * Flow:
121
- * 1. Load and validate batch-state.json
122
- * 2. Check phase eligibility (paused/executing/merging only)
123
- * 3. Check for alive TMUX sessions and .DONE files
124
- * 4. Reconcile persisted state against live signals
125
- * 5. Compute resume point (which wave to start from)
126
- * 6. Reconstruct runtime state and continue execution
127
- *
128
- * @param orchConfig - Orchestrator configuration
129
- * @param runnerConfig - Task runner configuration
130
- * @param cwd - Repository root
131
- * @param batchState - Mutable batch state (will be populated from persisted state)
132
- * @param onNotify - Callback for user-facing messages
133
- * @param onMonitorUpdate - Optional callback for dashboard updates
134
- */
1
+ /**
2
+ * User-facing message templates (ORCH_MESSAGES)
3
+ * @module orch/messages
4
+ */
5
+ import type { AbortMode } from "./types.ts";
6
+
7
+ // ── Message Templates ────────────────────────────────────────────────
8
+
9
+ /**
10
+ * Deterministic message templates for user-facing /orch commands.
11
+ * Ensures consistent UX across invocations.
12
+ */
13
+ export const ORCH_MESSAGES = {
14
+ // /orch
15
+ orchStarting: (batchId: string, waves: number, tasks: number) =>
16
+ `🚀 Starting batch ${batchId}: ${waves} wave(s), ${tasks} task(s)`,
17
+ orchWaveStart: (waveNum: number, totalWaves: number, tasks: number, lanes: number) =>
18
+ `\n🌊 Wave ${waveNum}/${totalWaves}: ${tasks} task(s) across ${lanes} lane(s)`,
19
+ orchWaveComplete: (waveNum: number, succeeded: number, failed: number, skipped: number, elapsedSec: number) =>
20
+ `✅ Wave ${waveNum} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped (${elapsedSec}s)`,
21
+ orchMergeStart: (waveNum: number, laneCount: number) =>
22
+ `🔀 [Wave ${waveNum}] Merging ${laneCount} lane(s) into develop...`,
23
+ orchMergeLaneSuccess: (laneNum: number, commit: string, durationSec: number) =>
24
+ ` ✅ Lane ${laneNum} merged (${commit.slice(0, 8)}, ${durationSec}s)`,
25
+ orchMergeLaneConflictResolved: (laneNum: number, conflictCount: number, durationSec: number) =>
26
+ ` ⚡ Lane ${laneNum} merged with ${conflictCount} auto-resolved conflict(s) (${durationSec}s)`,
27
+ orchMergeLaneFailed: (laneNum: number, reason: string) =>
28
+ ` ❌ Lane ${laneNum} merge failed: ${reason}`,
29
+ orchMergeComplete: (waveNum: number, mergedCount: number, totalSec: number) =>
30
+ `🔀 [Wave ${waveNum}] Merge complete: ${mergedCount} lane(s) merged (${totalSec}s)`,
31
+ orchMergeFailed: (waveNum: number, laneNum: number, reason: string) =>
32
+ `❌ [Wave ${waveNum}] Merge failed at lane ${laneNum}: ${reason}`,
33
+ orchMergeSkipped: (waveNum: number) =>
34
+ `📝 [Wave ${waveNum}] No successful lanes to merge`,
35
+ orchMergePlaceholder: (waveNum: number) =>
36
+ `🔀 [Wave ${waveNum}] Merge: placeholder — Step 3 (TS-008) will replace with mergeWave()`,
37
+ orchWorktreeReset: (waveNum: number, lanes: number) =>
38
+ `🔄 Resetting ${lanes} worktree(s) to develop HEAD after wave ${waveNum}`,
39
+ orchBatchComplete: (batchId: string, succeeded: number, failed: number, skipped: number, blocked: number, elapsedSec: number) => {
40
+ const lines = [`\n🏁 Batch ${batchId} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped, ${blocked} blocked (${elapsedSec}s)`];
41
+ if (failed > 0 || blocked > 0) {
42
+ lines.push("");
43
+ if (blocked > 0) {
44
+ lines.push(` ${blocked} task(s) were blocked because upstream tasks failed.`);
45
+ }
46
+ lines.push(" Next steps:");
47
+ lines.push(" /orch-status — review what failed and why");
48
+ lines.push(" • /orch-resume — retry from the failed wave");
49
+ lines.push(" • /orch-abort — clean up and start fresh");
50
+ }
51
+ return lines.join("\n");
52
+ },
53
+ orchBatchFailed: (batchId: string, reason: string) =>
54
+ `\n❌ Batch ${batchId} failed: ${reason}`,
55
+ orchBatchStopped: (batchId: string, policy: string) =>
56
+ `\n⛔ Batch ${batchId} stopped by ${policy} policy`,
57
+
58
+ // /orch-pause
59
+ pauseNoBatch: () => "No active batch is running. Use /orch <areas|all> to start.",
60
+ pauseAlreadyPaused: (batchId: string) => `Batch ${batchId} is already paused.`,
61
+ pauseActivated: (batchId: string) =>
62
+ `⏸️ Pausing batch ${batchId}... lanes will stop after their current tasks complete.`,
63
+
64
+ // /orch-sessions
65
+ sessionsNone: () => "No orchestrator TMUX sessions found.",
66
+ sessionsHeader: (count: number) => `🖥️ ${count} orchestrator session(s):`,
67
+
68
+ // /orch orphan detection
69
+ orphanDetectionResume: (batchId: string, sessionCount: number) =>
70
+ `🔄 Found ${sessionCount} running orchestrator session(s) from batch ${batchId}.\n` +
71
+ ` Use /orch-resume to continue, or /orch-abort to clean up.`,
72
+ orphanDetectionAbort: (sessionCount: number) =>
73
+ `⚠️ Found ${sessionCount} orphan orchestrator session(s) without usable state.\n` +
74
+ ` Use /orch-abort to clean up before starting a new batch.`,
75
+ orphanDetectionCleanup: () =>
76
+ `🧹 Cleaned up stale batch state file. Starting fresh.`,
77
+
78
+ // /orch-resume
79
+ resumeStarting: (batchId: string, phase: string) =>
80
+ `🔄 Resuming batch ${batchId} (was: ${phase})...`,
81
+ resumeReconciled: (batchId: string, completed: number, pending: number, failed: number, reconnecting: number, reExecuting: number = 0) =>
82
+ `📊 Batch ${batchId} reconciliation: ${completed} completed, ${pending} pending, ${failed} failed, ${reconnecting} reconnecting` +
83
+ (reExecuting > 0 ? `, ${reExecuting} re-executing` : ""),
84
+ resumeSkippedWaves: (skippedCount: number) =>
85
+ `⏭️ Skipping ${skippedCount} completed wave(s)`,
86
+ resumeReconnecting: (sessionCount: number) =>
87
+ `🔗 Reconnecting to ${sessionCount} alive session(s)...`,
88
+ resumeNoState: () =>
89
+ `❌ No batch to resume. No batch-state.json file found.\n` +
90
+ ` Use /orch <areas|all> to start a new batch.`,
91
+ resumeInvalidState: (error: string) =>
92
+ `❌ Cannot resume: batch state file is invalid.\n` +
93
+ ` Error: ${error}\n` +
94
+ ` Delete .pi/batch-state.json and start a new batch.`,
95
+ resumePhaseNotResumable: (batchId: string, phase: string, reason: string) =>
96
+ `❌ Cannot resume batch ${batchId} (phase: ${phase}).\n` +
97
+ ` ${reason}`,
98
+ resumeComplete: (batchId: string, succeeded: number, failed: number, skipped: number, blocked: number, elapsedSec: number) =>
99
+ `\n🏁 Resumed batch ${batchId} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped, ${blocked} blocked (${elapsedSec}s total)`,
100
+
101
+ // /orch-abort
102
+ abortGracefulStarting: (batchId: string, sessionCount: number) =>
103
+ `⏳ Graceful abort of batch ${batchId}: signaling ${sessionCount} session(s) to checkpoint and exit...`,
104
+ abortGracefulWaiting: (batchId: string, graceSec: number) =>
105
+ `⏳ Waiting up to ${graceSec}s for sessions to checkpoint and exit...`,
106
+ abortGracefulForceKill: (count: number) =>
107
+ `⚠️ Force-killing ${count} session(s) that did not exit within timeout`,
108
+ abortGracefulComplete: (batchId: string, graceful: number, forceKilled: number, durationSec: number) =>
109
+ `✅ Graceful abort complete for batch ${batchId}: ${graceful} exited gracefully, ${forceKilled} force-killed (${durationSec}s)`,
110
+ abortHardStarting: (batchId: string, sessionCount: number) =>
111
+ `⚡ Hard abort of batch ${batchId}: killing ${sessionCount} session(s) immediately...`,
112
+ abortHardComplete: (batchId: string, killed: number, durationSec: number) =>
113
+ `✅ Hard abort complete for batch ${batchId}: ${killed} session(s) killed (${durationSec}s)`,
114
+ abortPartialFailure: (failureCount: number) =>
115
+ `⚠️ ${failureCount} error(s) during abort (see details above)`,
116
+ abortNoBatch: () =>
117
+ `No active batch to abort. Use /orch <areas|all> to start a batch.`,
118
+ abortComplete: (mode: AbortMode, sessionsKilled: number) =>
119
+ `🏁 Abort (${mode}) complete: ${sessionsKilled} session(s) terminated. Worktrees and branches preserved.`,
120
+ } as const;
121
+
122
+
123
+ // ── Resume ORCH_MESSAGES ─────────────────────────────────────────────
124
+
125
+ // Note: These are added via extension to the ORCH_MESSAGES object below.
126
+
127
+ // ── Resume Orchestration ─────────────────────────────────────────────
128
+
129
+ /**
130
+ * Resume an interrupted batch from persisted state.
131
+ *
132
+ * Flow:
133
+ * 1. Load and validate batch-state.json
134
+ * 2. Check phase eligibility (paused/executing/merging only)
135
+ * 3. Check for alive TMUX sessions and .DONE files
136
+ * 4. Reconcile persisted state against live signals
137
+ * 5. Compute resume point (which wave to start from)
138
+ * 6. Reconstruct runtime state and continue execution
139
+ *
140
+ * @param orchConfig - Orchestrator configuration
141
+ * @param runnerConfig - Task runner configuration
142
+ * @param cwd - Repository root
143
+ * @param batchState - Mutable batch state (will be populated from persisted state)
144
+ * @param onNotify - Callback for user-facing messages
145
+ * @param onMonitorUpdate - Optional callback for dashboard updates
146
+ */