@litmers/cursorflow-orchestrator 0.1.20 → 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 +9 -0
- 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 +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +4 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +8 -3
- 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 +1 -1
- 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 +15 -2
- package/dist/core/orchestrator.js +392 -15
- 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 +321 -146
- 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 +11 -2
- 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 +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- 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 +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- 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/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 +58 -2
- package/dist/utils/state.js +306 -3
- 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/types.d.ts +2 -272
- 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 +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +4 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +9 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- 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 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +388 -162
- 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 +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +374 -2
- 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 +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- 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 +369 -3
- package/src/utils/task-service.ts +370 -0
- 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
|
+
|
package/src/utils/doctor.ts
CHANGED
|
@@ -74,9 +74,11 @@ function addIssue(issues: DoctorIssue[], issue: DoctorIssue): void {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function resolveRepoRoot(cwd: string): string | null {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 ||
|
|
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,
|
|
903
|
+
const baseBranches = collectBaseBranchesFromLanes(lanes, git.getCurrentBranch());
|
|
898
904
|
for (const baseBranch of baseBranches) {
|
|
899
905
|
if (!branchExists(gitCwd, baseBranch)) {
|
|
900
906
|
addIssue(issues, {
|