@litmers/cursorflow-orchestrator 0.1.18 → 0.1.26
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.
- package/CHANGELOG.md +25 -0
- package/README.md +25 -7
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +178 -6
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +12 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +8 -7
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +126 -77
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1021 -202
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +39 -21
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +268 -163
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +11 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +8 -8
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +16 -2
- package/dist/core/orchestrator.js +439 -105
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +374 -164
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +18 -8
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +17 -11
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +108 -20
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +484 -11
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +25 -0
- package/dist/utils/log-formatter.js +237 -0
- package/dist/utils/log-formatter.js.map +1 -0
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/path.d.ts +19 -0
- package/dist/utils/path.js +77 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +62 -3
- package/dist/utils/state.js +317 -11
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/template.d.ts +14 -0
- package/dist/utils/template.js +122 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/types.d.ts +2 -271
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +187 -6
- package/src/cli/index.ts +12 -1
- package/src/cli/init.ts +8 -7
- package/src/cli/logs.ts +124 -77
- package/src/cli/monitor.ts +1815 -898
- package/src/cli/prepare.ts +41 -21
- package/src/cli/resume.ts +753 -626
- package/src/cli/run.ts +12 -5
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +8 -7
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1131 -704
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +444 -180
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +18 -8
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +18 -11
- package/src/utils/enhanced-logger.ts +122 -60
- package/src/utils/git.ts +517 -11
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +245 -0
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/path.ts +45 -0
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +385 -11
- package/src/utils/task-service.ts +370 -0
- package/src/utils/template.ts +92 -0
- package/src/utils/types.ts +2 -314
- package/templates/basic.json +21 -0
package/src/cli/resume.ts
CHANGED
|
@@ -1,626 +1,753 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CursorFlow resume command
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import { spawn, ChildProcess } from 'child_process';
|
|
8
|
-
import * as logger from '../utils/logger';
|
|
9
|
-
import { loadConfig, getLogsDir } from '../utils/config';
|
|
10
|
-
import { loadState } from '../utils/state';
|
|
11
|
-
import { LaneState } from '../
|
|
12
|
-
import { runDoctor } from '../utils/doctor';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
console.log(''
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Run doctor check
|
|
558
|
-
if (!options.skipDoctor) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const blockingIssues = report.issues.filter(i =>
|
|
570
|
-
i.severity === 'error' &&
|
|
571
|
-
(i.id.startsWith('branch.') || i.id.startsWith('git.'))
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
if (blockingIssues.length > 0) {
|
|
575
|
-
logger.section('🛑 Pre-resume check found issues');
|
|
576
|
-
for (const issue of blockingIssues) {
|
|
577
|
-
logger.error(`${issue.title} (${issue.id})`, '❌');
|
|
578
|
-
console.log(` ${issue.message}`);
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow resume command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
8
|
+
import * as logger from '../utils/logger';
|
|
9
|
+
import { loadConfig, getLogsDir, getPofDir } from '../utils/config';
|
|
10
|
+
import { loadState, saveState } from '../utils/state';
|
|
11
|
+
import { LaneState } from '../types';
|
|
12
|
+
import { runDoctor } from '../utils/doctor';
|
|
13
|
+
import { safeJoin } from '../utils/path';
|
|
14
|
+
import {
|
|
15
|
+
EnhancedLogManager,
|
|
16
|
+
createLogManager,
|
|
17
|
+
ParsedMessage
|
|
18
|
+
} from '../utils/enhanced-logger';
|
|
19
|
+
import { formatMessageForConsole } from '../utils/log-formatter';
|
|
20
|
+
|
|
21
|
+
interface ResumeOptions {
|
|
22
|
+
lane: string | null;
|
|
23
|
+
runDir: string | null;
|
|
24
|
+
clean: boolean;
|
|
25
|
+
restart: boolean;
|
|
26
|
+
skipDoctor: boolean;
|
|
27
|
+
all: boolean;
|
|
28
|
+
status: boolean;
|
|
29
|
+
maxConcurrent: number;
|
|
30
|
+
help: boolean;
|
|
31
|
+
noGit: boolean;
|
|
32
|
+
executor: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function printHelp(): void {
|
|
36
|
+
console.log(`
|
|
37
|
+
Usage: cursorflow resume [lane] [options]
|
|
38
|
+
|
|
39
|
+
Resume interrupted or failed lanes.
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
<lane> Lane name or tasks directory to resume
|
|
43
|
+
--all Resume ALL incomplete/failed lanes
|
|
44
|
+
--status Show status of all lanes in the run (no resume)
|
|
45
|
+
--run-dir <path> Use a specific run directory (default: latest)
|
|
46
|
+
--max-concurrent <n> Max lanes to run in parallel (default: 3)
|
|
47
|
+
--clean Clean up existing worktree before resuming
|
|
48
|
+
--restart Restart from the first task (index 0)
|
|
49
|
+
--skip-doctor Skip environment/branch checks (not recommended)
|
|
50
|
+
--no-git Disable Git operations (must match original run)
|
|
51
|
+
--executor <type> Override executor (default: cursor-agent)
|
|
52
|
+
--help, -h Show help
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
cursorflow resume --status # Check status of all lanes
|
|
56
|
+
cursorflow resume --all # Resume all incomplete lanes
|
|
57
|
+
cursorflow resume lane-1 # Resume single lane
|
|
58
|
+
cursorflow resume _cursorflow/tasks/feat1 # Resume all lanes in directory
|
|
59
|
+
cursorflow resume --all --restart # Restart all incomplete lanes from task 0
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseArgs(args: string[]): ResumeOptions {
|
|
64
|
+
const runDirIdx = args.indexOf('--run-dir');
|
|
65
|
+
const maxConcurrentIdx = args.indexOf('--max-concurrent');
|
|
66
|
+
const executorIdx = args.indexOf('--executor');
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
lane: args.find(a => !a.startsWith('--')) || null,
|
|
70
|
+
runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
|
|
71
|
+
clean: args.includes('--clean'),
|
|
72
|
+
restart: args.includes('--restart'),
|
|
73
|
+
skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
|
|
74
|
+
all: args.includes('--all'),
|
|
75
|
+
status: args.includes('--status'),
|
|
76
|
+
maxConcurrent: maxConcurrentIdx >= 0 ? parseInt(args[maxConcurrentIdx + 1] || '3') : 3,
|
|
77
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
78
|
+
noGit: args.includes('--no-git'),
|
|
79
|
+
executor: executorIdx >= 0 ? args[executorIdx + 1] || null : null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Find the latest run directory
|
|
85
|
+
*/
|
|
86
|
+
function findLatestRunDir(logsDir: string): string | null {
|
|
87
|
+
const runsDir = safeJoin(logsDir, 'runs');
|
|
88
|
+
if (!fs.existsSync(runsDir)) return null;
|
|
89
|
+
|
|
90
|
+
const runs = fs.readdirSync(runsDir)
|
|
91
|
+
.filter(d => d.startsWith('run-'))
|
|
92
|
+
.sort()
|
|
93
|
+
.reverse();
|
|
94
|
+
|
|
95
|
+
return runs.length > 0 ? safeJoin(runsDir, runs[0]!) : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Status indicator colors
|
|
100
|
+
*/
|
|
101
|
+
const STATUS_COLORS: Record<string, string> = {
|
|
102
|
+
completed: '\x1b[32m', // green
|
|
103
|
+
running: '\x1b[36m', // cyan
|
|
104
|
+
pending: '\x1b[33m', // yellow
|
|
105
|
+
failed: '\x1b[31m', // red
|
|
106
|
+
paused: '\x1b[35m', // magenta
|
|
107
|
+
waiting: '\x1b[33m', // yellow
|
|
108
|
+
reviewing: '\x1b[36m', // cyan
|
|
109
|
+
unknown: '\x1b[90m', // gray
|
|
110
|
+
};
|
|
111
|
+
const RESET = '\x1b[0m';
|
|
112
|
+
|
|
113
|
+
interface LaneInfo {
|
|
114
|
+
name: string;
|
|
115
|
+
dir: string;
|
|
116
|
+
state: LaneState | null;
|
|
117
|
+
needsResume: boolean;
|
|
118
|
+
dependsOn: string[];
|
|
119
|
+
isCompleted: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if a process is alive by its PID
|
|
124
|
+
*/
|
|
125
|
+
function isProcessAlive(pid: number): boolean {
|
|
126
|
+
try {
|
|
127
|
+
// On Unix-like systems, sending signal 0 checks if process exists
|
|
128
|
+
process.kill(pid, 0);
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check for zombie "running" lanes and fix them
|
|
137
|
+
* A zombie lane is one that has status "running" but its process is dead
|
|
138
|
+
*/
|
|
139
|
+
function checkAndFixZombieLanes(runDir: string): { fixed: string[]; pofCreated: boolean } {
|
|
140
|
+
const lanesDir = safeJoin(runDir, 'lanes');
|
|
141
|
+
if (!fs.existsSync(lanesDir)) {
|
|
142
|
+
return { fixed: [], pofCreated: false };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const fixed: string[] = [];
|
|
146
|
+
const zombieDetails: Array<{
|
|
147
|
+
name: string;
|
|
148
|
+
pid: number;
|
|
149
|
+
taskIndex: number;
|
|
150
|
+
totalTasks: number;
|
|
151
|
+
}> = [];
|
|
152
|
+
|
|
153
|
+
const laneDirs = fs.readdirSync(lanesDir)
|
|
154
|
+
.filter(f => fs.statSync(safeJoin(lanesDir, f)).isDirectory());
|
|
155
|
+
|
|
156
|
+
for (const laneName of laneDirs) {
|
|
157
|
+
const dir = safeJoin(lanesDir, laneName);
|
|
158
|
+
const statePath = safeJoin(dir, 'state.json');
|
|
159
|
+
|
|
160
|
+
if (!fs.existsSync(statePath)) continue;
|
|
161
|
+
|
|
162
|
+
const state = loadState<LaneState>(statePath);
|
|
163
|
+
if (!state) continue;
|
|
164
|
+
|
|
165
|
+
// Check for zombie: status is "running" but process is dead
|
|
166
|
+
if (state.status === 'running' && state.pid) {
|
|
167
|
+
const alive = isProcessAlive(state.pid);
|
|
168
|
+
|
|
169
|
+
if (!alive) {
|
|
170
|
+
logger.warn(`🧟 Zombie lane detected: ${laneName} (PID ${state.pid} is dead)`);
|
|
171
|
+
|
|
172
|
+
// Update state to failed
|
|
173
|
+
const updatedState: LaneState = {
|
|
174
|
+
...state,
|
|
175
|
+
status: 'failed',
|
|
176
|
+
error: `Process terminated unexpectedly (PID ${state.pid} was running but is now dead)`,
|
|
177
|
+
endTime: Date.now(),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
saveState(statePath, updatedState);
|
|
181
|
+
fixed.push(laneName);
|
|
182
|
+
|
|
183
|
+
zombieDetails.push({
|
|
184
|
+
name: laneName,
|
|
185
|
+
pid: state.pid,
|
|
186
|
+
taskIndex: state.currentTaskIndex,
|
|
187
|
+
totalTasks: state.totalTasks,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
logger.info(` → Status changed to 'failed', ready for resume`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create POF file if any zombies were found
|
|
196
|
+
let pofCreated = false;
|
|
197
|
+
if (zombieDetails.length > 0) {
|
|
198
|
+
const config = loadConfig();
|
|
199
|
+
const pofDir = getPofDir(config);
|
|
200
|
+
if (!fs.existsSync(pofDir)) {
|
|
201
|
+
fs.mkdirSync(pofDir, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const runId = path.basename(runDir);
|
|
205
|
+
const pofPath = safeJoin(pofDir, `pof-${runId}.json`);
|
|
206
|
+
|
|
207
|
+
let existingPof = null;
|
|
208
|
+
try {
|
|
209
|
+
existingPof = JSON.parse(fs.readFileSync(pofPath, 'utf-8'));
|
|
210
|
+
} catch {
|
|
211
|
+
// Ignore errors (file might not exist)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const pof = {
|
|
215
|
+
title: 'Run Failure Post-mortem',
|
|
216
|
+
runId: path.basename(runDir),
|
|
217
|
+
failureTime: new Date().toISOString(),
|
|
218
|
+
detectedAt: new Date().toISOString(),
|
|
219
|
+
summary: `${zombieDetails.length} lane(s) found with dead processes (zombie state)`,
|
|
220
|
+
|
|
221
|
+
rootCause: {
|
|
222
|
+
type: 'ZOMBIE_PROCESS',
|
|
223
|
+
description: 'Lane processes were marked as running but the processes are no longer alive',
|
|
224
|
+
symptoms: [
|
|
225
|
+
'Process PIDs no longer exist in the system',
|
|
226
|
+
'Lanes were stuck in "running" state',
|
|
227
|
+
'No completion or error was recorded before process death',
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
affectedLanes: zombieDetails.map(z => ({
|
|
232
|
+
name: z.name,
|
|
233
|
+
status: 'failed (was: running)',
|
|
234
|
+
task: `[${z.taskIndex + 1}/${z.totalTasks}]`,
|
|
235
|
+
taskIndex: z.taskIndex,
|
|
236
|
+
pid: z.pid,
|
|
237
|
+
reason: 'Process terminated unexpectedly',
|
|
238
|
+
})),
|
|
239
|
+
|
|
240
|
+
possibleCauses: [
|
|
241
|
+
'System killed process due to memory pressure (OOM)',
|
|
242
|
+
'User killed process manually (Ctrl+C, kill command)',
|
|
243
|
+
'Agent timeout exceeded and process was terminated',
|
|
244
|
+
'System restart or crash',
|
|
245
|
+
'Agent hung and watchdog terminated it',
|
|
246
|
+
],
|
|
247
|
+
|
|
248
|
+
recovery: {
|
|
249
|
+
command: `cursorflow resume --all --run-dir ${runDir}`,
|
|
250
|
+
description: 'Resume all failed lanes from their last checkpoint',
|
|
251
|
+
alternativeCommand: `cursorflow resume --all --restart --run-dir ${runDir}`,
|
|
252
|
+
alternativeDescription: 'Restart all failed lanes from the beginning',
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Merge with existing POF if present
|
|
256
|
+
previousFailures: existingPof ? [existingPof] : undefined,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Use atomic write: write to temp file then rename
|
|
260
|
+
const tempPath = `${pofPath}.${Math.random().toString(36).substring(2, 7)}.tmp`;
|
|
261
|
+
try {
|
|
262
|
+
fs.writeFileSync(tempPath, JSON.stringify(pof, null, 2), 'utf8');
|
|
263
|
+
fs.renameSync(tempPath, pofPath);
|
|
264
|
+
pofCreated = true;
|
|
265
|
+
logger.info(`📋 POF file created: ${pofPath}`);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
// If temp file was created, try to clean it up
|
|
268
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch { /* ignore */ }
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { fixed, pofCreated };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get all lane statuses from a run directory
|
|
278
|
+
*/
|
|
279
|
+
function getAllLaneStatuses(runDir: string): LaneInfo[] {
|
|
280
|
+
const lanesDir = safeJoin(runDir, 'lanes');
|
|
281
|
+
if (!fs.existsSync(lanesDir)) {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const lanes = fs.readdirSync(lanesDir)
|
|
286
|
+
.filter(f => fs.statSync(safeJoin(lanesDir, f)).isDirectory())
|
|
287
|
+
.map(name => {
|
|
288
|
+
const dir = safeJoin(lanesDir, name);
|
|
289
|
+
const statePath = safeJoin(dir, 'state.json');
|
|
290
|
+
const state = fs.existsSync(statePath) ? loadState<LaneState>(statePath) : null;
|
|
291
|
+
|
|
292
|
+
// Determine if lane needs resume: everything that is not completed
|
|
293
|
+
const needsResume = state ? (
|
|
294
|
+
state.status !== 'completed'
|
|
295
|
+
) : true;
|
|
296
|
+
|
|
297
|
+
const isCompleted = state?.status === 'completed';
|
|
298
|
+
const dependsOn = state?.dependsOn || [];
|
|
299
|
+
|
|
300
|
+
return { name, dir, state, needsResume, dependsOn, isCompleted };
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return lanes;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check if all dependencies of a lane are completed
|
|
308
|
+
*/
|
|
309
|
+
function areDependenciesCompleted(
|
|
310
|
+
lane: LaneInfo,
|
|
311
|
+
allLanes: LaneInfo[],
|
|
312
|
+
completedLanes: Set<string>
|
|
313
|
+
): boolean {
|
|
314
|
+
if (!lane.dependsOn || lane.dependsOn.length === 0) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const depName of lane.dependsOn) {
|
|
319
|
+
// Check if dependency is in completed set (already succeeded in this resume session)
|
|
320
|
+
if (completedLanes.has(depName)) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check if dependency was already completed before this resume
|
|
325
|
+
const depLane = allLanes.find(l => l.name === depName);
|
|
326
|
+
if (!depLane || !depLane.isCompleted) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Print status of all lanes
|
|
336
|
+
*/
|
|
337
|
+
function printAllLaneStatus(runDir: string): { total: number; completed: number; needsResume: number } {
|
|
338
|
+
const lanes = getAllLaneStatuses(runDir);
|
|
339
|
+
|
|
340
|
+
if (lanes.length === 0) {
|
|
341
|
+
logger.warn('No lanes found in this run.');
|
|
342
|
+
return { total: 0, completed: 0, needsResume: 0 };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
logger.section(`📊 Lane Status (${path.basename(runDir)})`);
|
|
346
|
+
console.log('');
|
|
347
|
+
|
|
348
|
+
// Table header
|
|
349
|
+
console.log(' ' +
|
|
350
|
+
'Lane'.padEnd(25) +
|
|
351
|
+
'Status'.padEnd(12) +
|
|
352
|
+
'Progress'.padEnd(12) +
|
|
353
|
+
'DependsOn'.padEnd(15) +
|
|
354
|
+
'Resumable'
|
|
355
|
+
);
|
|
356
|
+
console.log(' ' + '-'.repeat(75));
|
|
357
|
+
|
|
358
|
+
let completedCount = 0;
|
|
359
|
+
let needsResumeCount = 0;
|
|
360
|
+
const completedSet = new Set<string>();
|
|
361
|
+
|
|
362
|
+
// First pass: collect completed lanes
|
|
363
|
+
for (const lane of lanes) {
|
|
364
|
+
if (lane.isCompleted) {
|
|
365
|
+
completedSet.add(lane.name);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (const lane of lanes) {
|
|
370
|
+
const state = lane.state;
|
|
371
|
+
const status = state?.status || 'unknown';
|
|
372
|
+
const color = STATUS_COLORS[status] || STATUS_COLORS.unknown;
|
|
373
|
+
const progress = state ? `${state.currentTaskIndex}/${state.totalTasks}` : '-/-';
|
|
374
|
+
const dependsOnStr = lane.dependsOn.length > 0 ? lane.dependsOn.join(',').substring(0, 12) : '-';
|
|
375
|
+
|
|
376
|
+
// Check if dependencies are met
|
|
377
|
+
const depsCompleted = areDependenciesCompleted(lane, lanes, completedSet);
|
|
378
|
+
const canResume = lane.needsResume && depsCompleted;
|
|
379
|
+
const blockedByDep = lane.needsResume && !depsCompleted;
|
|
380
|
+
|
|
381
|
+
if (status === 'completed') completedCount++;
|
|
382
|
+
if (lane.needsResume) needsResumeCount++;
|
|
383
|
+
|
|
384
|
+
let resumeIndicator = '';
|
|
385
|
+
if (canResume) {
|
|
386
|
+
resumeIndicator = '\x1b[33m✓\x1b[0m';
|
|
387
|
+
} else if (blockedByDep) {
|
|
388
|
+
resumeIndicator = '\x1b[90m⏳ waiting\x1b[0m';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
console.log(' ' +
|
|
392
|
+
lane.name.padEnd(25) +
|
|
393
|
+
`${color}${status.padEnd(12)}${RESET}` +
|
|
394
|
+
progress.padEnd(12) +
|
|
395
|
+
dependsOnStr.padEnd(15) +
|
|
396
|
+
resumeIndicator
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Show error if failed
|
|
400
|
+
if (status === 'failed' && state?.error) {
|
|
401
|
+
console.log(` ${''.padEnd(25)}\x1b[31m└─ ${state.error.substring(0, 50)}${state.error.length > 50 ? '...' : ''}\x1b[0m`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Show blocked dependency info
|
|
405
|
+
if (blockedByDep) {
|
|
406
|
+
const pendingDeps = lane.dependsOn.filter(d => !completedSet.has(d));
|
|
407
|
+
console.log(` ${''.padEnd(25)}\x1b[90m└─ waiting for: ${pendingDeps.join(', ')}\x1b[0m`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
console.log('');
|
|
412
|
+
console.log(` Total: ${lanes.length} | Completed: ${completedCount} | Needs Resume: ${needsResumeCount}`);
|
|
413
|
+
|
|
414
|
+
if (needsResumeCount > 0) {
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log(' \x1b[33mTip:\x1b[0m Run \x1b[32mcursorflow resume --all\x1b[0m to resume all incomplete lanes');
|
|
417
|
+
console.log(' Lanes with dependencies will wait until their dependencies complete.');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { total: lanes.length, completed: completedCount, needsResume: needsResumeCount };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Resume a single lane and return the child process and its log manager
|
|
425
|
+
*/
|
|
426
|
+
function spawnLaneResume(
|
|
427
|
+
laneName: string,
|
|
428
|
+
laneDir: string,
|
|
429
|
+
state: LaneState,
|
|
430
|
+
options: {
|
|
431
|
+
restart: boolean;
|
|
432
|
+
noGit?: boolean;
|
|
433
|
+
pipelineBranch?: string;
|
|
434
|
+
executor?: string | null;
|
|
435
|
+
enhancedLogConfig?: any;
|
|
436
|
+
}
|
|
437
|
+
): { child: ChildProcess; logManager: EnhancedLogManager } {
|
|
438
|
+
const runnerPath = require.resolve('../core/runner');
|
|
439
|
+
const startIndex = options.restart ? 0 : state.currentTaskIndex;
|
|
440
|
+
|
|
441
|
+
const runnerArgs = [
|
|
442
|
+
runnerPath,
|
|
443
|
+
state.tasksFile!,
|
|
444
|
+
'--run-dir', laneDir,
|
|
445
|
+
'--start-index', String(startIndex),
|
|
446
|
+
];
|
|
447
|
+
|
|
448
|
+
if (state.worktreeDir) {
|
|
449
|
+
runnerArgs.push('--worktree-dir', state.worktreeDir);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (options.noGit) {
|
|
453
|
+
runnerArgs.push('--no-git');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Explicitly pass pipeline branch if available (either from state or override)
|
|
457
|
+
const branch = options.pipelineBranch || state.pipelineBranch;
|
|
458
|
+
if (branch) {
|
|
459
|
+
runnerArgs.push('--pipeline-branch', branch);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Pass executor if provided
|
|
463
|
+
if (options.executor) {
|
|
464
|
+
runnerArgs.push('--executor', options.executor);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const logManager = createLogManager(laneDir, laneName, options.enhancedLogConfig || {}, (msg) => {
|
|
468
|
+
const formatted = formatMessageForConsole(msg, {
|
|
469
|
+
laneLabel: `[${laneName}]`,
|
|
470
|
+
includeTimestamp: true
|
|
471
|
+
});
|
|
472
|
+
process.stdout.write(formatted + '\n');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const child = spawn('node', runnerArgs, {
|
|
476
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
477
|
+
env: process.env,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
if (child.stdout) {
|
|
481
|
+
child.stdout.on('data', (data: Buffer) => {
|
|
482
|
+
logManager.writeStdout(data);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (child.stderr) {
|
|
487
|
+
child.stderr.on('data', (data: Buffer) => {
|
|
488
|
+
logManager.writeStderr(data);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
child.on('exit', () => {
|
|
493
|
+
logManager.close();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
return { child, logManager };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Wait for a child process to exit
|
|
501
|
+
*/
|
|
502
|
+
function waitForChild(child: ChildProcess): Promise<number> {
|
|
503
|
+
return new Promise((resolve, reject) => {
|
|
504
|
+
child.on('exit', (code) => {
|
|
505
|
+
resolve(code ?? -1);
|
|
506
|
+
});
|
|
507
|
+
child.on('error', (err) => {
|
|
508
|
+
reject(err);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Resume multiple lanes with concurrency control and dependency awareness
|
|
515
|
+
*/
|
|
516
|
+
async function resumeLanes(
|
|
517
|
+
lanesToResume: LaneInfo[],
|
|
518
|
+
allLanes: LaneInfo[],
|
|
519
|
+
options: {
|
|
520
|
+
restart: boolean;
|
|
521
|
+
maxConcurrent: number;
|
|
522
|
+
skipDoctor: boolean;
|
|
523
|
+
noGit: boolean;
|
|
524
|
+
executor: string | null;
|
|
525
|
+
enhancedLogConfig?: any;
|
|
526
|
+
}
|
|
527
|
+
): Promise<{ succeeded: string[]; failed: string[]; skipped: string[] }> {
|
|
528
|
+
const completedSet = new Set<string>(allLanes.filter(l => l.isCompleted).map(l => l.name));
|
|
529
|
+
const toResumeNames = new Set<string>(lanesToResume.map(l => l.name));
|
|
530
|
+
|
|
531
|
+
const skippedLanes: string[] = [];
|
|
532
|
+
const resolvableLanes: LaneInfo[] = [];
|
|
533
|
+
|
|
534
|
+
for (const lane of lanesToResume) {
|
|
535
|
+
// Check if all dependencies can be satisfied (either already completed or in the resume list)
|
|
536
|
+
const unmetDeps = lane.dependsOn.filter(dep =>
|
|
537
|
+
!completedSet.has(dep) && !toResumeNames.has(dep)
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
if (unmetDeps.length > 0) {
|
|
541
|
+
logger.warn(`⏭ Skipping ${lane.name}: unresolvable dependencies (${unmetDeps.join(', ')})`);
|
|
542
|
+
skippedLanes.push(lane.name);
|
|
543
|
+
} else {
|
|
544
|
+
resolvableLanes.push(lane);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (resolvableLanes.length === 0) {
|
|
549
|
+
logger.warn('No lanes can be resumed due to dependency constraints.');
|
|
550
|
+
return { succeeded: [], failed: [], skipped: skippedLanes };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
logger.section(`🔁 Resuming ${resolvableLanes.length} Lane(s)`);
|
|
554
|
+
logger.info(`Max concurrent: ${options.maxConcurrent}`);
|
|
555
|
+
logger.info(`Mode: ${options.restart ? 'Restart from beginning' : 'Continue from last task'}`);
|
|
556
|
+
|
|
557
|
+
// Run doctor check once if needed (check git status)
|
|
558
|
+
if (!options.skipDoctor) {
|
|
559
|
+
logger.info('Running pre-flight checks...');
|
|
560
|
+
const firstLane = resolvableLanes[0]!;
|
|
561
|
+
const tasksDir = path.dirname(firstLane.state!.tasksFile!);
|
|
562
|
+
|
|
563
|
+
const report = runDoctor({
|
|
564
|
+
cwd: process.cwd(),
|
|
565
|
+
tasksDir,
|
|
566
|
+
includeCursorAgentChecks: false,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const blockingIssues = report.issues.filter(i =>
|
|
570
|
+
i.severity === 'error' &&
|
|
571
|
+
(i.id.startsWith('branch.') || i.id.startsWith('git.'))
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
if (blockingIssues.length > 0) {
|
|
575
|
+
logger.section('🛑 Pre-resume check found issues');
|
|
576
|
+
for (const issue of blockingIssues) {
|
|
577
|
+
logger.error(`${issue.title} (${issue.id})`, '❌');
|
|
578
|
+
console.log(` ${issue.message}`);
|
|
579
|
+
}
|
|
580
|
+
throw new Error('Pre-resume checks failed. Use --skip-doctor to bypass.');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const succeeded: string[] = [];
|
|
585
|
+
const failed: string[] = [];
|
|
586
|
+
const sessionCompleted = new Set<string>(completedSet);
|
|
587
|
+
const pending = new Set<string>(resolvableLanes.map(l => l.name));
|
|
588
|
+
const active: Map<string, ChildProcess> = new Map();
|
|
589
|
+
const laneMap = new Map<string, LaneInfo>(resolvableLanes.map(l => [l.name, l]));
|
|
590
|
+
|
|
591
|
+
const findReadyLane = (): LaneInfo | null => {
|
|
592
|
+
for (const laneName of pending) {
|
|
593
|
+
const lane = laneMap.get(laneName)!;
|
|
594
|
+
if (areDependenciesCompleted(lane, allLanes, sessionCompleted)) {
|
|
595
|
+
return lane;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const processNext = (): void => {
|
|
602
|
+
while (active.size < options.maxConcurrent) {
|
|
603
|
+
const lane = findReadyLane();
|
|
604
|
+
if (!lane) {
|
|
605
|
+
if (pending.size > 0 && active.size === 0) {
|
|
606
|
+
const pendingList = Array.from(pending).join(', ');
|
|
607
|
+
logger.error(`Deadlock detected! Lanes waiting: ${pendingList}`);
|
|
608
|
+
for (const ln of pending) failed.push(ln);
|
|
609
|
+
pending.clear();
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
pending.delete(lane.name);
|
|
615
|
+
const depsInfo = lane.dependsOn.length > 0 ? ` (after: ${lane.dependsOn.join(', ')})` : '';
|
|
616
|
+
logger.info(`Starting: ${lane.name} (task ${lane.state!.currentTaskIndex}/${lane.state!.totalTasks})${depsInfo}`);
|
|
617
|
+
|
|
618
|
+
const { child } = spawnLaneResume(lane.name, lane.dir, lane.state!, {
|
|
619
|
+
restart: options.restart,
|
|
620
|
+
noGit: options.noGit,
|
|
621
|
+
executor: options.executor,
|
|
622
|
+
enhancedLogConfig: options.enhancedLogConfig,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
active.set(lane.name, child);
|
|
626
|
+
|
|
627
|
+
waitForChild(child).then(code => {
|
|
628
|
+
active.delete(lane.name);
|
|
629
|
+
if (code === 0) {
|
|
630
|
+
logger.success(`✓ ${lane.name} completed`);
|
|
631
|
+
succeeded.push(lane.name);
|
|
632
|
+
sessionCompleted.add(lane.name);
|
|
633
|
+
} else if (code === 2) {
|
|
634
|
+
logger.warn(`⚠ ${lane.name} blocked on dependency change`);
|
|
635
|
+
failed.push(lane.name);
|
|
636
|
+
} else {
|
|
637
|
+
logger.error(`✗ ${lane.name} failed (exit ${code})`);
|
|
638
|
+
failed.push(lane.name);
|
|
639
|
+
}
|
|
640
|
+
processNext();
|
|
641
|
+
}).catch(err => {
|
|
642
|
+
active.delete(lane.name);
|
|
643
|
+
logger.error(`✗ ${lane.name} error: ${err.message}`);
|
|
644
|
+
failed.push(lane.name);
|
|
645
|
+
processNext();
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
processNext();
|
|
651
|
+
|
|
652
|
+
while (active.size > 0 || pending.size > 0) {
|
|
653
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
654
|
+
if (active.size < options.maxConcurrent && pending.size > 0) {
|
|
655
|
+
processNext();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
console.log('');
|
|
660
|
+
logger.section('📊 Resume Summary');
|
|
661
|
+
logger.info(`Succeeded: ${succeeded.length}`);
|
|
662
|
+
if (failed.length > 0) logger.error(`Failed: ${failed.length} (${failed.join(', ')})`);
|
|
663
|
+
if (skippedLanes.length > 0) logger.warn(`Skipped: ${skippedLanes.length} (${skippedLanes.join(', ')})`);
|
|
664
|
+
|
|
665
|
+
return { succeeded, failed, skipped: skippedLanes };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function resume(args: string[]): Promise<void> {
|
|
669
|
+
const options = parseArgs(args);
|
|
670
|
+
|
|
671
|
+
if (options.help) {
|
|
672
|
+
printHelp();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const config = loadConfig();
|
|
677
|
+
const logsDir = getLogsDir(config);
|
|
678
|
+
|
|
679
|
+
let runDir = options.runDir;
|
|
680
|
+
if (!runDir) {
|
|
681
|
+
runDir = findLatestRunDir(logsDir);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (!runDir || !fs.existsSync(runDir)) {
|
|
685
|
+
throw new Error(`Run directory not found: ${runDir || 'latest'}. Have you run any tasks yet?`);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const allLanes = getAllLaneStatuses(runDir);
|
|
689
|
+
let lanesToResume: LaneInfo[] = [];
|
|
690
|
+
|
|
691
|
+
// Check if the lane argument is actually a tasks directory
|
|
692
|
+
if (options.lane && fs.existsSync(options.lane) && fs.statSync(options.lane).isDirectory()) {
|
|
693
|
+
const tasksDir = path.resolve(options.lane);
|
|
694
|
+
lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile && path.resolve(l.state.tasksFile).startsWith(tasksDir));
|
|
695
|
+
|
|
696
|
+
if (lanesToResume.length > 0) {
|
|
697
|
+
logger.info(`📂 Task directory detected: ${options.lane}`);
|
|
698
|
+
logger.info(`Resuming ${lanesToResume.length} lane(s) from this directory.`);
|
|
699
|
+
} else {
|
|
700
|
+
logger.warn(`No incomplete lanes found using tasks from directory: ${options.lane}`);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
} else if (options.all) {
|
|
704
|
+
lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile);
|
|
705
|
+
} else if (options.lane) {
|
|
706
|
+
const lane = allLanes.find(l => l.name === options.lane);
|
|
707
|
+
if (!lane) {
|
|
708
|
+
throw new Error(`Lane '${options.lane}' not found in run directory.`);
|
|
709
|
+
}
|
|
710
|
+
if (!lane.needsResume) {
|
|
711
|
+
logger.success(`Lane '${options.lane}' is already completed.`);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
lanesToResume = [lane];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Check for zombie lanes
|
|
718
|
+
const zombieCheck = checkAndFixZombieLanes(runDir);
|
|
719
|
+
if (zombieCheck.fixed.length > 0) {
|
|
720
|
+
logger.section('🔧 Zombie Lane Recovery');
|
|
721
|
+
logger.info(`Fixed ${zombieCheck.fixed.length} zombie lane(s): ${zombieCheck.fixed.join(', ')}`);
|
|
722
|
+
console.log('');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (options.status) {
|
|
726
|
+
printAllLaneStatus(runDir);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (lanesToResume.length === 0) {
|
|
731
|
+
if (options.lane || options.all) {
|
|
732
|
+
logger.success('No lanes need to be resumed.');
|
|
733
|
+
} else {
|
|
734
|
+
printAllLaneStatus(runDir);
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const result = await resumeLanes(lanesToResume, allLanes, {
|
|
740
|
+
restart: options.restart,
|
|
741
|
+
maxConcurrent: options.maxConcurrent,
|
|
742
|
+
skipDoctor: options.skipDoctor,
|
|
743
|
+
noGit: options.noGit,
|
|
744
|
+
executor: options.executor,
|
|
745
|
+
enhancedLogConfig: config.enhancedLogging,
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
if (result.failed.length > 0) {
|
|
749
|
+
throw new Error(`${result.failed.length} lane(s) failed to complete`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export = resume;
|