@litmers/cursorflow-orchestrator 0.1.20 → 0.1.28

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/CHANGELOG.md +20 -0
  2. package/commands/cursorflow-clean.md +19 -0
  3. package/commands/cursorflow-runs.md +59 -0
  4. package/commands/cursorflow-stop.md +55 -0
  5. package/dist/cli/clean.js +171 -0
  6. package/dist/cli/clean.js.map +1 -1
  7. package/dist/cli/index.js +7 -0
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/init.js +1 -1
  10. package/dist/cli/init.js.map +1 -1
  11. package/dist/cli/logs.js +83 -42
  12. package/dist/cli/logs.js.map +1 -1
  13. package/dist/cli/monitor.d.ts +7 -0
  14. package/dist/cli/monitor.js +1007 -189
  15. package/dist/cli/monitor.js.map +1 -1
  16. package/dist/cli/prepare.js +87 -3
  17. package/dist/cli/prepare.js.map +1 -1
  18. package/dist/cli/resume.js +188 -236
  19. package/dist/cli/resume.js.map +1 -1
  20. package/dist/cli/run.js +125 -3
  21. package/dist/cli/run.js.map +1 -1
  22. package/dist/cli/runs.d.ts +5 -0
  23. package/dist/cli/runs.js +214 -0
  24. package/dist/cli/runs.js.map +1 -0
  25. package/dist/cli/setup-commands.js +0 -0
  26. package/dist/cli/signal.js +1 -1
  27. package/dist/cli/signal.js.map +1 -1
  28. package/dist/cli/stop.d.ts +5 -0
  29. package/dist/cli/stop.js +215 -0
  30. package/dist/cli/stop.js.map +1 -0
  31. package/dist/cli/tasks.d.ts +10 -0
  32. package/dist/cli/tasks.js +165 -0
  33. package/dist/cli/tasks.js.map +1 -0
  34. package/dist/core/auto-recovery.d.ts +212 -0
  35. package/dist/core/auto-recovery.js +737 -0
  36. package/dist/core/auto-recovery.js.map +1 -0
  37. package/dist/core/failure-policy.d.ts +156 -0
  38. package/dist/core/failure-policy.js +488 -0
  39. package/dist/core/failure-policy.js.map +1 -0
  40. package/dist/core/orchestrator.d.ts +15 -2
  41. package/dist/core/orchestrator.js +397 -15
  42. package/dist/core/orchestrator.js.map +1 -1
  43. package/dist/core/reviewer.d.ts +2 -0
  44. package/dist/core/reviewer.js +2 -0
  45. package/dist/core/reviewer.js.map +1 -1
  46. package/dist/core/runner.d.ts +33 -10
  47. package/dist/core/runner.js +321 -146
  48. package/dist/core/runner.js.map +1 -1
  49. package/dist/services/logging/buffer.d.ts +67 -0
  50. package/dist/services/logging/buffer.js +309 -0
  51. package/dist/services/logging/buffer.js.map +1 -0
  52. package/dist/services/logging/console.d.ts +89 -0
  53. package/dist/services/logging/console.js +169 -0
  54. package/dist/services/logging/console.js.map +1 -0
  55. package/dist/services/logging/file-writer.d.ts +71 -0
  56. package/dist/services/logging/file-writer.js +516 -0
  57. package/dist/services/logging/file-writer.js.map +1 -0
  58. package/dist/services/logging/formatter.d.ts +39 -0
  59. package/dist/services/logging/formatter.js +227 -0
  60. package/dist/services/logging/formatter.js.map +1 -0
  61. package/dist/services/logging/index.d.ts +11 -0
  62. package/dist/services/logging/index.js +30 -0
  63. package/dist/services/logging/index.js.map +1 -0
  64. package/dist/services/logging/parser.d.ts +31 -0
  65. package/dist/services/logging/parser.js +222 -0
  66. package/dist/services/logging/parser.js.map +1 -0
  67. package/dist/services/process/index.d.ts +59 -0
  68. package/dist/services/process/index.js +257 -0
  69. package/dist/services/process/index.js.map +1 -0
  70. package/dist/types/agent.d.ts +20 -0
  71. package/dist/types/agent.js +6 -0
  72. package/dist/types/agent.js.map +1 -0
  73. package/dist/types/config.d.ts +65 -0
  74. package/dist/types/config.js +6 -0
  75. package/dist/types/config.js.map +1 -0
  76. package/dist/types/events.d.ts +125 -0
  77. package/dist/types/events.js +6 -0
  78. package/dist/types/events.js.map +1 -0
  79. package/dist/types/index.d.ts +12 -0
  80. package/dist/types/index.js +37 -0
  81. package/dist/types/index.js.map +1 -0
  82. package/dist/types/lane.d.ts +43 -0
  83. package/dist/types/lane.js +6 -0
  84. package/dist/types/lane.js.map +1 -0
  85. package/dist/types/logging.d.ts +71 -0
  86. package/dist/types/logging.js +16 -0
  87. package/dist/types/logging.js.map +1 -0
  88. package/dist/types/review.d.ts +17 -0
  89. package/dist/types/review.js +6 -0
  90. package/dist/types/review.js.map +1 -0
  91. package/dist/types/run.d.ts +32 -0
  92. package/dist/types/run.js +6 -0
  93. package/dist/types/run.js.map +1 -0
  94. package/dist/types/task.d.ts +71 -0
  95. package/dist/types/task.js +6 -0
  96. package/dist/types/task.js.map +1 -0
  97. package/dist/ui/components.d.ts +134 -0
  98. package/dist/ui/components.js +389 -0
  99. package/dist/ui/components.js.map +1 -0
  100. package/dist/ui/log-viewer.d.ts +49 -0
  101. package/dist/ui/log-viewer.js +449 -0
  102. package/dist/ui/log-viewer.js.map +1 -0
  103. package/dist/utils/checkpoint.d.ts +87 -0
  104. package/dist/utils/checkpoint.js +317 -0
  105. package/dist/utils/checkpoint.js.map +1 -0
  106. package/dist/utils/config.d.ts +4 -0
  107. package/dist/utils/config.js +11 -2
  108. package/dist/utils/config.js.map +1 -1
  109. package/dist/utils/cursor-agent.js.map +1 -1
  110. package/dist/utils/dependency.d.ts +74 -0
  111. package/dist/utils/dependency.js +420 -0
  112. package/dist/utils/dependency.js.map +1 -0
  113. package/dist/utils/doctor.js +10 -5
  114. package/dist/utils/doctor.js.map +1 -1
  115. package/dist/utils/enhanced-logger.d.ts +10 -33
  116. package/dist/utils/enhanced-logger.js +94 -9
  117. package/dist/utils/enhanced-logger.js.map +1 -1
  118. package/dist/utils/git.d.ts +121 -0
  119. package/dist/utils/git.js +322 -2
  120. package/dist/utils/git.js.map +1 -1
  121. package/dist/utils/health.d.ts +91 -0
  122. package/dist/utils/health.js +556 -0
  123. package/dist/utils/health.js.map +1 -0
  124. package/dist/utils/lock.d.ts +95 -0
  125. package/dist/utils/lock.js +332 -0
  126. package/dist/utils/lock.js.map +1 -0
  127. package/dist/utils/log-buffer.d.ts +17 -0
  128. package/dist/utils/log-buffer.js +14 -0
  129. package/dist/utils/log-buffer.js.map +1 -0
  130. package/dist/utils/log-constants.d.ts +23 -0
  131. package/dist/utils/log-constants.js +28 -0
  132. package/dist/utils/log-constants.js.map +1 -0
  133. package/dist/utils/log-formatter.d.ts +9 -0
  134. package/dist/utils/log-formatter.js +113 -70
  135. package/dist/utils/log-formatter.js.map +1 -1
  136. package/dist/utils/log-service.d.ts +19 -0
  137. package/dist/utils/log-service.js +47 -0
  138. package/dist/utils/log-service.js.map +1 -0
  139. package/dist/utils/logger.d.ts +46 -27
  140. package/dist/utils/logger.js +82 -60
  141. package/dist/utils/logger.js.map +1 -1
  142. package/dist/utils/process-manager.d.ts +21 -0
  143. package/dist/utils/process-manager.js +138 -0
  144. package/dist/utils/process-manager.js.map +1 -0
  145. package/dist/utils/retry.d.ts +121 -0
  146. package/dist/utils/retry.js +374 -0
  147. package/dist/utils/retry.js.map +1 -0
  148. package/dist/utils/run-service.d.ts +88 -0
  149. package/dist/utils/run-service.js +412 -0
  150. package/dist/utils/run-service.js.map +1 -0
  151. package/dist/utils/state.d.ts +58 -2
  152. package/dist/utils/state.js +306 -3
  153. package/dist/utils/state.js.map +1 -1
  154. package/dist/utils/task-service.d.ts +82 -0
  155. package/dist/utils/task-service.js +348 -0
  156. package/dist/utils/task-service.js.map +1 -0
  157. package/dist/utils/types.d.ts +2 -272
  158. package/dist/utils/types.js +16 -0
  159. package/dist/utils/types.js.map +1 -1
  160. package/package.json +38 -23
  161. package/scripts/ai-security-check.js +0 -1
  162. package/scripts/local-security-gate.sh +0 -0
  163. package/scripts/monitor-lanes.sh +94 -0
  164. package/scripts/patches/test-cursor-agent.js +0 -1
  165. package/scripts/release.sh +0 -0
  166. package/scripts/setup-security.sh +0 -0
  167. package/scripts/stream-logs.sh +72 -0
  168. package/scripts/verify-and-fix.sh +0 -0
  169. package/src/cli/clean.ts +180 -0
  170. package/src/cli/index.ts +7 -0
  171. package/src/cli/init.ts +1 -1
  172. package/src/cli/logs.ts +79 -42
  173. package/src/cli/monitor.ts +1815 -899
  174. package/src/cli/prepare.ts +97 -3
  175. package/src/cli/resume.ts +220 -277
  176. package/src/cli/run.ts +154 -3
  177. package/src/cli/runs.ts +212 -0
  178. package/src/cli/setup-commands.ts +0 -0
  179. package/src/cli/signal.ts +1 -1
  180. package/src/cli/stop.ts +209 -0
  181. package/src/cli/tasks.ts +154 -0
  182. package/src/core/auto-recovery.ts +909 -0
  183. package/src/core/failure-policy.ts +592 -0
  184. package/src/core/orchestrator.ts +1136 -675
  185. package/src/core/reviewer.ts +4 -0
  186. package/src/core/runner.ts +1443 -1217
  187. package/src/services/logging/buffer.ts +326 -0
  188. package/src/services/logging/console.ts +193 -0
  189. package/src/services/logging/file-writer.ts +526 -0
  190. package/src/services/logging/formatter.ts +268 -0
  191. package/src/services/logging/index.ts +16 -0
  192. package/src/services/logging/parser.ts +232 -0
  193. package/src/services/process/index.ts +261 -0
  194. package/src/types/agent.ts +24 -0
  195. package/src/types/config.ts +79 -0
  196. package/src/types/events.ts +156 -0
  197. package/src/types/index.ts +29 -0
  198. package/src/types/lane.ts +56 -0
  199. package/src/types/logging.ts +96 -0
  200. package/src/types/review.ts +20 -0
  201. package/src/types/run.ts +37 -0
  202. package/src/types/task.ts +79 -0
  203. package/src/ui/components.ts +430 -0
  204. package/src/ui/log-viewer.ts +485 -0
  205. package/src/utils/checkpoint.ts +374 -0
  206. package/src/utils/config.ts +11 -2
  207. package/src/utils/cursor-agent.ts +1 -1
  208. package/src/utils/dependency.ts +482 -0
  209. package/src/utils/doctor.ts +11 -5
  210. package/src/utils/enhanced-logger.ts +108 -49
  211. package/src/utils/git.ts +871 -499
  212. package/src/utils/health.ts +596 -0
  213. package/src/utils/lock.ts +346 -0
  214. package/src/utils/log-buffer.ts +28 -0
  215. package/src/utils/log-constants.ts +26 -0
  216. package/src/utils/log-formatter.ts +120 -37
  217. package/src/utils/log-service.ts +49 -0
  218. package/src/utils/logger.ts +100 -51
  219. package/src/utils/process-manager.ts +100 -0
  220. package/src/utils/retry.ts +413 -0
  221. package/src/utils/run-service.ts +433 -0
  222. package/src/utils/state.ts +369 -3
  223. package/src/utils/task-service.ts +370 -0
  224. package/src/utils/types.ts +2 -315
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Dependency management utilities for CursorFlow
3
+ *
4
+ * Features:
5
+ * - Cyclic dependency detection
6
+ * - Dependency wait with timeout
7
+ * - Topological sorting
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { safeJoin } from './path';
13
+ import { loadState } from './state';
14
+ import { LaneState } from './types';
15
+ import * as logger from './logger';
16
+
17
+ export interface DependencyInfo {
18
+ name: string;
19
+ dependsOn: string[];
20
+ }
21
+
22
+ export interface CycleDetectionResult {
23
+ hasCycle: boolean;
24
+ cycle: string[] | null;
25
+ sortedOrder: string[] | null;
26
+ }
27
+
28
+ export interface DependencyWaitOptions {
29
+ /** Maximum time to wait in milliseconds */
30
+ timeoutMs?: number;
31
+ /** Polling interval in milliseconds */
32
+ pollIntervalMs?: number;
33
+ /** Callback when timeout is reached */
34
+ onTimeout?: 'fail' | 'warn' | 'continue';
35
+ /** Callback for progress updates */
36
+ onProgress?: (pending: string[], completed: string[]) => void;
37
+ }
38
+
39
+ export const DEFAULT_DEPENDENCY_WAIT_OPTIONS: Required<Omit<DependencyWaitOptions, 'onProgress'>> = {
40
+ timeoutMs: 30 * 60 * 1000, // 30 minutes
41
+ pollIntervalMs: 5000, // 5 seconds
42
+ onTimeout: 'fail',
43
+ };
44
+
45
+ export interface DependencyWaitResult {
46
+ success: boolean;
47
+ timedOut: boolean;
48
+ failedDependencies: string[];
49
+ completedDependencies: string[];
50
+ elapsedMs: number;
51
+ }
52
+
53
+ /**
54
+ * Detect cyclic dependencies in a list of lanes
55
+ */
56
+ export function detectCyclicDependencies(lanes: DependencyInfo[]): CycleDetectionResult {
57
+ // Build adjacency graph
58
+ const graph = new Map<string, Set<string>>();
59
+ const allNodes = new Set<string>();
60
+
61
+ for (const lane of lanes) {
62
+ allNodes.add(lane.name);
63
+ graph.set(lane.name, new Set(lane.dependsOn));
64
+
65
+ // Add dependency nodes even if they're not in the list
66
+ for (const dep of lane.dependsOn) {
67
+ allNodes.add(dep);
68
+ if (!graph.has(dep)) {
69
+ graph.set(dep, new Set());
70
+ }
71
+ }
72
+ }
73
+
74
+ // Kahn's algorithm for topological sort with cycle detection
75
+ const inDegree = new Map<string, number>();
76
+
77
+ // Initialize in-degrees
78
+ for (const node of allNodes) {
79
+ inDegree.set(node, 0);
80
+ }
81
+
82
+ for (const [, deps] of graph) {
83
+ for (const dep of deps) {
84
+ inDegree.set(dep, (inDegree.get(dep) || 0) + 1);
85
+ }
86
+ }
87
+
88
+ // Queue of nodes with no incoming edges
89
+ const queue: string[] = [];
90
+ for (const [node, degree] of inDegree) {
91
+ if (degree === 0) {
92
+ queue.push(node);
93
+ }
94
+ }
95
+
96
+ const sorted: string[] = [];
97
+
98
+ while (queue.length > 0) {
99
+ const node = queue.shift()!;
100
+ sorted.push(node);
101
+
102
+ const deps = graph.get(node) || new Set();
103
+ for (const dep of deps) {
104
+ const newDegree = (inDegree.get(dep) || 0) - 1;
105
+ inDegree.set(dep, newDegree);
106
+
107
+ if (newDegree === 0) {
108
+ queue.push(dep);
109
+ }
110
+ }
111
+ }
112
+
113
+ // If not all nodes are in sorted order, there's a cycle
114
+ if (sorted.length !== allNodes.size) {
115
+ // Find the cycle using DFS
116
+ const cycle = findCycle(graph, allNodes);
117
+ return {
118
+ hasCycle: true,
119
+ cycle,
120
+ sortedOrder: null,
121
+ };
122
+ }
123
+
124
+ return {
125
+ hasCycle: false,
126
+ cycle: null,
127
+ sortedOrder: sorted,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Find a cycle in the graph using DFS
133
+ */
134
+ function findCycle(graph: Map<string, Set<string>>, allNodes: Set<string>): string[] | null {
135
+ const visited = new Set<string>();
136
+ const recursionStack = new Set<string>();
137
+ const parent = new Map<string, string>();
138
+
139
+ function dfs(node: string): string | null {
140
+ visited.add(node);
141
+ recursionStack.add(node);
142
+
143
+ const deps = graph.get(node) || new Set();
144
+ for (const dep of deps) {
145
+ if (!visited.has(dep)) {
146
+ parent.set(dep, node);
147
+ const cycleNode = dfs(dep);
148
+ if (cycleNode) return cycleNode;
149
+ } else if (recursionStack.has(dep)) {
150
+ // Found a cycle
151
+ parent.set(dep, node);
152
+ return dep;
153
+ }
154
+ }
155
+
156
+ recursionStack.delete(node);
157
+ return null;
158
+ }
159
+
160
+ for (const node of allNodes) {
161
+ if (!visited.has(node)) {
162
+ const cycleNode = dfs(node);
163
+ if (cycleNode) {
164
+ // Reconstruct the cycle
165
+ const cycle: string[] = [cycleNode];
166
+ let current = parent.get(cycleNode);
167
+ while (current && current !== cycleNode) {
168
+ cycle.push(current);
169
+ current = parent.get(current);
170
+ }
171
+ cycle.push(cycleNode);
172
+ return cycle.reverse();
173
+ }
174
+ }
175
+ }
176
+
177
+ return null;
178
+ }
179
+
180
+ /**
181
+ * Get topologically sorted order for lanes
182
+ */
183
+ export function getExecutionOrder(lanes: DependencyInfo[]): string[] | null {
184
+ const result = detectCyclicDependencies(lanes);
185
+
186
+ if (result.hasCycle) {
187
+ return null;
188
+ }
189
+
190
+ // Reverse the sorted order (we want dependencies first)
191
+ return result.sortedOrder!.reverse();
192
+ }
193
+
194
+ /**
195
+ * Wait for task-level dependencies with timeout and progress tracking
196
+ */
197
+ export async function waitForTaskDependencies(
198
+ deps: string[],
199
+ lanesRoot: string,
200
+ options: DependencyWaitOptions = {}
201
+ ): Promise<DependencyWaitResult> {
202
+ const opts = { ...DEFAULT_DEPENDENCY_WAIT_OPTIONS, ...options };
203
+ const startTime = Date.now();
204
+ const pendingDeps = new Set(deps);
205
+ const completedDeps: string[] = [];
206
+ const failedDeps: string[] = [];
207
+
208
+ if (deps.length === 0) {
209
+ return {
210
+ success: true,
211
+ timedOut: false,
212
+ failedDependencies: [],
213
+ completedDependencies: [],
214
+ elapsedMs: 0,
215
+ };
216
+ }
217
+
218
+ logger.info(`Waiting for task dependencies: ${deps.join(', ')}`);
219
+
220
+ while (pendingDeps.size > 0) {
221
+ // Check timeout
222
+ const elapsed = Date.now() - startTime;
223
+ if (elapsed > opts.timeoutMs) {
224
+ logger.warn(`Dependency wait timeout after ${Math.round(elapsed / 1000)}s`);
225
+
226
+ if (opts.onTimeout === 'fail') {
227
+ return {
228
+ success: false,
229
+ timedOut: true,
230
+ failedDependencies: Array.from(pendingDeps),
231
+ completedDependencies: completedDeps,
232
+ elapsedMs: elapsed,
233
+ };
234
+ } else if (opts.onTimeout === 'warn') {
235
+ logger.warn('Continuing despite timeout');
236
+ return {
237
+ success: true,
238
+ timedOut: true,
239
+ failedDependencies: [],
240
+ completedDependencies: completedDeps,
241
+ elapsedMs: elapsed,
242
+ };
243
+ }
244
+ // 'continue' - just return success
245
+ return {
246
+ success: true,
247
+ timedOut: true,
248
+ failedDependencies: [],
249
+ completedDependencies: completedDeps,
250
+ elapsedMs: elapsed,
251
+ };
252
+ }
253
+
254
+ for (const dep of pendingDeps) {
255
+ const [laneName, taskName] = dep.split(':');
256
+
257
+ if (!laneName || !taskName) {
258
+ logger.warn(`Invalid dependency format: ${dep}. Expected "lane:task"`);
259
+ pendingDeps.delete(dep);
260
+ failedDeps.push(dep);
261
+ continue;
262
+ }
263
+
264
+ const depStatePath = safeJoin(lanesRoot, laneName, 'state.json');
265
+
266
+ if (!fs.existsSync(depStatePath)) {
267
+ // Lane hasn't started yet - continue waiting
268
+ continue;
269
+ }
270
+
271
+ try {
272
+ const state = loadState<LaneState>(depStatePath);
273
+
274
+ if (!state) {
275
+ continue;
276
+ }
277
+
278
+ // Check if task is completed
279
+ if (state.completedTasks && state.completedTasks.includes(taskName)) {
280
+ logger.info(`✓ Dependency met: ${dep}`);
281
+ pendingDeps.delete(dep);
282
+ completedDeps.push(dep);
283
+ } else if (state.status === 'failed') {
284
+ // Dependency lane failed
285
+ logger.error(`✗ Dependency failed: ${dep} (Lane ${laneName} failed)`);
286
+ pendingDeps.delete(dep);
287
+ failedDeps.push(dep);
288
+ }
289
+ } catch (e: any) {
290
+ // Ignore parse errors, file might be being written
291
+ logger.warn(`Error reading dependency state: ${e.message}`);
292
+ }
293
+ }
294
+
295
+ // Report progress
296
+ if (options.onProgress) {
297
+ options.onProgress(Array.from(pendingDeps), completedDeps);
298
+ }
299
+
300
+ // Check for failed dependencies
301
+ if (failedDeps.length > 0) {
302
+ return {
303
+ success: false,
304
+ timedOut: false,
305
+ failedDependencies: failedDeps,
306
+ completedDependencies: completedDeps,
307
+ elapsedMs: Date.now() - startTime,
308
+ };
309
+ }
310
+
311
+ if (pendingDeps.size > 0) {
312
+ await new Promise(resolve => setTimeout(resolve, opts.pollIntervalMs));
313
+ }
314
+ }
315
+
316
+ return {
317
+ success: true,
318
+ timedOut: false,
319
+ failedDependencies: [],
320
+ completedDependencies: completedDeps,
321
+ elapsedMs: Date.now() - startTime,
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Check if a lane can start based on its dependencies
327
+ */
328
+ export function canLaneStart(
329
+ laneName: string,
330
+ lanes: DependencyInfo[],
331
+ completedLanes: Set<string>,
332
+ failedLanes: Set<string>
333
+ ): { canStart: boolean; reason?: string } {
334
+ const lane = lanes.find(l => l.name === laneName);
335
+
336
+ if (!lane) {
337
+ return { canStart: false, reason: `Lane ${laneName} not found` };
338
+ }
339
+
340
+ for (const dep of lane.dependsOn) {
341
+ if (failedLanes.has(dep)) {
342
+ return {
343
+ canStart: false,
344
+ reason: `Dependency ${dep} has failed`
345
+ };
346
+ }
347
+
348
+ if (!completedLanes.has(dep)) {
349
+ return {
350
+ canStart: false,
351
+ reason: `Waiting for dependency ${dep}`
352
+ };
353
+ }
354
+ }
355
+
356
+ return { canStart: true };
357
+ }
358
+
359
+ /**
360
+ * Get all transitive dependencies for a lane
361
+ */
362
+ export function getTransitiveDependencies(
363
+ laneName: string,
364
+ lanes: DependencyInfo[]
365
+ ): string[] {
366
+ const laneMap = new Map(lanes.map(l => [l.name, l]));
367
+ const visited = new Set<string>();
368
+ const result: string[] = [];
369
+
370
+ function visit(name: string): void {
371
+ if (visited.has(name)) return;
372
+ visited.add(name);
373
+
374
+ const lane = laneMap.get(name);
375
+ if (!lane) return;
376
+
377
+ for (const dep of lane.dependsOn) {
378
+ visit(dep);
379
+ if (!result.includes(dep)) {
380
+ result.push(dep);
381
+ }
382
+ }
383
+ }
384
+
385
+ visit(laneName);
386
+ return result;
387
+ }
388
+
389
+ /**
390
+ * Get lanes that depend on a given lane
391
+ */
392
+ export function getDependentLanes(
393
+ laneName: string,
394
+ lanes: DependencyInfo[]
395
+ ): string[] {
396
+ return lanes
397
+ .filter(l => l.dependsOn.includes(laneName))
398
+ .map(l => l.name);
399
+ }
400
+
401
+ /**
402
+ * Validate dependency configuration
403
+ */
404
+ export function validateDependencies(lanes: DependencyInfo[]): {
405
+ valid: boolean;
406
+ errors: string[];
407
+ warnings: string[];
408
+ } {
409
+ const errors: string[] = [];
410
+ const warnings: string[] = [];
411
+ const laneNames = new Set(lanes.map(l => l.name));
412
+
413
+ // Check for missing dependencies
414
+ for (const lane of lanes) {
415
+ for (const dep of lane.dependsOn) {
416
+ // Check if it's a task-level dependency
417
+ if (dep.includes(':')) {
418
+ const [depLane] = dep.split(':');
419
+ if (!laneNames.has(depLane!)) {
420
+ errors.push(`Lane "${lane.name}" depends on unknown lane "${depLane}"`);
421
+ }
422
+ } else if (!laneNames.has(dep)) {
423
+ errors.push(`Lane "${lane.name}" depends on unknown lane "${dep}"`);
424
+ }
425
+ }
426
+ }
427
+
428
+ // Check for cycles
429
+ const cycleResult = detectCyclicDependencies(lanes);
430
+ if (cycleResult.hasCycle && cycleResult.cycle) {
431
+ errors.push(`Cyclic dependency detected: ${cycleResult.cycle.join(' -> ')}`);
432
+ }
433
+
434
+ // Warning for deeply nested dependencies
435
+ for (const lane of lanes) {
436
+ const transitive = getTransitiveDependencies(lane.name, lanes);
437
+ if (transitive.length > 5) {
438
+ warnings.push(`Lane "${lane.name}" has ${transitive.length} transitive dependencies`);
439
+ }
440
+ }
441
+
442
+ return {
443
+ valid: errors.length === 0,
444
+ errors,
445
+ warnings,
446
+ };
447
+ }
448
+
449
+ /**
450
+ * Print dependency graph to console
451
+ */
452
+ export function printDependencyGraph(lanes: DependencyInfo[]): void {
453
+ const cycleResult = detectCyclicDependencies(lanes);
454
+
455
+ logger.section('📊 Dependency Graph');
456
+
457
+ if (cycleResult.hasCycle) {
458
+ logger.error(`⚠️ Cyclic dependency detected: ${cycleResult.cycle?.join(' -> ')}`);
459
+ console.log('');
460
+ }
461
+
462
+ for (const lane of lanes) {
463
+ const deps = lane.dependsOn.length > 0
464
+ ? ` [depends on: ${lane.dependsOn.join(', ')}]`
465
+ : '';
466
+ console.log(` ${logger.COLORS.cyan}${lane.name}${logger.COLORS.reset}${deps}`);
467
+
468
+ for (let i = 0; i < lane.dependsOn.length; i++) {
469
+ const isLast = i === lane.dependsOn.length - 1;
470
+ const prefix = isLast ? '└─' : '├─';
471
+ console.log(` ${prefix} ${lane.dependsOn[i]}`);
472
+ }
473
+ }
474
+
475
+ if (cycleResult.sortedOrder) {
476
+ console.log('');
477
+ console.log(` Execution order: ${cycleResult.sortedOrder.reverse().join(' → ')}`);
478
+ }
479
+
480
+ console.log('');
481
+ }
482
+
@@ -74,9 +74,11 @@ function addIssue(issues: DoctorIssue[], issue: DoctorIssue): void {
74
74
  }
75
75
 
76
76
  function resolveRepoRoot(cwd: string): string | null {
77
- const res = git.runGitResult(['rev-parse', '--show-toplevel'], { cwd });
78
- if (!res.success || !res.stdout) return null;
79
- return res.stdout;
77
+ try {
78
+ return git.getMainRepoRoot(cwd);
79
+ } catch {
80
+ return null;
81
+ }
80
82
  }
81
83
 
82
84
  function isInsideGitWorktree(cwd: string): boolean {
@@ -161,8 +163,12 @@ function readLaneJsonFiles(tasksDir: string): { path: string; json: any; fileNam
161
163
 
162
164
  function collectBaseBranchesFromLanes(lanes: { path: string; json: any }[], defaultBaseBranch: string): string[] {
163
165
  const set = new Set<string>();
166
+ // CursorFlow now defaults to the current branch to maintain dependency structure
167
+ const currentBranch = git.getCurrentBranch();
168
+ set.add(currentBranch);
169
+
164
170
  for (const lane of lanes) {
165
- const baseBranch = String(lane.json?.baseBranch || defaultBaseBranch || 'main').trim();
171
+ const baseBranch = String(lane.json?.baseBranch || defaultBaseBranch || currentBranch).trim();
166
172
  if (baseBranch) set.add(baseBranch);
167
173
  }
168
174
  return Array.from(set);
@@ -894,7 +900,7 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
894
900
  });
895
901
  } else {
896
902
  // Validate base branches
897
- const baseBranches = collectBaseBranchesFromLanes(lanes, 'main');
903
+ const baseBranches = collectBaseBranchesFromLanes(lanes, git.getCurrentBranch());
898
904
  for (const baseBranch of baseBranches) {
899
905
  if (!branchExists(gitCwd, baseBranch)) {
900
906
  addIssue(issues, {