@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.
Files changed (234) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +25 -7
  3. package/commands/cursorflow-clean.md +19 -0
  4. package/commands/cursorflow-runs.md +59 -0
  5. package/commands/cursorflow-stop.md +55 -0
  6. package/dist/cli/clean.js +178 -6
  7. package/dist/cli/clean.js.map +1 -1
  8. package/dist/cli/index.js +12 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +8 -7
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/logs.js +126 -77
  13. package/dist/cli/logs.js.map +1 -1
  14. package/dist/cli/monitor.d.ts +7 -0
  15. package/dist/cli/monitor.js +1021 -202
  16. package/dist/cli/monitor.js.map +1 -1
  17. package/dist/cli/prepare.js +39 -21
  18. package/dist/cli/prepare.js.map +1 -1
  19. package/dist/cli/resume.js +268 -163
  20. package/dist/cli/resume.js.map +1 -1
  21. package/dist/cli/run.js +11 -5
  22. package/dist/cli/run.js.map +1 -1
  23. package/dist/cli/runs.d.ts +5 -0
  24. package/dist/cli/runs.js +214 -0
  25. package/dist/cli/runs.js.map +1 -0
  26. package/dist/cli/setup-commands.js +0 -0
  27. package/dist/cli/signal.js +8 -8
  28. package/dist/cli/signal.js.map +1 -1
  29. package/dist/cli/stop.d.ts +5 -0
  30. package/dist/cli/stop.js +215 -0
  31. package/dist/cli/stop.js.map +1 -0
  32. package/dist/cli/tasks.d.ts +10 -0
  33. package/dist/cli/tasks.js +165 -0
  34. package/dist/cli/tasks.js.map +1 -0
  35. package/dist/core/auto-recovery.d.ts +212 -0
  36. package/dist/core/auto-recovery.js +737 -0
  37. package/dist/core/auto-recovery.js.map +1 -0
  38. package/dist/core/failure-policy.d.ts +156 -0
  39. package/dist/core/failure-policy.js +488 -0
  40. package/dist/core/failure-policy.js.map +1 -0
  41. package/dist/core/orchestrator.d.ts +16 -2
  42. package/dist/core/orchestrator.js +439 -105
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/reviewer.d.ts +2 -0
  45. package/dist/core/reviewer.js +2 -0
  46. package/dist/core/reviewer.js.map +1 -1
  47. package/dist/core/runner.d.ts +33 -10
  48. package/dist/core/runner.js +374 -164
  49. package/dist/core/runner.js.map +1 -1
  50. package/dist/services/logging/buffer.d.ts +67 -0
  51. package/dist/services/logging/buffer.js +309 -0
  52. package/dist/services/logging/buffer.js.map +1 -0
  53. package/dist/services/logging/console.d.ts +89 -0
  54. package/dist/services/logging/console.js +169 -0
  55. package/dist/services/logging/console.js.map +1 -0
  56. package/dist/services/logging/file-writer.d.ts +71 -0
  57. package/dist/services/logging/file-writer.js +516 -0
  58. package/dist/services/logging/file-writer.js.map +1 -0
  59. package/dist/services/logging/formatter.d.ts +39 -0
  60. package/dist/services/logging/formatter.js +227 -0
  61. package/dist/services/logging/formatter.js.map +1 -0
  62. package/dist/services/logging/index.d.ts +11 -0
  63. package/dist/services/logging/index.js +30 -0
  64. package/dist/services/logging/index.js.map +1 -0
  65. package/dist/services/logging/parser.d.ts +31 -0
  66. package/dist/services/logging/parser.js +222 -0
  67. package/dist/services/logging/parser.js.map +1 -0
  68. package/dist/services/process/index.d.ts +59 -0
  69. package/dist/services/process/index.js +257 -0
  70. package/dist/services/process/index.js.map +1 -0
  71. package/dist/types/agent.d.ts +20 -0
  72. package/dist/types/agent.js +6 -0
  73. package/dist/types/agent.js.map +1 -0
  74. package/dist/types/config.d.ts +65 -0
  75. package/dist/types/config.js +6 -0
  76. package/dist/types/config.js.map +1 -0
  77. package/dist/types/events.d.ts +125 -0
  78. package/dist/types/events.js +6 -0
  79. package/dist/types/events.js.map +1 -0
  80. package/dist/types/index.d.ts +12 -0
  81. package/dist/types/index.js +37 -0
  82. package/dist/types/index.js.map +1 -0
  83. package/dist/types/lane.d.ts +43 -0
  84. package/dist/types/lane.js +6 -0
  85. package/dist/types/lane.js.map +1 -0
  86. package/dist/types/logging.d.ts +71 -0
  87. package/dist/types/logging.js +16 -0
  88. package/dist/types/logging.js.map +1 -0
  89. package/dist/types/review.d.ts +17 -0
  90. package/dist/types/review.js +6 -0
  91. package/dist/types/review.js.map +1 -0
  92. package/dist/types/run.d.ts +32 -0
  93. package/dist/types/run.js +6 -0
  94. package/dist/types/run.js.map +1 -0
  95. package/dist/types/task.d.ts +71 -0
  96. package/dist/types/task.js +6 -0
  97. package/dist/types/task.js.map +1 -0
  98. package/dist/ui/components.d.ts +134 -0
  99. package/dist/ui/components.js +389 -0
  100. package/dist/ui/components.js.map +1 -0
  101. package/dist/ui/log-viewer.d.ts +49 -0
  102. package/dist/ui/log-viewer.js +449 -0
  103. package/dist/ui/log-viewer.js.map +1 -0
  104. package/dist/utils/checkpoint.d.ts +87 -0
  105. package/dist/utils/checkpoint.js +317 -0
  106. package/dist/utils/checkpoint.js.map +1 -0
  107. package/dist/utils/config.d.ts +4 -0
  108. package/dist/utils/config.js +18 -8
  109. package/dist/utils/config.js.map +1 -1
  110. package/dist/utils/cursor-agent.js.map +1 -1
  111. package/dist/utils/dependency.d.ts +74 -0
  112. package/dist/utils/dependency.js +420 -0
  113. package/dist/utils/dependency.js.map +1 -0
  114. package/dist/utils/doctor.js +17 -11
  115. package/dist/utils/doctor.js.map +1 -1
  116. package/dist/utils/enhanced-logger.d.ts +10 -33
  117. package/dist/utils/enhanced-logger.js +108 -20
  118. package/dist/utils/enhanced-logger.js.map +1 -1
  119. package/dist/utils/git.d.ts +121 -0
  120. package/dist/utils/git.js +484 -11
  121. package/dist/utils/git.js.map +1 -1
  122. package/dist/utils/health.d.ts +91 -0
  123. package/dist/utils/health.js +556 -0
  124. package/dist/utils/health.js.map +1 -0
  125. package/dist/utils/lock.d.ts +95 -0
  126. package/dist/utils/lock.js +332 -0
  127. package/dist/utils/lock.js.map +1 -0
  128. package/dist/utils/log-buffer.d.ts +17 -0
  129. package/dist/utils/log-buffer.js +14 -0
  130. package/dist/utils/log-buffer.js.map +1 -0
  131. package/dist/utils/log-constants.d.ts +23 -0
  132. package/dist/utils/log-constants.js +28 -0
  133. package/dist/utils/log-constants.js.map +1 -0
  134. package/dist/utils/log-formatter.d.ts +25 -0
  135. package/dist/utils/log-formatter.js +237 -0
  136. package/dist/utils/log-formatter.js.map +1 -0
  137. package/dist/utils/log-service.d.ts +19 -0
  138. package/dist/utils/log-service.js +47 -0
  139. package/dist/utils/log-service.js.map +1 -0
  140. package/dist/utils/logger.d.ts +46 -27
  141. package/dist/utils/logger.js +82 -60
  142. package/dist/utils/logger.js.map +1 -1
  143. package/dist/utils/path.d.ts +19 -0
  144. package/dist/utils/path.js +77 -0
  145. package/dist/utils/path.js.map +1 -0
  146. package/dist/utils/process-manager.d.ts +21 -0
  147. package/dist/utils/process-manager.js +138 -0
  148. package/dist/utils/process-manager.js.map +1 -0
  149. package/dist/utils/retry.d.ts +121 -0
  150. package/dist/utils/retry.js +374 -0
  151. package/dist/utils/retry.js.map +1 -0
  152. package/dist/utils/run-service.d.ts +88 -0
  153. package/dist/utils/run-service.js +412 -0
  154. package/dist/utils/run-service.js.map +1 -0
  155. package/dist/utils/state.d.ts +62 -3
  156. package/dist/utils/state.js +317 -11
  157. package/dist/utils/state.js.map +1 -1
  158. package/dist/utils/task-service.d.ts +82 -0
  159. package/dist/utils/task-service.js +348 -0
  160. package/dist/utils/task-service.js.map +1 -0
  161. package/dist/utils/template.d.ts +14 -0
  162. package/dist/utils/template.js +122 -0
  163. package/dist/utils/template.js.map +1 -0
  164. package/dist/utils/types.d.ts +2 -271
  165. package/dist/utils/types.js +16 -0
  166. package/dist/utils/types.js.map +1 -1
  167. package/package.json +38 -23
  168. package/scripts/ai-security-check.js +0 -1
  169. package/scripts/local-security-gate.sh +0 -0
  170. package/scripts/monitor-lanes.sh +94 -0
  171. package/scripts/patches/test-cursor-agent.js +0 -1
  172. package/scripts/release.sh +0 -0
  173. package/scripts/setup-security.sh +0 -0
  174. package/scripts/stream-logs.sh +72 -0
  175. package/scripts/verify-and-fix.sh +0 -0
  176. package/src/cli/clean.ts +187 -6
  177. package/src/cli/index.ts +12 -1
  178. package/src/cli/init.ts +8 -7
  179. package/src/cli/logs.ts +124 -77
  180. package/src/cli/monitor.ts +1815 -898
  181. package/src/cli/prepare.ts +41 -21
  182. package/src/cli/resume.ts +753 -626
  183. package/src/cli/run.ts +12 -5
  184. package/src/cli/runs.ts +212 -0
  185. package/src/cli/setup-commands.ts +0 -0
  186. package/src/cli/signal.ts +8 -7
  187. package/src/cli/stop.ts +209 -0
  188. package/src/cli/tasks.ts +154 -0
  189. package/src/core/auto-recovery.ts +909 -0
  190. package/src/core/failure-policy.ts +592 -0
  191. package/src/core/orchestrator.ts +1131 -704
  192. package/src/core/reviewer.ts +4 -0
  193. package/src/core/runner.ts +444 -180
  194. package/src/services/logging/buffer.ts +326 -0
  195. package/src/services/logging/console.ts +193 -0
  196. package/src/services/logging/file-writer.ts +526 -0
  197. package/src/services/logging/formatter.ts +268 -0
  198. package/src/services/logging/index.ts +16 -0
  199. package/src/services/logging/parser.ts +232 -0
  200. package/src/services/process/index.ts +261 -0
  201. package/src/types/agent.ts +24 -0
  202. package/src/types/config.ts +79 -0
  203. package/src/types/events.ts +156 -0
  204. package/src/types/index.ts +29 -0
  205. package/src/types/lane.ts +56 -0
  206. package/src/types/logging.ts +96 -0
  207. package/src/types/review.ts +20 -0
  208. package/src/types/run.ts +37 -0
  209. package/src/types/task.ts +79 -0
  210. package/src/ui/components.ts +430 -0
  211. package/src/ui/log-viewer.ts +485 -0
  212. package/src/utils/checkpoint.ts +374 -0
  213. package/src/utils/config.ts +18 -8
  214. package/src/utils/cursor-agent.ts +1 -1
  215. package/src/utils/dependency.ts +482 -0
  216. package/src/utils/doctor.ts +18 -11
  217. package/src/utils/enhanced-logger.ts +122 -60
  218. package/src/utils/git.ts +517 -11
  219. package/src/utils/health.ts +596 -0
  220. package/src/utils/lock.ts +346 -0
  221. package/src/utils/log-buffer.ts +28 -0
  222. package/src/utils/log-constants.ts +26 -0
  223. package/src/utils/log-formatter.ts +245 -0
  224. package/src/utils/log-service.ts +49 -0
  225. package/src/utils/logger.ts +100 -51
  226. package/src/utils/path.ts +45 -0
  227. package/src/utils/process-manager.ts +100 -0
  228. package/src/utils/retry.ts +413 -0
  229. package/src/utils/run-service.ts +433 -0
  230. package/src/utils/state.ts +385 -11
  231. package/src/utils/task-service.ts +370 -0
  232. package/src/utils/template.ts +92 -0
  233. package/src/utils/types.ts +2 -314
  234. package/templates/basic.json +21 -0
@@ -0,0 +1,596 @@
1
+ /**
2
+ * Health check system for CursorFlow
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import { spawnSync } from 'child_process';
9
+ import { safeJoin } from './path';
10
+ import * as git from './git';
11
+ import { cleanStaleLocks, getLockDir, getLockStatus } from './lock';
12
+ import { checkCursorAgentInstalled, checkCursorAuth } from './cursor-agent';
13
+ import * as logger from './logger';
14
+
15
+ export interface HealthCheckResult {
16
+ name: string;
17
+ ok: boolean;
18
+ message: string;
19
+ details?: Record<string, any>;
20
+ latencyMs?: number;
21
+ }
22
+
23
+ export interface SystemHealth {
24
+ healthy: boolean;
25
+ timestamp: number;
26
+ checks: HealthCheckResult[];
27
+ summary: {
28
+ passed: number;
29
+ failed: number;
30
+ warnings: number;
31
+ };
32
+ }
33
+
34
+ export interface PreflightResult {
35
+ canProceed: boolean;
36
+ blockers: string[];
37
+ warnings: string[];
38
+ recommendations: string[];
39
+ }
40
+
41
+ /**
42
+ * Check if cursor-agent is installed and responsive
43
+ */
44
+ export async function checkAgentHealth(): Promise<HealthCheckResult> {
45
+ const start = Date.now();
46
+
47
+ if (!checkCursorAgentInstalled()) {
48
+ return {
49
+ name: 'cursor-agent',
50
+ ok: false,
51
+ message: 'cursor-agent CLI is not installed',
52
+ latencyMs: Date.now() - start,
53
+ };
54
+ }
55
+
56
+ try {
57
+ const result = spawnSync('cursor-agent', ['--version'], {
58
+ encoding: 'utf8',
59
+ stdio: 'pipe',
60
+ timeout: 5000,
61
+ });
62
+
63
+ if (result.status === 0) {
64
+ const version = result.stdout.trim();
65
+ return {
66
+ name: 'cursor-agent',
67
+ ok: true,
68
+ message: `cursor-agent installed (${version})`,
69
+ details: { version },
70
+ latencyMs: Date.now() - start,
71
+ };
72
+ } else {
73
+ return {
74
+ name: 'cursor-agent',
75
+ ok: false,
76
+ message: `cursor-agent error: ${result.stderr}`,
77
+ latencyMs: Date.now() - start,
78
+ };
79
+ }
80
+ } catch (error: any) {
81
+ return {
82
+ name: 'cursor-agent',
83
+ ok: false,
84
+ message: `cursor-agent check failed: ${error.message}`,
85
+ latencyMs: Date.now() - start,
86
+ };
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Check cursor authentication status
92
+ */
93
+ export async function checkAuthHealth(): Promise<HealthCheckResult> {
94
+ const start = Date.now();
95
+
96
+ try {
97
+ const authStatus = checkCursorAuth();
98
+
99
+ return {
100
+ name: 'cursor-auth',
101
+ ok: authStatus.authenticated,
102
+ message: authStatus.message,
103
+ details: authStatus.details ? { details: authStatus.details } : undefined,
104
+ latencyMs: Date.now() - start,
105
+ };
106
+ } catch (error: any) {
107
+ return {
108
+ name: 'cursor-auth',
109
+ ok: false,
110
+ message: `Auth check failed: ${error.message}`,
111
+ latencyMs: Date.now() - start,
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Check Git repository status
118
+ */
119
+ export async function checkGitHealth(cwd?: string): Promise<HealthCheckResult> {
120
+ const start = Date.now();
121
+
122
+ try {
123
+ if (!git.isGitRepo(cwd)) {
124
+ return {
125
+ name: 'git-repo',
126
+ ok: false,
127
+ message: 'Not a Git repository',
128
+ latencyMs: Date.now() - start,
129
+ };
130
+ }
131
+
132
+ const branch = git.getCurrentBranch(cwd);
133
+ const hasRemote = git.remoteExists('origin', { cwd });
134
+
135
+ return {
136
+ name: 'git-repo',
137
+ ok: true,
138
+ message: `Git repository OK (branch: ${branch})`,
139
+ details: {
140
+ branch,
141
+ hasRemote,
142
+ },
143
+ latencyMs: Date.now() - start,
144
+ };
145
+ } catch (error: any) {
146
+ return {
147
+ name: 'git-repo',
148
+ ok: false,
149
+ message: `Git check failed: ${error.message}`,
150
+ latencyMs: Date.now() - start,
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Check Git remote connectivity
157
+ */
158
+ export async function checkGitRemoteHealth(cwd?: string): Promise<HealthCheckResult> {
159
+ const start = Date.now();
160
+
161
+ try {
162
+ if (!git.remoteExists('origin', { cwd })) {
163
+ return {
164
+ name: 'git-remote',
165
+ ok: true, // Not an error, just not configured
166
+ message: 'No remote "origin" configured',
167
+ latencyMs: Date.now() - start,
168
+ };
169
+ }
170
+
171
+ // Try to fetch with timeout
172
+ const result = spawnSync('git', ['ls-remote', '--exit-code', 'origin', 'HEAD'], {
173
+ cwd: cwd || process.cwd(),
174
+ encoding: 'utf8',
175
+ stdio: 'pipe',
176
+ timeout: 10000,
177
+ });
178
+
179
+ if (result.status === 0) {
180
+ return {
181
+ name: 'git-remote',
182
+ ok: true,
183
+ message: 'Git remote is reachable',
184
+ latencyMs: Date.now() - start,
185
+ };
186
+ } else {
187
+ return {
188
+ name: 'git-remote',
189
+ ok: false,
190
+ message: `Git remote unreachable: ${result.stderr}`,
191
+ latencyMs: Date.now() - start,
192
+ };
193
+ }
194
+ } catch (error: any) {
195
+ return {
196
+ name: 'git-remote',
197
+ ok: false,
198
+ message: `Git remote check failed: ${error.message}`,
199
+ latencyMs: Date.now() - start,
200
+ };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Check available disk space
206
+ */
207
+ export async function checkDiskSpace(minMb: number = 500): Promise<HealthCheckResult> {
208
+ const start = Date.now();
209
+
210
+ try {
211
+ const cwd = process.cwd();
212
+
213
+ // Use df command to check disk space
214
+ const result = spawnSync('df', ['-m', cwd], {
215
+ encoding: 'utf8',
216
+ stdio: 'pipe',
217
+ });
218
+
219
+ if (result.status === 0) {
220
+ const lines = result.stdout.trim().split('\n');
221
+ if (lines.length >= 2) {
222
+ const parts = lines[1]!.split(/\s+/);
223
+ const availableMb = parseInt(parts[3] || '0');
224
+
225
+ return {
226
+ name: 'disk-space',
227
+ ok: availableMb >= minMb,
228
+ message: availableMb >= minMb
229
+ ? `${availableMb} MB available`
230
+ : `Low disk space: ${availableMb} MB (minimum: ${minMb} MB)`,
231
+ details: { availableMb, minMb },
232
+ latencyMs: Date.now() - start,
233
+ };
234
+ }
235
+ }
236
+
237
+ // Fallback: assume OK if we can't check
238
+ return {
239
+ name: 'disk-space',
240
+ ok: true,
241
+ message: 'Could not determine disk space (assuming OK)',
242
+ latencyMs: Date.now() - start,
243
+ };
244
+ } catch (error: any) {
245
+ return {
246
+ name: 'disk-space',
247
+ ok: true, // Don't block on disk check failures
248
+ message: `Disk check skipped: ${error.message}`,
249
+ latencyMs: Date.now() - start,
250
+ };
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Check for stale lock files
256
+ */
257
+ export async function checkLockFiles(basePath?: string): Promise<HealthCheckResult> {
258
+ const start = Date.now();
259
+
260
+ try {
261
+ const lockDir = getLockDir(basePath || process.cwd());
262
+
263
+ if (!fs.existsSync(lockDir)) {
264
+ return {
265
+ name: 'lock-files',
266
+ ok: true,
267
+ message: 'No lock directory (OK)',
268
+ latencyMs: Date.now() - start,
269
+ };
270
+ }
271
+
272
+ const status = getLockStatus(lockDir);
273
+ const staleCount = status.filter(s => s.stale).length;
274
+ const activeCount = status.filter(s => !s.stale).length;
275
+
276
+ if (staleCount > 0) {
277
+ return {
278
+ name: 'lock-files',
279
+ ok: false,
280
+ message: `${staleCount} stale lock(s) found`,
281
+ details: { staleCount, activeCount, locks: status },
282
+ latencyMs: Date.now() - start,
283
+ };
284
+ }
285
+
286
+ return {
287
+ name: 'lock-files',
288
+ ok: true,
289
+ message: activeCount > 0 ? `${activeCount} active lock(s)` : 'No locks',
290
+ details: { activeCount },
291
+ latencyMs: Date.now() - start,
292
+ };
293
+ } catch (error: any) {
294
+ return {
295
+ name: 'lock-files',
296
+ ok: true, // Don't block on lock check failures
297
+ message: `Lock check skipped: ${error.message}`,
298
+ latencyMs: Date.now() - start,
299
+ };
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Check system resources (CPU, memory)
305
+ */
306
+ export async function checkSystemResources(): Promise<HealthCheckResult> {
307
+ const start = Date.now();
308
+
309
+ try {
310
+ const totalMem = os.totalmem() / (1024 * 1024 * 1024); // GB
311
+ const freeMem = os.freemem() / (1024 * 1024 * 1024); // GB
312
+ const usedPercent = ((totalMem - freeMem) / totalMem) * 100;
313
+ const cpuCount = os.cpus().length;
314
+ const loadAvg = os.loadavg()[0]!; // 1-minute average
315
+
316
+ const memoryOk = freeMem > 1; // At least 1GB free
317
+ const cpuOk = loadAvg < cpuCount * 2; // Load less than 2x CPU count
318
+
319
+ return {
320
+ name: 'system-resources',
321
+ ok: memoryOk && cpuOk,
322
+ message: memoryOk && cpuOk
323
+ ? 'System resources OK'
324
+ : `System under load (Memory: ${usedPercent.toFixed(0)}%, Load: ${loadAvg.toFixed(1)})`,
325
+ details: {
326
+ totalMemoryGb: totalMem.toFixed(1),
327
+ freeMemoryGb: freeMem.toFixed(1),
328
+ memoryUsedPercent: usedPercent.toFixed(1),
329
+ cpuCount,
330
+ loadAverage: loadAvg.toFixed(2),
331
+ },
332
+ latencyMs: Date.now() - start,
333
+ };
334
+ } catch (error: any) {
335
+ return {
336
+ name: 'system-resources',
337
+ ok: true, // Don't block on resource check failures
338
+ message: `Resource check skipped: ${error.message}`,
339
+ latencyMs: Date.now() - start,
340
+ };
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Check worktrees status
346
+ */
347
+ export async function checkWorktrees(cwd?: string): Promise<HealthCheckResult> {
348
+ const start = Date.now();
349
+
350
+ try {
351
+ const worktrees = git.listWorktrees(cwd);
352
+ const orphaned: string[] = [];
353
+
354
+ for (const wt of worktrees) {
355
+ if (wt.path && !fs.existsSync(wt.path)) {
356
+ orphaned.push(wt.path);
357
+ }
358
+ }
359
+
360
+ if (orphaned.length > 0) {
361
+ return {
362
+ name: 'worktrees',
363
+ ok: false,
364
+ message: `${orphaned.length} orphaned worktree(s) found`,
365
+ details: { total: worktrees.length, orphaned },
366
+ latencyMs: Date.now() - start,
367
+ };
368
+ }
369
+
370
+ return {
371
+ name: 'worktrees',
372
+ ok: true,
373
+ message: `${worktrees.length} worktree(s) OK`,
374
+ details: { count: worktrees.length },
375
+ latencyMs: Date.now() - start,
376
+ };
377
+ } catch (error: any) {
378
+ return {
379
+ name: 'worktrees',
380
+ ok: true, // Don't block on worktree check failures
381
+ message: `Worktree check skipped: ${error.message}`,
382
+ latencyMs: Date.now() - start,
383
+ };
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Run all health checks
389
+ */
390
+ export async function runHealthCheck(options: {
391
+ cwd?: string;
392
+ skipRemote?: boolean;
393
+ skipAuth?: boolean;
394
+ } = {}): Promise<SystemHealth> {
395
+ const checks: HealthCheckResult[] = [];
396
+
397
+ // Run checks in parallel where possible
398
+ const [agentHealth, gitHealth, diskSpace, lockFiles, sysResources, worktrees] = await Promise.all([
399
+ checkAgentHealth(),
400
+ checkGitHealth(options.cwd),
401
+ checkDiskSpace(),
402
+ checkLockFiles(options.cwd),
403
+ checkSystemResources(),
404
+ checkWorktrees(options.cwd),
405
+ ]);
406
+
407
+ checks.push(agentHealth, gitHealth, diskSpace, lockFiles, sysResources, worktrees);
408
+
409
+ // Remote checks (might be slow, so optional)
410
+ if (!options.skipRemote) {
411
+ checks.push(await checkGitRemoteHealth(options.cwd));
412
+ }
413
+
414
+ // Auth check (requires network, so optional)
415
+ if (!options.skipAuth) {
416
+ checks.push(await checkAuthHealth());
417
+ }
418
+
419
+ const passed = checks.filter(c => c.ok).length;
420
+ const failed = checks.filter(c => !c.ok).length;
421
+
422
+ return {
423
+ healthy: failed === 0,
424
+ timestamp: Date.now(),
425
+ checks,
426
+ summary: {
427
+ passed,
428
+ failed,
429
+ warnings: 0, // Could be extended to include warnings
430
+ },
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Run preflight checks before starting orchestration
436
+ */
437
+ export async function preflightCheck(options: {
438
+ cwd?: string;
439
+ requireRemote?: boolean;
440
+ requireAuth?: boolean;
441
+ } = {}): Promise<PreflightResult> {
442
+ const blockers: string[] = [];
443
+ const warnings: string[] = [];
444
+ const recommendations: string[] = [];
445
+
446
+ // Check cursor-agent
447
+ const agentHealth = await checkAgentHealth();
448
+ if (!agentHealth.ok) {
449
+ blockers.push(`cursor-agent: ${agentHealth.message}`);
450
+ }
451
+
452
+ // Check Git repository
453
+ const gitHealth = await checkGitHealth(options.cwd);
454
+ if (!gitHealth.ok) {
455
+ blockers.push(`Git: ${gitHealth.message}`);
456
+ }
457
+
458
+ // Check authentication
459
+ if (options.requireAuth !== false) {
460
+ const authHealth = await checkAuthHealth();
461
+ if (!authHealth.ok) {
462
+ blockers.push(`Authentication: ${authHealth.message}`);
463
+ }
464
+ }
465
+
466
+ // Check Git remote (warning only unless required)
467
+ const remoteHealth = await checkGitRemoteHealth(options.cwd);
468
+ if (!remoteHealth.ok) {
469
+ if (options.requireRemote) {
470
+ blockers.push(`Git remote: ${remoteHealth.message}`);
471
+ } else {
472
+ warnings.push(`Git remote: ${remoteHealth.message}`);
473
+ }
474
+ }
475
+
476
+ // Check disk space
477
+ const diskHealth = await checkDiskSpace();
478
+ if (!diskHealth.ok) {
479
+ warnings.push(`Disk space: ${diskHealth.message}`);
480
+ }
481
+
482
+ // Check for stale locks
483
+ const lockHealth = await checkLockFiles(options.cwd);
484
+ if (!lockHealth.ok) {
485
+ warnings.push(`Locks: ${lockHealth.message}`);
486
+ recommendations.push('Run `cursorflow clean locks` to remove stale locks');
487
+ }
488
+
489
+ // Check worktrees
490
+ const worktreeHealth = await checkWorktrees(options.cwd);
491
+ if (!worktreeHealth.ok) {
492
+ warnings.push(`Worktrees: ${worktreeHealth.message}`);
493
+ recommendations.push('Run `cursorflow clean worktrees` to clean up orphaned worktrees');
494
+ }
495
+
496
+ // Check system resources
497
+ const resourceHealth = await checkSystemResources();
498
+ if (!resourceHealth.ok) {
499
+ warnings.push(`Resources: ${resourceHealth.message}`);
500
+ }
501
+
502
+ return {
503
+ canProceed: blockers.length === 0,
504
+ blockers,
505
+ warnings,
506
+ recommendations,
507
+ };
508
+ }
509
+
510
+ /**
511
+ * Print health check results to console
512
+ */
513
+ export function printHealthReport(health: SystemHealth): void {
514
+ logger.section('🏥 System Health Check');
515
+
516
+ for (const check of health.checks) {
517
+ const icon = check.ok ? '✅' : '❌';
518
+ const latency = check.latencyMs ? ` (${check.latencyMs}ms)` : '';
519
+ console.log(`${icon} ${check.name}: ${check.message}${latency}`);
520
+ }
521
+
522
+ console.log('');
523
+ console.log(`Summary: ${health.summary.passed} passed, ${health.summary.failed} failed`);
524
+ console.log(`Overall: ${health.healthy ? '✅ Healthy' : '❌ Unhealthy'}`);
525
+ }
526
+
527
+ /**
528
+ * Print preflight check results to console
529
+ */
530
+ export function printPreflightReport(result: PreflightResult): void {
531
+ logger.section('🚀 Preflight Check');
532
+
533
+ if (result.blockers.length > 0) {
534
+ console.log('❌ Blockers:');
535
+ for (const blocker of result.blockers) {
536
+ console.log(` • ${blocker}`);
537
+ }
538
+ console.log('');
539
+ }
540
+
541
+ if (result.warnings.length > 0) {
542
+ console.log('⚠️ Warnings:');
543
+ for (const warning of result.warnings) {
544
+ console.log(` • ${warning}`);
545
+ }
546
+ console.log('');
547
+ }
548
+
549
+ if (result.recommendations.length > 0) {
550
+ console.log('💡 Recommendations:');
551
+ for (const rec of result.recommendations) {
552
+ console.log(` • ${rec}`);
553
+ }
554
+ console.log('');
555
+ }
556
+
557
+ if (result.canProceed) {
558
+ logger.success('Preflight check passed!');
559
+ } else {
560
+ logger.error('Preflight check failed. Please fix the blockers above.');
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Auto-repair common issues found during health check
566
+ */
567
+ export async function autoRepair(options: { cwd?: string } = {}): Promise<{ repaired: string[]; failed: string[] }> {
568
+ const repaired: string[] = [];
569
+ const failed: string[] = [];
570
+
571
+ // Clean stale locks
572
+ try {
573
+ const lockDir = getLockDir(options.cwd || process.cwd());
574
+ if (fs.existsSync(lockDir)) {
575
+ const cleaned = cleanStaleLocks(lockDir);
576
+ if (cleaned > 0) {
577
+ repaired.push(`Cleaned ${cleaned} stale lock(s)`);
578
+ }
579
+ }
580
+ } catch (e: any) {
581
+ failed.push(`Lock cleanup failed: ${e.message}`);
582
+ }
583
+
584
+ // Prune orphaned worktrees
585
+ try {
586
+ const result = git.runGitResult(['worktree', 'prune'], { cwd: options.cwd });
587
+ if (result.success) {
588
+ repaired.push('Pruned orphaned worktrees');
589
+ }
590
+ } catch (e: any) {
591
+ failed.push(`Worktree prune failed: ${e.message}`);
592
+ }
593
+
594
+ return { repaired, failed };
595
+ }
596
+