@ginkoai/cli 1.6.2 → 1.7.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 (224) hide show
  1. package/dist/commands/agent/agent-client.d.ts +150 -0
  2. package/dist/commands/agent/agent-client.d.ts.map +1 -0
  3. package/dist/commands/agent/agent-client.js +170 -0
  4. package/dist/commands/agent/agent-client.js.map +1 -0
  5. package/dist/commands/agent/index.d.ts +22 -0
  6. package/dist/commands/agent/index.d.ts.map +1 -0
  7. package/dist/commands/agent/index.js +121 -0
  8. package/dist/commands/agent/index.js.map +1 -0
  9. package/dist/commands/agent/list.d.ts +22 -0
  10. package/dist/commands/agent/list.d.ts.map +1 -0
  11. package/dist/commands/agent/list.js +119 -0
  12. package/dist/commands/agent/list.js.map +1 -0
  13. package/dist/commands/agent/register.d.ts +21 -0
  14. package/dist/commands/agent/register.d.ts.map +1 -0
  15. package/dist/commands/agent/register.js +97 -0
  16. package/dist/commands/agent/register.js.map +1 -0
  17. package/dist/commands/agent/status.d.ts +19 -0
  18. package/dist/commands/agent/status.d.ts.map +1 -0
  19. package/dist/commands/agent/status.js +271 -0
  20. package/dist/commands/agent/status.js.map +1 -0
  21. package/dist/commands/agent/work.d.ts +22 -0
  22. package/dist/commands/agent/work.d.ts.map +1 -0
  23. package/dist/commands/agent/work.js +459 -0
  24. package/dist/commands/agent/work.js.map +1 -0
  25. package/dist/commands/checkpoint/create.d.ts +27 -0
  26. package/dist/commands/checkpoint/create.d.ts.map +1 -0
  27. package/dist/commands/checkpoint/create.js +82 -0
  28. package/dist/commands/checkpoint/create.js.map +1 -0
  29. package/dist/commands/checkpoint/index.d.ts +23 -0
  30. package/dist/commands/checkpoint/index.d.ts.map +1 -0
  31. package/dist/commands/checkpoint/index.js +91 -0
  32. package/dist/commands/checkpoint/index.js.map +1 -0
  33. package/dist/commands/checkpoint/list.d.ts +27 -0
  34. package/dist/commands/checkpoint/list.d.ts.map +1 -0
  35. package/dist/commands/checkpoint/list.js +115 -0
  36. package/dist/commands/checkpoint/list.js.map +1 -0
  37. package/dist/commands/checkpoint/show.d.ts +23 -0
  38. package/dist/commands/checkpoint/show.d.ts.map +1 -0
  39. package/dist/commands/checkpoint/show.js +102 -0
  40. package/dist/commands/checkpoint/show.js.map +1 -0
  41. package/dist/commands/dlq.d.ts +24 -0
  42. package/dist/commands/dlq.d.ts.map +1 -0
  43. package/dist/commands/dlq.js +172 -0
  44. package/dist/commands/dlq.js.map +1 -0
  45. package/dist/commands/escalation/create.d.ts +22 -0
  46. package/dist/commands/escalation/create.d.ts.map +1 -0
  47. package/dist/commands/escalation/create.js +122 -0
  48. package/dist/commands/escalation/create.js.map +1 -0
  49. package/dist/commands/escalation/escalation-client.d.ts +101 -0
  50. package/dist/commands/escalation/escalation-client.d.ts.map +1 -0
  51. package/dist/commands/escalation/escalation-client.js +129 -0
  52. package/dist/commands/escalation/escalation-client.js.map +1 -0
  53. package/dist/commands/escalation/index.d.ts +22 -0
  54. package/dist/commands/escalation/index.d.ts.map +1 -0
  55. package/dist/commands/escalation/index.js +94 -0
  56. package/dist/commands/escalation/index.js.map +1 -0
  57. package/dist/commands/escalation/list.d.ts +24 -0
  58. package/dist/commands/escalation/list.d.ts.map +1 -0
  59. package/dist/commands/escalation/list.js +170 -0
  60. package/dist/commands/escalation/list.js.map +1 -0
  61. package/dist/commands/escalation/resolve.d.ts +20 -0
  62. package/dist/commands/escalation/resolve.d.ts.map +1 -0
  63. package/dist/commands/escalation/resolve.js +102 -0
  64. package/dist/commands/escalation/resolve.js.map +1 -0
  65. package/dist/commands/graph/api-client.d.ts +21 -1
  66. package/dist/commands/graph/api-client.d.ts.map +1 -1
  67. package/dist/commands/graph/api-client.js +23 -0
  68. package/dist/commands/graph/api-client.js.map +1 -1
  69. package/dist/commands/handoff.d.ts.map +1 -1
  70. package/dist/commands/handoff.js +9 -1
  71. package/dist/commands/handoff.js.map +1 -1
  72. package/dist/commands/log.d.ts +3 -0
  73. package/dist/commands/log.d.ts.map +1 -1
  74. package/dist/commands/log.js +73 -14
  75. package/dist/commands/log.js.map +1 -1
  76. package/dist/commands/notifications/history.d.ts +21 -0
  77. package/dist/commands/notifications/history.d.ts.map +1 -0
  78. package/dist/commands/notifications/history.js +160 -0
  79. package/dist/commands/notifications/history.js.map +1 -0
  80. package/dist/commands/notifications/index.d.ts +22 -0
  81. package/dist/commands/notifications/index.d.ts.map +1 -0
  82. package/dist/commands/notifications/index.js +87 -0
  83. package/dist/commands/notifications/index.js.map +1 -0
  84. package/dist/commands/notifications/list.d.ts +19 -0
  85. package/dist/commands/notifications/list.d.ts.map +1 -0
  86. package/dist/commands/notifications/list.js +132 -0
  87. package/dist/commands/notifications/list.js.map +1 -0
  88. package/dist/commands/notifications/test.d.ts +19 -0
  89. package/dist/commands/notifications/test.d.ts.map +1 -0
  90. package/dist/commands/notifications/test.js +217 -0
  91. package/dist/commands/notifications/test.js.map +1 -0
  92. package/dist/commands/orchestrate.d.ts +25 -0
  93. package/dist/commands/orchestrate.d.ts.map +1 -0
  94. package/dist/commands/orchestrate.js +858 -0
  95. package/dist/commands/orchestrate.js.map +1 -0
  96. package/dist/commands/sprint/deps.d.ts +29 -0
  97. package/dist/commands/sprint/deps.d.ts.map +1 -0
  98. package/dist/commands/sprint/deps.js +269 -0
  99. package/dist/commands/sprint/deps.js.map +1 -0
  100. package/dist/commands/sprint/index.d.ts +10 -5
  101. package/dist/commands/sprint/index.d.ts.map +1 -1
  102. package/dist/commands/sprint/index.js +26 -5
  103. package/dist/commands/sprint/index.js.map +1 -1
  104. package/dist/commands/start/index.d.ts.map +1 -1
  105. package/dist/commands/start/index.js +6 -0
  106. package/dist/commands/start/index.js.map +1 -1
  107. package/dist/commands/start/start-reflection.d.ts.map +1 -1
  108. package/dist/commands/start/start-reflection.js +8 -0
  109. package/dist/commands/start/start-reflection.js.map +1 -1
  110. package/dist/commands/verify.d.ts +17 -0
  111. package/dist/commands/verify.d.ts.map +1 -0
  112. package/dist/commands/verify.js +232 -0
  113. package/dist/commands/verify.js.map +1 -0
  114. package/dist/core/session-log-manager.d.ts +1 -1
  115. package/dist/core/session-log-manager.d.ts.map +1 -1
  116. package/dist/index.js +78 -1
  117. package/dist/index.js.map +1 -1
  118. package/dist/lib/__tests__/task-timeout.test.d.ts +12 -0
  119. package/dist/lib/__tests__/task-timeout.test.d.ts.map +1 -0
  120. package/dist/lib/__tests__/task-timeout.test.js +278 -0
  121. package/dist/lib/__tests__/task-timeout.test.js.map +1 -0
  122. package/dist/lib/agent-heartbeat.d.ts +68 -0
  123. package/dist/lib/agent-heartbeat.d.ts.map +1 -0
  124. package/dist/lib/agent-heartbeat.js +117 -0
  125. package/dist/lib/agent-heartbeat.js.map +1 -0
  126. package/dist/lib/checkpoint.d.ts +85 -0
  127. package/dist/lib/checkpoint.d.ts.map +1 -0
  128. package/dist/lib/checkpoint.js +323 -0
  129. package/dist/lib/checkpoint.js.map +1 -0
  130. package/dist/lib/context-metrics.d.ts +230 -0
  131. package/dist/lib/context-metrics.d.ts.map +1 -0
  132. package/dist/lib/context-metrics.js +372 -0
  133. package/dist/lib/context-metrics.js.map +1 -0
  134. package/dist/lib/dead-letter-queue.d.ts +108 -0
  135. package/dist/lib/dead-letter-queue.d.ts.map +1 -0
  136. package/dist/lib/dead-letter-queue.js +378 -0
  137. package/dist/lib/dead-letter-queue.js.map +1 -0
  138. package/dist/lib/event-logger.d.ts +9 -1
  139. package/dist/lib/event-logger.d.ts.map +1 -1
  140. package/dist/lib/event-logger.js +45 -3
  141. package/dist/lib/event-logger.js.map +1 -1
  142. package/dist/lib/event-queue.d.ts.map +1 -1
  143. package/dist/lib/event-queue.js +13 -2
  144. package/dist/lib/event-queue.js.map +1 -1
  145. package/dist/lib/examples/timeout-demo.d.ts +13 -0
  146. package/dist/lib/examples/timeout-demo.d.ts.map +1 -0
  147. package/dist/lib/examples/timeout-demo.js +102 -0
  148. package/dist/lib/examples/timeout-demo.js.map +1 -0
  149. package/dist/lib/examples/timeout-integration-example.d.ts +17 -0
  150. package/dist/lib/examples/timeout-integration-example.d.ts.map +1 -0
  151. package/dist/lib/examples/timeout-integration-example.js +223 -0
  152. package/dist/lib/examples/timeout-integration-example.js.map +1 -0
  153. package/dist/lib/notification-hooks.d.ts +103 -0
  154. package/dist/lib/notification-hooks.d.ts.map +1 -0
  155. package/dist/lib/notification-hooks.js +223 -0
  156. package/dist/lib/notification-hooks.js.map +1 -0
  157. package/dist/lib/notifications/discord.d.ts +20 -0
  158. package/dist/lib/notifications/discord.d.ts.map +1 -0
  159. package/dist/lib/notifications/discord.js +140 -0
  160. package/dist/lib/notifications/discord.js.map +1 -0
  161. package/dist/lib/notifications/index.d.ts +66 -0
  162. package/dist/lib/notifications/index.d.ts.map +1 -0
  163. package/dist/lib/notifications/index.js +120 -0
  164. package/dist/lib/notifications/index.js.map +1 -0
  165. package/dist/lib/notifications/slack.d.ts +20 -0
  166. package/dist/lib/notifications/slack.d.ts.map +1 -0
  167. package/dist/lib/notifications/slack.js +186 -0
  168. package/dist/lib/notifications/slack.js.map +1 -0
  169. package/dist/lib/notifications/teams.d.ts +20 -0
  170. package/dist/lib/notifications/teams.d.ts.map +1 -0
  171. package/dist/lib/notifications/teams.js +146 -0
  172. package/dist/lib/notifications/teams.js.map +1 -0
  173. package/dist/lib/notifications/webhook.d.ts +23 -0
  174. package/dist/lib/notifications/webhook.d.ts.map +1 -0
  175. package/dist/lib/notifications/webhook.js +65 -0
  176. package/dist/lib/notifications/webhook.js.map +1 -0
  177. package/dist/lib/orchestrator-state.d.ts +194 -0
  178. package/dist/lib/orchestrator-state.d.ts.map +1 -0
  179. package/dist/lib/orchestrator-state.js +332 -0
  180. package/dist/lib/orchestrator-state.js.map +1 -0
  181. package/dist/lib/realtime-cursor.d.ts +107 -0
  182. package/dist/lib/realtime-cursor.d.ts.map +1 -0
  183. package/dist/lib/realtime-cursor.js +260 -0
  184. package/dist/lib/realtime-cursor.js.map +1 -0
  185. package/dist/lib/rollback.d.ts +86 -0
  186. package/dist/lib/rollback.d.ts.map +1 -0
  187. package/dist/lib/rollback.js +405 -0
  188. package/dist/lib/rollback.js.map +1 -0
  189. package/dist/lib/sprint-loader.d.ts +39 -2
  190. package/dist/lib/sprint-loader.d.ts.map +1 -1
  191. package/dist/lib/sprint-loader.js +269 -5
  192. package/dist/lib/sprint-loader.js.map +1 -1
  193. package/dist/lib/stale-agent-detector.d.ts +102 -0
  194. package/dist/lib/stale-agent-detector.d.ts.map +1 -0
  195. package/dist/lib/stale-agent-detector.js +156 -0
  196. package/dist/lib/stale-agent-detector.js.map +1 -0
  197. package/dist/lib/task-dependencies.d.ts +143 -0
  198. package/dist/lib/task-dependencies.d.ts.map +1 -0
  199. package/dist/lib/task-dependencies.js +357 -0
  200. package/dist/lib/task-dependencies.js.map +1 -0
  201. package/dist/lib/task-timeout.d.ts +153 -0
  202. package/dist/lib/task-timeout.d.ts.map +1 -0
  203. package/dist/lib/task-timeout.js +505 -0
  204. package/dist/lib/task-timeout.js.map +1 -0
  205. package/dist/lib/verification/build-check.d.ts +55 -0
  206. package/dist/lib/verification/build-check.d.ts.map +1 -0
  207. package/dist/lib/verification/build-check.js +111 -0
  208. package/dist/lib/verification/build-check.js.map +1 -0
  209. package/dist/lib/verification/index.d.ts +19 -0
  210. package/dist/lib/verification/index.d.ts.map +1 -0
  211. package/dist/lib/verification/index.js +17 -0
  212. package/dist/lib/verification/index.js.map +1 -0
  213. package/dist/lib/verification/lint-check.d.ts +34 -0
  214. package/dist/lib/verification/lint-check.d.ts.map +1 -0
  215. package/dist/lib/verification/lint-check.js +215 -0
  216. package/dist/lib/verification/lint-check.js.map +1 -0
  217. package/dist/lib/verification/test-runner.d.ts +50 -0
  218. package/dist/lib/verification/test-runner.d.ts.map +1 -0
  219. package/dist/lib/verification/test-runner.js +225 -0
  220. package/dist/lib/verification/test-runner.js.map +1 -0
  221. package/dist/utils/command-helpers.d.ts.map +1 -1
  222. package/dist/utils/command-helpers.js +7 -0
  223. package/dist/utils/command-helpers.js.map +1 -1
  224. package/package.json +1 -1
@@ -0,0 +1,858 @@
1
+ /**
2
+ * @fileType: command
3
+ * @status: current
4
+ * @updated: 2025-12-07
5
+ * @tags: [cli, orchestrate, multi-agent, supervisor, epic-004, sprint-4, task-10]
6
+ * @related: [agent/index.ts, agent/work.ts, ../lib/task-dependencies.ts, graph/api-client.ts, ../lib/orchestrator-state.ts]
7
+ * @priority: high
8
+ * @complexity: high
9
+ * @dependencies: [commander, chalk, ora, fs-extra]
10
+ */
11
+ /**
12
+ * Orchestrate Command (EPIC-004 Sprint 4 TASK-7, TASK-10)
13
+ *
14
+ * Run as supervisor agent to coordinate multi-agent task execution.
15
+ *
16
+ * The orchestrator:
17
+ * 1. Registers as an orchestrator agent (or resumes from checkpoint)
18
+ * 2. Loads sprint tasks with dependencies
19
+ * 3. Computes execution waves (topological ordering)
20
+ * 4. Monitors available worker agents
21
+ * 5. Assigns tasks to workers based on capabilities
22
+ * 6. Reacts to task completion events
23
+ * 7. Handles blockers and reassignment
24
+ * 8. Saves checkpoint on exit for seamless respawn (TASK-10)
25
+ *
26
+ * Exit Conditions (TASK-10):
27
+ * - All tasks complete → success exit (code 0), checkpoint deleted
28
+ * - Context pressure > 80% → checkpoint + respawn (code 75)
29
+ * - No progress for 10 cycles → escalate + pause (code 1)
30
+ * - Human interrupt (SIGINT) → graceful shutdown (code 0)
31
+ * - Max runtime exceeded → checkpoint + respawn (code 75)
32
+ *
33
+ * Resume Flow (TASK-10):
34
+ * - Use --resume to continue from last checkpoint
35
+ * - Restores completed tasks, in-progress assignments, context metrics
36
+ * - New instance seamlessly continues orchestration
37
+ */
38
+ import chalk from 'chalk';
39
+ import ora from 'ora';
40
+ import { AgentClient } from './agent/agent-client.js';
41
+ import { GraphApiClient } from './graph/api-client.js';
42
+ import { requireGinkoRoot } from '../utils/ginko-root.js';
43
+ import { loadGraphConfig } from './graph/config.js';
44
+ import { loadSprintChecklist } from '../lib/sprint-loader.js';
45
+ import { getExecutionOrder, validateDependencies, getDependencyStats, } from '../lib/task-dependencies.js';
46
+ import { startHeartbeat, shutdownHeartbeat } from '../lib/agent-heartbeat.js';
47
+ import { getContextMonitor, resetContextMonitor, getPressureColor, } from '../lib/context-metrics.js';
48
+ import { OrchestratorStateManager, EXIT_CODE_SUCCESS, EXIT_CODE_ERROR, EXIT_CODE_RESPAWN, getExitMessage, persistStateToGraph, recoverStateFromGraph, reconcileTaskStatuses, } from '../lib/orchestrator-state.js';
49
+ // ============================================================
50
+ // Constants
51
+ // ============================================================
52
+ const MAX_CYCLES_WITHOUT_PROGRESS = 10;
53
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
54
+ const DEFAULT_MAX_RUNTIME_MINUTES = 60;
55
+ // EXIT_CODE_RESPAWN imported from orchestrator-state.ts
56
+ // ============================================================
57
+ // Main Command
58
+ // ============================================================
59
+ /**
60
+ * Run orchestrator agent
61
+ */
62
+ export async function orchestrateCommand(options = {}) {
63
+ let state = null;
64
+ let spinner = null;
65
+ let isShuttingDown = false;
66
+ let stateManager = null;
67
+ let resumedFromCheckpoint = false;
68
+ try {
69
+ // ============================================================
70
+ // PHASE 1: Initialization
71
+ // ============================================================
72
+ spinner = ora('Initializing orchestrator...').start();
73
+ const projectRoot = await requireGinkoRoot();
74
+ const graphConfig = await loadGraphConfig();
75
+ if (!graphConfig?.graphId) {
76
+ spinner.fail(chalk.red('No graph configured'));
77
+ console.error(chalk.red(' Run `ginko graph init` to initialize the graph first.'));
78
+ process.exit(1);
79
+ }
80
+ // ============================================================
81
+ // PHASE 2: Load Sprint & Tasks
82
+ // ============================================================
83
+ spinner.text = 'Loading sprint tasks...';
84
+ const sprint = await loadSprintChecklist(projectRoot);
85
+ if (!sprint) {
86
+ spinner.fail(chalk.red('No active sprint found'));
87
+ console.error(chalk.red(' Create a sprint file in docs/sprints/ first.'));
88
+ process.exit(1);
89
+ }
90
+ // TASK-10: Initialize state manager
91
+ stateManager = new OrchestratorStateManager(projectRoot, graphConfig.graphId);
92
+ // TASK-8: Try to recover from graph first (cross-machine recovery)
93
+ const client = new GraphApiClient();
94
+ let graphCheckpoint = null;
95
+ // TASK-10: Check for existing checkpoint
96
+ if (options.resume) {
97
+ spinner.text = 'Checking for checkpoint...';
98
+ // TASK-8: Try graph recovery first
99
+ try {
100
+ const epicId = options.epic || sprint.file || 'unknown';
101
+ graphCheckpoint = await recoverStateFromGraph(graphConfig.graphId, epicId, client);
102
+ if (graphCheckpoint) {
103
+ spinner.succeed(chalk.green('Recovered state from graph (cross-machine recovery)'));
104
+ console.log(chalk.dim(` Orchestrator: ${graphCheckpoint.orchestratorName}`));
105
+ console.log(chalk.dim(` Last persisted: ${graphCheckpoint.persistedAt || graphCheckpoint.savedAt}`));
106
+ console.log(chalk.dim(` Completed: ${graphCheckpoint.completedTasks.length} tasks`));
107
+ console.log(chalk.dim(` In progress: ${Object.keys(graphCheckpoint.inProgressTasks).length} tasks`));
108
+ resumedFromCheckpoint = true;
109
+ spinner = ora('Resuming orchestration...').start();
110
+ }
111
+ }
112
+ catch (error) {
113
+ // Graph recovery not available, fall back to local checkpoint
114
+ console.log(chalk.dim(' Graph recovery unavailable, checking local checkpoint...'));
115
+ }
116
+ // Fall back to local checkpoint if graph recovery failed
117
+ if (!graphCheckpoint) {
118
+ const checkpoint = await stateManager.loadCheckpoint();
119
+ if (checkpoint) {
120
+ graphCheckpoint = checkpoint;
121
+ spinner.succeed(chalk.green('Found checkpoint from previous session (local recovery)'));
122
+ console.log(chalk.dim(` Saved at: ${checkpoint.savedAt}`));
123
+ console.log(chalk.dim(` Completed: ${checkpoint.completedTasks.length} tasks`));
124
+ console.log(chalk.dim(` In progress: ${Object.keys(checkpoint.inProgressTasks).length} tasks`));
125
+ if (checkpoint.exitReason) {
126
+ console.log(chalk.dim(` Exit reason: ${getExitMessage(checkpoint.exitReason)}`));
127
+ }
128
+ resumedFromCheckpoint = true;
129
+ spinner = ora('Resuming orchestration...').start();
130
+ }
131
+ else {
132
+ spinner.warn(chalk.yellow('No checkpoint found - starting fresh'));
133
+ console.log(chalk.dim(' Use --resume only after a previous session saved a checkpoint'));
134
+ }
135
+ }
136
+ }
137
+ else {
138
+ // Check if checkpoint exists and warn user
139
+ const hasCheckpoint = await stateManager.hasCheckpoint();
140
+ if (hasCheckpoint) {
141
+ spinner.info(chalk.cyan('Previous checkpoint found'));
142
+ console.log(chalk.dim(' Use --resume to continue from last session'));
143
+ console.log(chalk.dim(' Starting fresh will overwrite the checkpoint'));
144
+ console.log('');
145
+ spinner = ora('Starting fresh orchestration...').start();
146
+ }
147
+ }
148
+ // Convert sprint tasks to orchestrator tasks
149
+ const tasks = convertSprintTasks(sprint);
150
+ if (tasks.length === 0) {
151
+ spinner.fail(chalk.red('No tasks found in sprint'));
152
+ process.exit(1);
153
+ }
154
+ // Validate dependencies
155
+ const depTasks = tasks.map(t => ({
156
+ id: t.id,
157
+ dependsOn: t.dependsOn,
158
+ status: mapStatusForDeps(t.status),
159
+ }));
160
+ const errors = validateDependencies(depTasks);
161
+ const hasBlockingErrors = errors.some(e => e.type === 'circular' || e.type === 'self_reference');
162
+ if (errors.length > 0) {
163
+ spinner.warn(chalk.yellow('Dependency warnings:'));
164
+ for (const error of errors) {
165
+ console.log(chalk.yellow(` ⚠️ ${error.details}`));
166
+ }
167
+ // For self-references and circular deps, remove the problematic deps to continue
168
+ if (hasBlockingErrors) {
169
+ console.log(chalk.dim(' Removing problematic dependencies to continue...'));
170
+ for (const task of tasks) {
171
+ // Remove self-references
172
+ task.dependsOn = task.dependsOn.filter(d => d !== task.id);
173
+ // Update depTasks too
174
+ const depTask = depTasks.find(t => t.id === task.id);
175
+ if (depTask) {
176
+ depTask.dependsOn = task.dependsOn;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ // Compute execution waves
182
+ let waves;
183
+ try {
184
+ waves = getExecutionOrder(depTasks);
185
+ }
186
+ catch (waveError) {
187
+ // If still failing, fallback to treating all tasks as wave 1 (no deps)
188
+ spinner.warn(chalk.yellow('Cannot compute optimal wave order, using flat execution'));
189
+ waves = [{
190
+ wave: 1,
191
+ tasks: depTasks,
192
+ }];
193
+ }
194
+ const stats = getDependencyStats(depTasks);
195
+ spinner.succeed(chalk.green(`Loaded ${tasks.length} tasks in ${waves.length} waves`));
196
+ // ============================================================
197
+ // PHASE 3: Display Initial Status (before registration for dry-run)
198
+ // ============================================================
199
+ console.log('');
200
+ displayOrchestrationPlan(tasks, waves, stats, options.verbose);
201
+ if (options.dryRun) {
202
+ console.log('');
203
+ console.log(chalk.yellow('🔍 Dry run mode - no tasks will be assigned'));
204
+ console.log(chalk.dim(' Remove --dry-run to start orchestration'));
205
+ return;
206
+ }
207
+ // ============================================================
208
+ // PHASE 4: Register Orchestrator Agent (or restore from checkpoint)
209
+ // ============================================================
210
+ // TASK-10 & TASK-8: Check if resuming from checkpoint
211
+ let checkpoint = graphCheckpoint;
212
+ if (checkpoint && resumedFromCheckpoint) {
213
+ // TASK-8: Reconcile task statuses before restoration
214
+ spinner.text = 'Reconciling task statuses...';
215
+ const actualTasks = tasks.map(t => ({
216
+ id: t.id,
217
+ status: t.status === 'complete' ? 'complete' : t.status,
218
+ }));
219
+ checkpoint = await reconcileTaskStatuses(checkpoint, actualTasks, client);
220
+ // TASK-10: Restore state from checkpoint
221
+ spinner.text = 'Restoring state from checkpoint...';
222
+ // Restore context monitor with previous metrics
223
+ resetContextMonitor();
224
+ const contextMonitor = getContextMonitor({
225
+ model: checkpoint.contextMetrics.model || 'claude-opus-4-5-20251101',
226
+ });
227
+ // Restore token count from checkpoint
228
+ contextMonitor.addTokens(checkpoint.contextMetrics.estimatedTokens);
229
+ // Restore state
230
+ const restored = stateManager.restoreFromCheckpoint(checkpoint);
231
+ state = {
232
+ orchestratorId: checkpoint.orchestratorId,
233
+ orchestratorName: checkpoint.orchestratorName,
234
+ graphId: checkpoint.graphId,
235
+ sprintId: checkpoint.sprintId,
236
+ startedAt: restored.startedAt,
237
+ lastProgressAt: new Date(), // Reset progress timer for new session
238
+ cyclesWithoutProgress: 0, // Reset cycle counter
239
+ completedTasks: restored.completedTasks,
240
+ inProgressTasks: restored.inProgressTasks,
241
+ blockedTasks: restored.blockedTasks,
242
+ assignmentHistory: restored.assignmentHistory,
243
+ contextMonitor,
244
+ recoveredFromCheckpointId: checkpoint.orchestratorId, // Track recovery source
245
+ };
246
+ // Update task statuses from restored state
247
+ for (const task of tasks) {
248
+ if (state.completedTasks.has(task.id)) {
249
+ task.status = 'complete';
250
+ }
251
+ else if (state.inProgressTasks.has(task.id)) {
252
+ task.status = 'assigned';
253
+ task.assignedTo = state.inProgressTasks.get(task.id);
254
+ }
255
+ }
256
+ spinner.succeed(chalk.green(`Resumed as ${chalk.bold(state.orchestratorName)}`));
257
+ console.log(chalk.dim(` Agent ID: ${state.orchestratorId}`));
258
+ console.log(chalk.dim(` Restored: ${state.completedTasks.size} completed, ${state.inProgressTasks.size} in progress`));
259
+ if (checkpoint.persistedAt) {
260
+ console.log(chalk.dim(` Recovered from: ${checkpoint.recoveredFrom || 'graph state'}`));
261
+ }
262
+ }
263
+ else {
264
+ // Fresh start - register new orchestrator
265
+ spinner = ora('Registering orchestrator agent...').start();
266
+ const orchestratorName = `orchestrator-${Date.now()}`;
267
+ let registerResponse;
268
+ try {
269
+ registerResponse = await AgentClient.register({
270
+ name: orchestratorName,
271
+ capabilities: ['task-assignment', 'task-monitoring', 'orchestration'],
272
+ status: 'active',
273
+ });
274
+ }
275
+ catch (regError) {
276
+ spinner.warn(chalk.yellow(`Agent registration failed: ${regError.message}`));
277
+ console.log(chalk.dim(' Continuing with local-only orchestration...'));
278
+ // Create local-only state
279
+ registerResponse = {
280
+ agentId: `local-${Date.now()}`,
281
+ name: orchestratorName,
282
+ capabilities: ['task-assignment', 'task-monitoring', 'orchestration'],
283
+ status: 'active',
284
+ organizationId: 'local',
285
+ createdAt: new Date().toISOString(),
286
+ };
287
+ }
288
+ // Initialize context monitor for pressure tracking (TASK-9)
289
+ resetContextMonitor(); // Reset any previous session
290
+ const contextMonitor = getContextMonitor({
291
+ model: 'claude-opus-4-5-20251101', // Default to Opus for orchestrator
292
+ });
293
+ state = {
294
+ orchestratorId: registerResponse.agentId,
295
+ orchestratorName: registerResponse.name,
296
+ graphId: graphConfig.graphId,
297
+ sprintId: sprint.file,
298
+ startedAt: new Date(),
299
+ lastProgressAt: new Date(),
300
+ cyclesWithoutProgress: 0,
301
+ completedTasks: new Set(),
302
+ inProgressTasks: new Map(),
303
+ blockedTasks: new Set(),
304
+ assignmentHistory: [],
305
+ contextMonitor,
306
+ };
307
+ // Mark already-completed tasks from sprint file
308
+ for (const task of tasks) {
309
+ if (task.status === 'complete') {
310
+ state.completedTasks.add(task.id);
311
+ }
312
+ }
313
+ spinner.succeed(chalk.green(`Registered as ${chalk.bold(state.orchestratorName)}`));
314
+ console.log(chalk.dim(` Agent ID: ${state.orchestratorId}`));
315
+ }
316
+ // ============================================================
317
+ // PHASE 5: Start Heartbeat
318
+ // ============================================================
319
+ console.log('');
320
+ spinner = ora('Starting heartbeat...').start();
321
+ startHeartbeat(state.orchestratorId);
322
+ spinner.succeed(chalk.green('Heartbeat started (30s interval)'));
323
+ // ============================================================
324
+ // PHASE 6: Setup Graceful Shutdown
325
+ // ============================================================
326
+ const gracefulShutdown = async (signal) => {
327
+ if (isShuttingDown)
328
+ return;
329
+ isShuttingDown = true;
330
+ console.log('');
331
+ console.log(chalk.yellow(`\n📡 Received ${signal}, shutting down gracefully...`));
332
+ // TASK-10: Save checkpoint with exit reason
333
+ if (state && stateManager) {
334
+ const checkpoint = stateManager.createCheckpoint(state);
335
+ await stateManager.saveCheckpoint(checkpoint, {
336
+ exitReason: 'user_interrupt',
337
+ exitCode: EXIT_CODE_SUCCESS,
338
+ });
339
+ console.log(chalk.dim(' Checkpoint saved for later resume'));
340
+ }
341
+ // Stop heartbeat
342
+ await shutdownHeartbeat();
343
+ console.log(chalk.green('✓ Orchestrator stopped'));
344
+ displayFinalStatus(state, tasks);
345
+ process.exit(EXIT_CODE_SUCCESS);
346
+ };
347
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
348
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
349
+ // ============================================================
350
+ // PHASE 7: Main Orchestration Loop
351
+ // ============================================================
352
+ console.log('');
353
+ console.log(chalk.bold.cyan('🔄 Starting orchestration loop...'));
354
+ console.log(chalk.dim(` Poll interval: ${options.pollInterval || DEFAULT_POLL_INTERVAL_SECONDS}s`));
355
+ console.log(chalk.dim(` Max runtime: ${options.maxRuntime || DEFAULT_MAX_RUNTIME_MINUTES} minutes`));
356
+ console.log('');
357
+ await runOrchestrationLoop(state, tasks, waves, {
358
+ pollInterval: (options.pollInterval || DEFAULT_POLL_INTERVAL_SECONDS) * 1000,
359
+ maxRuntime: (options.maxRuntime || DEFAULT_MAX_RUNTIME_MINUTES) * 60 * 1000,
360
+ verbose: options.verbose || false,
361
+ stateManager: stateManager, // TASK-10: Pass state manager for checkpoints
362
+ graphClient: client, // TASK-8: Pass graph client for state persistence
363
+ });
364
+ // ============================================================
365
+ // PHASE 8: Success Exit
366
+ // ============================================================
367
+ console.log('');
368
+ console.log(chalk.bold.green('🎉 All tasks completed!'));
369
+ displayFinalStatus(state, tasks);
370
+ // TASK-10: Delete checkpoint on successful completion
371
+ if (stateManager) {
372
+ await stateManager.deleteCheckpoint();
373
+ console.log(chalk.dim(' Checkpoint cleared (all tasks complete)'));
374
+ }
375
+ await shutdownHeartbeat();
376
+ process.exit(EXIT_CODE_SUCCESS);
377
+ }
378
+ catch (error) {
379
+ if (spinner)
380
+ spinner.fail(chalk.red('Orchestration failed'));
381
+ console.error(chalk.red(`\n❌ Error: ${error.message}`));
382
+ if (options.verbose && error.stack) {
383
+ console.error(chalk.dim(error.stack));
384
+ }
385
+ // TASK-10: Save checkpoint on error for recovery
386
+ if (state && stateManager) {
387
+ try {
388
+ const checkpoint = stateManager.createCheckpoint(state);
389
+ await stateManager.saveCheckpoint(checkpoint, {
390
+ exitReason: 'error',
391
+ exitCode: EXIT_CODE_ERROR,
392
+ });
393
+ console.log(chalk.dim(' Checkpoint saved for recovery'));
394
+ }
395
+ catch {
396
+ // Ignore checkpoint save errors
397
+ }
398
+ }
399
+ // Cleanup
400
+ if (state) {
401
+ try {
402
+ await shutdownHeartbeat();
403
+ }
404
+ catch {
405
+ // Ignore cleanup errors
406
+ }
407
+ }
408
+ process.exit(1);
409
+ }
410
+ }
411
+ async function runOrchestrationLoop(state, tasks, waves, options) {
412
+ const client = options.graphClient;
413
+ let lastEventId = null;
414
+ const startTime = Date.now();
415
+ const { stateManager, graphClient } = options;
416
+ // TASK-8: Track last graph persistence time
417
+ const GRAPH_PERSIST_INTERVAL = 30 * 1000; // 30 seconds
418
+ let lastGraphPersist = Date.now();
419
+ while (true) {
420
+ // Check exit conditions
421
+ const incompleteTasks = tasks.filter(t => t.status !== 'complete').length;
422
+ if (incompleteTasks === 0) {
423
+ // All tasks complete!
424
+ return;
425
+ }
426
+ // Check max runtime
427
+ if (Date.now() - startTime > options.maxRuntime) {
428
+ console.log(chalk.yellow('\n⏰ Max runtime exceeded - checkpointing and exiting'));
429
+ // TASK-10: Save checkpoint with exit reason
430
+ const checkpoint = stateManager.createCheckpoint(state);
431
+ await stateManager.saveCheckpoint(checkpoint, {
432
+ exitReason: 'max_runtime',
433
+ exitCode: EXIT_CODE_RESPAWN,
434
+ });
435
+ console.log(chalk.dim(' Checkpoint saved for respawn'));
436
+ process.exit(EXIT_CODE_RESPAWN);
437
+ }
438
+ // TASK-9: Check context pressure (>80% triggers respawn)
439
+ const pressure = state.contextMonitor.getPressure();
440
+ if (state.contextMonitor.shouldRespawn()) {
441
+ console.log(chalk.magenta(`\n📊 Context pressure critical (${(pressure * 100).toFixed(1)}%) - checkpointing and respawning`));
442
+ // TASK-10: Save checkpoint with exit reason
443
+ const checkpoint = stateManager.createCheckpoint(state);
444
+ await stateManager.saveCheckpoint(checkpoint, {
445
+ exitReason: 'context_pressure',
446
+ exitCode: EXIT_CODE_RESPAWN,
447
+ });
448
+ console.log(chalk.dim(' Checkpoint saved for respawn'));
449
+ process.exit(EXIT_CODE_RESPAWN);
450
+ }
451
+ // TASK-9: Warn at 70% pressure (once per 5 minutes max)
452
+ if (state.contextMonitor.shouldWarn()) {
453
+ const now = new Date();
454
+ const shouldLog = !state.lastPressureWarning ||
455
+ (now.getTime() - state.lastPressureWarning.getTime()) > 5 * 60 * 1000;
456
+ if (shouldLog) {
457
+ const zone = state.contextMonitor.getZone();
458
+ const color = getPressureColor(zone);
459
+ console.log(chalk[color](`\n⚠️ Context pressure elevated: ${state.contextMonitor.formatMetrics()}`));
460
+ state.lastPressureWarning = now;
461
+ }
462
+ }
463
+ // Check progress stall
464
+ if (state.cyclesWithoutProgress >= MAX_CYCLES_WITHOUT_PROGRESS) {
465
+ console.log(chalk.red('\n⚠️ No progress for 10 cycles - escalating'));
466
+ displayBlockers(state, tasks);
467
+ // TASK-10: Save checkpoint with exit reason
468
+ const checkpoint = stateManager.createCheckpoint(state);
469
+ await stateManager.saveCheckpoint(checkpoint, {
470
+ exitReason: 'no_progress',
471
+ exitCode: EXIT_CODE_ERROR,
472
+ });
473
+ console.log(chalk.dim(' Checkpoint saved for investigation'));
474
+ process.exit(EXIT_CODE_ERROR);
475
+ }
476
+ try {
477
+ // Step 1: Discover available workers
478
+ const workers = await discoverWorkers();
479
+ // Step 2: Find available tasks (dependencies satisfied, not assigned)
480
+ const availableTasks = getAvailableOrchestratorTasks(tasks, state);
481
+ // Step 3: Match and assign tasks
482
+ const assignments = await assignTasks(state, availableTasks, workers, client, options.verbose);
483
+ if (assignments > 0) {
484
+ state.lastProgressAt = new Date();
485
+ state.cyclesWithoutProgress = 0;
486
+ }
487
+ // Step 4: Poll for completion events
488
+ const events = await pollCompletionEvents(state, client, lastEventId, options.pollInterval);
489
+ // Step 5: Process completion events
490
+ for (const event of events) {
491
+ await processCompletionEvent(state, tasks, event, options.verbose);
492
+ lastEventId = event.id;
493
+ state.lastProgressAt = new Date();
494
+ state.cyclesWithoutProgress = 0;
495
+ // TASK-9: Track event processing in context metrics
496
+ state.contextMonitor.recordEvent();
497
+ }
498
+ // TASK-9: Update context metrics for this cycle
499
+ // Estimate tokens from cycle activity (API calls, events, state updates)
500
+ const cycleTokenEstimate = 500 + (assignments * 200) + (events.length * 300);
501
+ state.contextMonitor.addTokens(cycleTokenEstimate);
502
+ // Update status display
503
+ if (options.verbose) {
504
+ displayCycleStatus(state, tasks, workers.length, availableTasks.length);
505
+ // TASK-9: Show context pressure in verbose mode
506
+ console.log(chalk.dim(` 📊 Context: ${state.contextMonitor.formatMetrics()}`));
507
+ }
508
+ else {
509
+ displayProgressBar(state, tasks);
510
+ }
511
+ // Increment cycle counter if no progress
512
+ if (assignments === 0 && events.length === 0) {
513
+ state.cyclesWithoutProgress++;
514
+ }
515
+ // TASK-8: Persist state to graph every 30 seconds
516
+ const now = Date.now();
517
+ if (now - lastGraphPersist >= GRAPH_PERSIST_INTERVAL) {
518
+ try {
519
+ const checkpoint = stateManager.createCheckpoint(state);
520
+ // Add persistence timestamp
521
+ checkpoint.persistedAt = new Date().toISOString();
522
+ checkpoint.recoveredFrom = state.recoveredFromCheckpointId;
523
+ // Persist to graph (non-blocking)
524
+ await persistStateToGraph(checkpoint, graphClient);
525
+ lastGraphPersist = now;
526
+ state.lastGraphPersist = new Date();
527
+ if (options.verbose) {
528
+ console.log(chalk.dim(` 💾 State persisted to graph`));
529
+ }
530
+ }
531
+ catch (persistError) {
532
+ // Log but don't fail - graph persistence is best-effort
533
+ if (options.verbose) {
534
+ console.log(chalk.yellow(` ⚠️ Graph persistence failed: ${persistError.message}`));
535
+ }
536
+ }
537
+ }
538
+ // Wait before next cycle
539
+ await sleep(options.pollInterval);
540
+ }
541
+ catch (error) {
542
+ console.error(chalk.red(`\n⚠️ Error in orchestration cycle: ${error.message}`));
543
+ console.log(chalk.dim(' Retrying in 30 seconds...'));
544
+ await sleep(30000);
545
+ }
546
+ }
547
+ }
548
+ // ============================================================
549
+ // Worker Discovery
550
+ // ============================================================
551
+ async function discoverWorkers() {
552
+ try {
553
+ const response = await AgentClient.list({
554
+ status: 'active',
555
+ limit: 100,
556
+ });
557
+ // Filter out orchestrators (only get workers)
558
+ return response.agents.filter(agent => !agent.capabilities.includes('orchestration')).map(agent => ({
559
+ id: agent.id,
560
+ name: agent.name,
561
+ capabilities: agent.capabilities,
562
+ status: agent.status,
563
+ }));
564
+ }
565
+ catch (error) {
566
+ // Return empty array if can't discover workers
567
+ return [];
568
+ }
569
+ }
570
+ // ============================================================
571
+ // Task Assignment
572
+ // ============================================================
573
+ function getAvailableOrchestratorTasks(tasks, state) {
574
+ return tasks.filter(task => {
575
+ // Must be pending (not complete, not assigned, not in progress)
576
+ if (task.status !== 'pending') {
577
+ return false;
578
+ }
579
+ // Must not already be assigned
580
+ if (state.inProgressTasks.has(task.id)) {
581
+ return false;
582
+ }
583
+ // All dependencies must be complete
584
+ for (const depId of task.dependsOn) {
585
+ if (!state.completedTasks.has(depId)) {
586
+ return false;
587
+ }
588
+ }
589
+ return true;
590
+ });
591
+ }
592
+ async function assignTasks(state, availableTasks, workers, client, verbose) {
593
+ let assignments = 0;
594
+ // Get idle workers (not already assigned a task)
595
+ const busyAgentIds = new Set(state.inProgressTasks.values());
596
+ const idleWorkers = workers.filter(w => !busyAgentIds.has(w.id));
597
+ if (idleWorkers.length === 0 || availableTasks.length === 0) {
598
+ return 0;
599
+ }
600
+ // Sort tasks by priority (higher first)
601
+ const sortedTasks = [...availableTasks].sort((a, b) => (b.priority || 0) - (a.priority || 0));
602
+ for (const task of sortedTasks) {
603
+ // TASK-8: Duplicate prevention - check if task already assigned
604
+ if (state.inProgressTasks.has(task.id)) {
605
+ if (verbose) {
606
+ console.log(chalk.dim(` Skipping ${task.id} - already assigned`));
607
+ }
608
+ continue;
609
+ }
610
+ // Find a capable worker
611
+ const capableWorker = idleWorkers.find(worker => task.requiredCapabilities.every(cap => worker.capabilities.includes(cap)) ||
612
+ task.requiredCapabilities.length === 0 // No specific requirements
613
+ );
614
+ if (!capableWorker) {
615
+ if (verbose) {
616
+ console.log(chalk.dim(` No capable worker for ${task.id}`));
617
+ }
618
+ continue;
619
+ }
620
+ // Assign task
621
+ try {
622
+ // TASK-8: Double-check not already assigned (race condition protection)
623
+ if (state.inProgressTasks.has(task.id)) {
624
+ if (verbose) {
625
+ console.log(chalk.dim(` Race condition detected for ${task.id} - skipping`));
626
+ }
627
+ continue;
628
+ }
629
+ // Note: This would call the assign API in production
630
+ // For now, we'll track locally
631
+ console.log(chalk.green(` → Assigned ${chalk.bold(task.id)} to ${capableWorker.name}`));
632
+ task.status = 'assigned';
633
+ task.assignedTo = capableWorker.id;
634
+ state.inProgressTasks.set(task.id, capableWorker.id);
635
+ state.assignmentHistory.push({
636
+ taskId: task.id,
637
+ agentId: capableWorker.id,
638
+ assignedAt: new Date(),
639
+ status: 'assigned',
640
+ });
641
+ // Remove worker from idle pool
642
+ const workerIndex = idleWorkers.indexOf(capableWorker);
643
+ if (workerIndex > -1) {
644
+ idleWorkers.splice(workerIndex, 1);
645
+ }
646
+ assignments++;
647
+ }
648
+ catch (error) {
649
+ console.error(chalk.red(` Failed to assign ${task.id}: ${error.message}`));
650
+ }
651
+ }
652
+ return assignments;
653
+ }
654
+ async function pollCompletionEvents(state, client, lastEventId, timeout) {
655
+ // In production, this would call GET /api/v1/events/stream
656
+ // For now, return empty array (events would come from worker agents)
657
+ return [];
658
+ }
659
+ async function processCompletionEvent(state, tasks, event, verbose) {
660
+ const task = tasks.find(t => t.id === event.taskId);
661
+ if (!task)
662
+ return;
663
+ if (event.status === 'completed') {
664
+ console.log(chalk.green(` ✓ ${chalk.bold(event.taskId)} completed by ${event.agentId}`));
665
+ task.status = 'complete';
666
+ state.completedTasks.add(event.taskId);
667
+ state.inProgressTasks.delete(event.taskId);
668
+ // Update assignment history
669
+ const assignment = state.assignmentHistory.find(a => a.taskId === event.taskId && a.status === 'assigned');
670
+ if (assignment) {
671
+ assignment.status = 'completed';
672
+ }
673
+ }
674
+ else if (event.status === 'failed' || event.status === 'released') {
675
+ console.log(chalk.yellow(` ⚠️ ${event.taskId} ${event.status} - returning to queue`));
676
+ task.status = 'pending';
677
+ task.assignedTo = undefined;
678
+ state.inProgressTasks.delete(event.taskId);
679
+ const assignment = state.assignmentHistory.find(a => a.taskId === event.taskId && a.status === 'assigned');
680
+ if (assignment) {
681
+ assignment.status = event.status === 'failed' ? 'failed' : 'released';
682
+ }
683
+ }
684
+ }
685
+ // ============================================================
686
+ // Display Functions
687
+ // ============================================================
688
+ function displayOrchestrationPlan(tasks, waves, stats, verbose) {
689
+ console.log(chalk.bold('\n📋 Orchestration Plan'));
690
+ console.log(chalk.dim('─'.repeat(50)));
691
+ // Summary
692
+ const completed = tasks.filter(t => t.status === 'complete').length;
693
+ const pending = tasks.filter(t => t.status === 'pending').length;
694
+ const inProgress = tasks.filter(t => t.status === 'in_progress' || t.status === 'assigned').length;
695
+ console.log(` Total tasks: ${chalk.bold(tasks.length.toString())}`);
696
+ console.log(` Completed: ${chalk.green(completed.toString())}`);
697
+ console.log(` Pending: ${chalk.yellow(pending.toString())}`);
698
+ console.log(` In progress: ${chalk.cyan(inProgress.toString())}`);
699
+ console.log(` Execution waves: ${chalk.bold(waves.length.toString())}`);
700
+ if (verbose) {
701
+ console.log('');
702
+ console.log(chalk.bold(' Waves:'));
703
+ for (const wave of waves) {
704
+ console.log(` Wave ${wave.wave}: ${wave.tasks.map(t => t.id).join(', ')}`);
705
+ }
706
+ }
707
+ console.log(chalk.dim('─'.repeat(50)));
708
+ }
709
+ function displayProgressBar(state, tasks) {
710
+ const total = tasks.length;
711
+ const completed = state.completedTasks.size;
712
+ const inProgress = state.inProgressTasks.size;
713
+ const pending = total - completed - inProgress;
714
+ const barWidth = 30;
715
+ const completedWidth = Math.round((completed / total) * barWidth);
716
+ const inProgressWidth = Math.round((inProgress / total) * barWidth);
717
+ const pendingWidth = barWidth - completedWidth - inProgressWidth;
718
+ const bar = chalk.green('█'.repeat(completedWidth)) +
719
+ chalk.cyan('▓'.repeat(inProgressWidth)) +
720
+ chalk.dim('░'.repeat(pendingWidth));
721
+ const percent = Math.round((completed / total) * 100);
722
+ // Clear line and print progress
723
+ process.stdout.write(`\r Progress: [${bar}] ${percent}% (${completed}/${total} complete, ${inProgress} in progress)`);
724
+ }
725
+ function displayCycleStatus(state, tasks, workerCount, availableTaskCount) {
726
+ console.log(chalk.dim(`\n Cycle: workers=${workerCount}, available=${availableTaskCount}, in_progress=${state.inProgressTasks.size}`));
727
+ }
728
+ function displayBlockers(state, tasks) {
729
+ console.log(chalk.bold('\n⚠️ Blockers:'));
730
+ const pendingTasks = tasks.filter(t => t.status === 'pending');
731
+ for (const task of pendingTasks) {
732
+ const missingDeps = task.dependsOn.filter(d => !state.completedTasks.has(d));
733
+ if (missingDeps.length > 0) {
734
+ console.log(chalk.yellow(` ${task.id} blocked by: ${missingDeps.join(', ')}`));
735
+ }
736
+ }
737
+ if (state.inProgressTasks.size > 0) {
738
+ console.log(chalk.bold('\n In Progress (may be stuck):'));
739
+ for (const [taskId, agentId] of state.inProgressTasks) {
740
+ console.log(chalk.cyan(` ${taskId} → ${agentId}`));
741
+ }
742
+ }
743
+ }
744
+ function displayFinalStatus(state, tasks) {
745
+ const duration = Date.now() - state.startedAt.getTime();
746
+ const minutes = Math.floor(duration / 60000);
747
+ const seconds = Math.floor((duration % 60000) / 1000);
748
+ console.log(chalk.bold('\n📊 Final Status'));
749
+ console.log(chalk.dim('─'.repeat(50)));
750
+ console.log(` Duration: ${minutes}m ${seconds}s`);
751
+ console.log(` Completed: ${chalk.green(state.completedTasks.size.toString())}/${tasks.length}`);
752
+ console.log(` Assignments made: ${state.assignmentHistory.length}`);
753
+ console.log(chalk.dim('─'.repeat(50)));
754
+ }
755
+ // ============================================================
756
+ // Task Conversion
757
+ // ============================================================
758
+ function convertSprintTasks(sprint) {
759
+ const tasks = [];
760
+ for (const task of sprint.tasks) {
761
+ tasks.push({
762
+ id: task.id,
763
+ title: task.title,
764
+ description: task.title, // Could be enhanced with acceptance criteria
765
+ effort: task.effort,
766
+ priority: parsePriority(task.priority),
767
+ requiredCapabilities: extractCapabilities(task),
768
+ dependsOn: task.dependsOn || [],
769
+ status: mapSprintStatus(task.state),
770
+ });
771
+ }
772
+ return tasks;
773
+ }
774
+ /**
775
+ * Parse priority string to number
776
+ */
777
+ function parsePriority(priority) {
778
+ if (!priority)
779
+ return 0;
780
+ // Handle numeric strings
781
+ const numValue = parseInt(priority, 10);
782
+ if (!isNaN(numValue))
783
+ return numValue;
784
+ // Handle named priorities
785
+ switch (priority.toLowerCase()) {
786
+ case 'critical':
787
+ return 100;
788
+ case 'high':
789
+ return 75;
790
+ case 'medium':
791
+ return 50;
792
+ case 'low':
793
+ return 25;
794
+ default:
795
+ return 0;
796
+ }
797
+ }
798
+ function mapSprintStatus(state) {
799
+ switch (state) {
800
+ case 'complete':
801
+ return 'complete';
802
+ case 'in_progress':
803
+ return 'in_progress';
804
+ case 'paused':
805
+ return 'blocked';
806
+ default:
807
+ return 'pending';
808
+ }
809
+ }
810
+ function mapStatusForDeps(status) {
811
+ switch (status) {
812
+ case 'complete':
813
+ return 'complete';
814
+ case 'in_progress':
815
+ case 'assigned':
816
+ return 'in_progress';
817
+ case 'blocked':
818
+ return 'blocked';
819
+ default:
820
+ return 'pending';
821
+ }
822
+ }
823
+ function extractCapabilities(task) {
824
+ // Extract capabilities from task metadata or files
825
+ const capabilities = [];
826
+ // Infer from file extensions mentioned
827
+ const files = task.files || [];
828
+ for (const file of files) {
829
+ if (file.endsWith('.ts') || file.endsWith('.tsx')) {
830
+ if (!capabilities.includes('typescript')) {
831
+ capabilities.push('typescript');
832
+ }
833
+ }
834
+ if (file.endsWith('.test.ts') || file.endsWith('.spec.ts')) {
835
+ if (!capabilities.includes('testing')) {
836
+ capabilities.push('testing');
837
+ }
838
+ }
839
+ if (file.includes('/api/')) {
840
+ if (!capabilities.includes('api')) {
841
+ capabilities.push('api');
842
+ }
843
+ }
844
+ }
845
+ return capabilities;
846
+ }
847
+ // ============================================================
848
+ // State Persistence (TASK-10: Moved to orchestrator-state.ts)
849
+ // ============================================================
850
+ // State persistence is now handled by OrchestratorStateManager
851
+ // ============================================================
852
+ // Utilities
853
+ // ============================================================
854
+ function sleep(ms) {
855
+ return new Promise(resolve => setTimeout(resolve, ms));
856
+ }
857
+ export default orchestrateCommand;
858
+ //# sourceMappingURL=orchestrate.js.map