@litmers/cursorflow-orchestrator 0.1.31 → 0.1.36
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 +27 -0
- package/README.md +182 -59
- package/commands/cursorflow-add.md +159 -0
- package/commands/cursorflow-doctor.md +45 -23
- package/commands/cursorflow-monitor.md +23 -2
- package/commands/cursorflow-new.md +87 -0
- package/commands/cursorflow-run.md +60 -111
- package/dist/cli/add.d.ts +7 -0
- package/dist/cli/add.js +377 -0
- package/dist/cli/add.js.map +1 -0
- package/dist/cli/clean.js +1 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/config.d.ts +7 -0
- package/dist/cli/config.js +181 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/doctor.js +47 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +34 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +17 -34
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +62 -65
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/new.d.ts +7 -0
- package/dist/cli/new.js +232 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/prepare.js +95 -193
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +57 -68
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +60 -30
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stop.js +6 -0
- package/dist/cli/stop.js.map +1 -1
- package/dist/cli/tasks.d.ts +5 -3
- package/dist/cli/tasks.js +181 -29
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/failure-policy.d.ts +9 -0
- package/dist/core/failure-policy.js +9 -0
- package/dist/core/failure-policy.js.map +1 -1
- package/dist/core/orchestrator.d.ts +20 -6
- package/dist/core/orchestrator.js +215 -334
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +27 -0
- package/dist/core/runner/agent.js +294 -0
- package/dist/core/runner/agent.js.map +1 -0
- package/dist/core/runner/index.d.ts +5 -0
- package/dist/core/runner/index.js +22 -0
- package/dist/core/runner/index.js.map +1 -0
- package/dist/core/runner/pipeline.d.ts +9 -0
- package/dist/core/runner/pipeline.js +539 -0
- package/dist/core/runner/pipeline.js.map +1 -0
- package/dist/core/runner/prompt.d.ts +25 -0
- package/dist/core/runner/prompt.js +175 -0
- package/dist/core/runner/prompt.js.map +1 -0
- package/dist/core/runner/task.d.ts +26 -0
- package/dist/core/runner/task.js +283 -0
- package/dist/core/runner/task.js.map +1 -0
- package/dist/core/runner/utils.d.ts +37 -0
- package/dist/core/runner/utils.js +161 -0
- package/dist/core/runner/utils.js.map +1 -0
- package/dist/core/runner.d.ts +2 -96
- package/dist/core/runner.js +11 -1136
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +326 -0
- package/dist/core/stall-detection.js +781 -0
- package/dist/core/stall-detection.js.map +1 -0
- package/dist/services/logging/console.js +2 -1
- package/dist/services/logging/console.js.map +1 -1
- package/dist/types/config.d.ts +6 -6
- package/dist/types/flow.d.ts +84 -0
- package/dist/types/flow.js +10 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/lane.d.ts +0 -2
- package/dist/types/logging.d.ts +5 -1
- package/dist/types/task.d.ts +7 -11
- package/dist/utils/config.d.ts +5 -1
- package/dist/utils/config.js +15 -16
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/dependency.d.ts +36 -1
- package/dist/utils/dependency.js +256 -1
- package/dist/utils/dependency.js.map +1 -1
- package/dist/utils/doctor.js +40 -8
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +45 -82
- package/dist/utils/enhanced-logger.js +239 -844
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/flow.d.ts +9 -0
- package/dist/utils/flow.js +73 -0
- package/dist/utils/flow.js.map +1 -0
- package/dist/utils/git.d.ts +29 -0
- package/dist/utils/git.js +115 -5
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/state.js +0 -2
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +2 -2
- package/dist/utils/task-service.js +40 -31
- package/dist/utils/task-service.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/add.ts +397 -0
- package/src/cli/clean.ts +1 -0
- package/src/cli/config.ts +177 -0
- package/src/cli/doctor.ts +48 -4
- package/src/cli/index.ts +36 -32
- package/src/cli/logs.ts +20 -33
- package/src/cli/monitor.ts +70 -75
- package/src/cli/new.ts +235 -0
- package/src/cli/prepare.ts +98 -205
- package/src/cli/resume.ts +61 -76
- package/src/cli/run.ts +333 -306
- package/src/cli/stop.ts +8 -0
- package/src/cli/tasks.ts +200 -21
- package/src/core/failure-policy.ts +9 -0
- package/src/core/orchestrator.ts +279 -379
- package/src/core/runner/agent.ts +314 -0
- package/src/core/runner/index.ts +6 -0
- package/src/core/runner/pipeline.ts +567 -0
- package/src/core/runner/prompt.ts +174 -0
- package/src/core/runner/task.ts +320 -0
- package/src/core/runner/utils.ts +142 -0
- package/src/core/runner.ts +8 -1347
- package/src/core/stall-detection.ts +936 -0
- package/src/services/logging/console.ts +2 -1
- package/src/types/config.ts +6 -6
- package/src/types/flow.ts +91 -0
- package/src/types/index.ts +15 -3
- package/src/types/lane.ts +0 -2
- package/src/types/logging.ts +5 -1
- package/src/types/task.ts +7 -11
- package/src/utils/config.ts +16 -17
- package/src/utils/dependency.ts +311 -2
- package/src/utils/doctor.ts +36 -8
- package/src/utils/enhanced-logger.ts +264 -927
- package/src/utils/flow.ts +42 -0
- package/src/utils/git.ts +145 -5
- package/src/utils/state.ts +0 -2
- package/src/utils/task-service.ts +48 -40
- package/commands/cursorflow-review.md +0 -56
- package/commands/cursorflow-runs.md +0 -59
- package/dist/cli/runs.d.ts +0 -5
- package/dist/cli/runs.js +0 -214
- package/dist/cli/runs.js.map +0 -1
- package/dist/core/reviewer.d.ts +0 -66
- package/dist/core/reviewer.js +0 -265
- package/dist/core/reviewer.js.map +0 -1
- package/src/cli/runs.ts +0 -212
- package/src/core/reviewer.ts +0 -285
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { safeJoin } from './path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find flow directory by name in the flows directory.
|
|
7
|
+
* Matches by exact name or by suffix (ignoring ID prefix like '001_').
|
|
8
|
+
*
|
|
9
|
+
* @param flowsDir The base flows directory (e.g., _cursorflow/flows)
|
|
10
|
+
* @param flowName The name of the flow to find
|
|
11
|
+
* @returns The absolute path to the flow directory, or null if not found
|
|
12
|
+
*/
|
|
13
|
+
export function findFlowDir(flowsDir: string, flowName: string): string | null {
|
|
14
|
+
if (!fs.existsSync(flowsDir)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dirs = fs.readdirSync(flowsDir)
|
|
19
|
+
.filter(name => {
|
|
20
|
+
const dirPath = safeJoin(flowsDir, name);
|
|
21
|
+
try {
|
|
22
|
+
return fs.statSync(dirPath).isDirectory();
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.filter(name => {
|
|
28
|
+
// Match by exact name or by suffix (ignoring ID prefix)
|
|
29
|
+
const match = name.match(/^\d+_(.+)$/);
|
|
30
|
+
return match ? match[1] === flowName : name === flowName;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (dirs.length === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the most recent one (highest ID / alphabetical)
|
|
38
|
+
dirs.sort((a, b) => b.localeCompare(a));
|
|
39
|
+
return safeJoin(flowsDir, dirs[0]!);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
package/src/utils/git.ts
CHANGED
|
@@ -6,6 +6,16 @@ import { execSync, spawnSync } from 'child_process';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { safeJoin } from './path';
|
|
9
|
+
import * as logger from './logger';
|
|
10
|
+
|
|
11
|
+
let verboseGitEnabled = true;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Enable or disable verbose git logging
|
|
15
|
+
*/
|
|
16
|
+
export function setVerboseGit(enabled: boolean): void {
|
|
17
|
+
verboseGitEnabled = enabled;
|
|
18
|
+
}
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* Acquire a file-based lock for Git operations
|
|
@@ -132,8 +142,11 @@ function filterGitStderr(stderr: string): string {
|
|
|
132
142
|
export function runGit(args: string[], options: GitRunOptions = {}): string {
|
|
133
143
|
const { cwd, silent = false } = options;
|
|
134
144
|
|
|
135
|
-
if (process.env['DEBUG_GIT']) {
|
|
136
|
-
|
|
145
|
+
if (verboseGitEnabled || process.env['DEBUG_GIT']) {
|
|
146
|
+
logger.debug(`Running: git ${args.join(' ')}`, { context: 'git', emoji: '🛠️' });
|
|
147
|
+
if (cwd) {
|
|
148
|
+
logger.debug(` cwd: ${cwd}`, { context: 'git' });
|
|
149
|
+
}
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
try {
|
|
@@ -182,8 +195,11 @@ export function runGit(args: string[], options: GitRunOptions = {}): string {
|
|
|
182
195
|
export function runGitResult(args: string[], options: GitRunOptions = {}): GitResult {
|
|
183
196
|
const { cwd } = options;
|
|
184
197
|
|
|
185
|
-
if (process.env['DEBUG_GIT']) {
|
|
186
|
-
|
|
198
|
+
if (verboseGitEnabled || process.env['DEBUG_GIT']) {
|
|
199
|
+
logger.debug(`Running: git ${args.join(' ')} (result mode)`, { context: 'git', emoji: '🛠️' });
|
|
200
|
+
if (cwd) {
|
|
201
|
+
logger.debug(` cwd: ${cwd}`, { context: 'git' });
|
|
202
|
+
}
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
const result = spawnSync('git', args, {
|
|
@@ -267,7 +283,8 @@ export function createWorktree(worktreePath: string, branchName: string, options
|
|
|
267
283
|
}
|
|
268
284
|
|
|
269
285
|
// Ensure baseBranch is unambiguous (branch name rather than tag)
|
|
270
|
-
|
|
286
|
+
// Special case: HEAD should not be prefixed with refs/heads/
|
|
287
|
+
const unambiguousBase = (baseBranch === 'HEAD' || baseBranch.startsWith('refs/') || baseBranch.includes('/'))
|
|
271
288
|
? baseBranch
|
|
272
289
|
: `refs/heads/${baseBranch}`;
|
|
273
290
|
|
|
@@ -588,6 +605,129 @@ export interface SafeMergeResult {
|
|
|
588
605
|
aborted: boolean;
|
|
589
606
|
}
|
|
590
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Check if merging a branch would cause conflicts (dry-run)
|
|
610
|
+
* This does NOT actually perform the merge - it only checks for potential conflicts
|
|
611
|
+
*/
|
|
612
|
+
export function checkMergeConflict(branchName: string, options: { cwd?: string } = {}): {
|
|
613
|
+
willConflict: boolean;
|
|
614
|
+
conflictingFiles: string[];
|
|
615
|
+
error?: string;
|
|
616
|
+
} {
|
|
617
|
+
const { cwd } = options;
|
|
618
|
+
|
|
619
|
+
// Use merge-tree to check for conflicts without actually merging
|
|
620
|
+
// First, get the merge base
|
|
621
|
+
const mergeBaseResult = runGitResult(['merge-base', 'HEAD', branchName], { cwd });
|
|
622
|
+
if (!mergeBaseResult.success) {
|
|
623
|
+
return { willConflict: false, conflictingFiles: [], error: `Cannot find merge base: ${mergeBaseResult.stderr}` };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const mergeBase = mergeBaseResult.stdout.trim();
|
|
627
|
+
|
|
628
|
+
// Use merge-tree to simulate the merge
|
|
629
|
+
const mergeTreeResult = runGitResult(['merge-tree', mergeBase, 'HEAD', branchName], { cwd });
|
|
630
|
+
|
|
631
|
+
// Check for conflict markers in the output
|
|
632
|
+
const output = mergeTreeResult.stdout;
|
|
633
|
+
const hasConflict = output.includes('<<<<<<<') || output.includes('>>>>>>>') || output.includes('=======');
|
|
634
|
+
|
|
635
|
+
if (hasConflict) {
|
|
636
|
+
// Extract conflicting file names from merge-tree output
|
|
637
|
+
const conflictingFiles: string[] = [];
|
|
638
|
+
const lines = output.split('\n');
|
|
639
|
+
let currentFile = '';
|
|
640
|
+
|
|
641
|
+
for (const line of lines) {
|
|
642
|
+
// merge-tree output format includes file paths
|
|
643
|
+
if (line.startsWith('changed in both')) {
|
|
644
|
+
const match = line.match(/changed in both\s+(.+)/);
|
|
645
|
+
if (match) {
|
|
646
|
+
conflictingFiles.push(match[1]!.trim());
|
|
647
|
+
}
|
|
648
|
+
} else if (line.match(/^[a-f0-9]+ [a-f0-9]+ [a-f0-9]+\t(.+)$/)) {
|
|
649
|
+
const match = line.match(/\t(.+)$/);
|
|
650
|
+
if (match) {
|
|
651
|
+
currentFile = match[1]!;
|
|
652
|
+
}
|
|
653
|
+
} else if (line.includes('<<<<<<<') && currentFile) {
|
|
654
|
+
if (!conflictingFiles.includes(currentFile)) {
|
|
655
|
+
conflictingFiles.push(currentFile);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return { willConflict: true, conflictingFiles };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return { willConflict: false, conflictingFiles: [] };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Sync local branch with remote before starting work
|
|
668
|
+
* Fetches the latest from remote and fast-forwards if possible
|
|
669
|
+
*/
|
|
670
|
+
export function syncBranchWithRemote(branchName: string, options: {
|
|
671
|
+
cwd?: string;
|
|
672
|
+
createIfMissing?: boolean;
|
|
673
|
+
} = {}): {
|
|
674
|
+
success: boolean;
|
|
675
|
+
updated: boolean;
|
|
676
|
+
error?: string;
|
|
677
|
+
ahead?: number;
|
|
678
|
+
behind?: number;
|
|
679
|
+
} {
|
|
680
|
+
const { cwd, createIfMissing = false } = options;
|
|
681
|
+
|
|
682
|
+
// Fetch the branch from origin
|
|
683
|
+
const fetchResult = runGitResult(['fetch', 'origin', branchName], { cwd });
|
|
684
|
+
|
|
685
|
+
if (!fetchResult.success) {
|
|
686
|
+
// Branch might not exist on remote yet
|
|
687
|
+
if (createIfMissing || fetchResult.stderr.includes("couldn't find remote ref")) {
|
|
688
|
+
return { success: true, updated: false };
|
|
689
|
+
}
|
|
690
|
+
return { success: false, updated: false, error: fetchResult.stderr };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Check if we're ahead/behind
|
|
694
|
+
const statusResult = runGitResult(['rev-list', '--left-right', '--count', `${branchName}...origin/${branchName}`], { cwd });
|
|
695
|
+
|
|
696
|
+
if (!statusResult.success) {
|
|
697
|
+
// Remote tracking branch might not exist
|
|
698
|
+
return { success: true, updated: false };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const [aheadStr, behindStr] = statusResult.stdout.trim().split(/\s+/);
|
|
702
|
+
const ahead = parseInt(aheadStr || '0');
|
|
703
|
+
const behind = parseInt(behindStr || '0');
|
|
704
|
+
|
|
705
|
+
if (behind === 0) {
|
|
706
|
+
// Already up to date or ahead
|
|
707
|
+
return { success: true, updated: false, ahead, behind };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (ahead > 0 && behind > 0) {
|
|
711
|
+
// Diverged - cannot fast-forward
|
|
712
|
+
return {
|
|
713
|
+
success: false,
|
|
714
|
+
updated: false,
|
|
715
|
+
ahead,
|
|
716
|
+
behind,
|
|
717
|
+
error: `Branch has diverged: ${ahead} commits ahead, ${behind} commits behind. Manual resolution required.`
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Can fast-forward
|
|
722
|
+
const mergeResult = runGitResult(['merge', '--ff-only', `origin/${branchName}`], { cwd });
|
|
723
|
+
|
|
724
|
+
if (mergeResult.success) {
|
|
725
|
+
return { success: true, updated: true, ahead: 0, behind: 0 };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return { success: false, updated: false, error: mergeResult.stderr };
|
|
729
|
+
}
|
|
730
|
+
|
|
591
731
|
/**
|
|
592
732
|
* Safely merge a branch with conflict detection and auto-abort
|
|
593
733
|
*/
|
package/src/utils/state.ts
CHANGED
|
@@ -333,7 +333,6 @@ export function repairLaneState(statePath: string): LaneState | null {
|
|
|
333
333
|
error: null,
|
|
334
334
|
dependencyRequest: null,
|
|
335
335
|
tasksFile: state.tasksFile,
|
|
336
|
-
dependsOn: state.dependsOn || [],
|
|
337
336
|
completedTasks: state.completedTasks || [],
|
|
338
337
|
updatedAt: Date.now(),
|
|
339
338
|
};
|
|
@@ -465,7 +464,6 @@ export function createLaneState(
|
|
|
465
464
|
error: null,
|
|
466
465
|
dependencyRequest: null,
|
|
467
466
|
tasksFile,
|
|
468
|
-
dependsOn: config.dependsOn || [],
|
|
469
467
|
};
|
|
470
468
|
}
|
|
471
469
|
|
|
@@ -106,7 +106,6 @@ export class TaskService {
|
|
|
106
106
|
|
|
107
107
|
const laneName = this.extractLaneName(fileName);
|
|
108
108
|
const tasks = content.tasks || [];
|
|
109
|
-
const dependsOn = content.dependsOn || [];
|
|
110
109
|
const preset = this.detectPreset(tasks);
|
|
111
110
|
const taskFlow = this.generateTaskFlow(tasks);
|
|
112
111
|
|
|
@@ -116,7 +115,6 @@ export class TaskService {
|
|
|
116
115
|
preset,
|
|
117
116
|
taskCount: tasks.length,
|
|
118
117
|
taskFlow,
|
|
119
|
-
dependsOn,
|
|
120
118
|
});
|
|
121
119
|
} catch (error) {
|
|
122
120
|
logger.debug(`Failed to parse lane file ${fileName}: ${error}`);
|
|
@@ -224,16 +222,6 @@ export class TaskService {
|
|
|
224
222
|
errors.push(`Lane ${lane.fileName} has no tasks defined`);
|
|
225
223
|
}
|
|
226
224
|
|
|
227
|
-
// Check dependencies exist
|
|
228
|
-
for (const dep of lane.dependsOn) {
|
|
229
|
-
const depExists = taskInfo.lanes.some(l =>
|
|
230
|
-
l.laneName === dep || l.fileName === dep || l.fileName === `${dep}.json`
|
|
231
|
-
);
|
|
232
|
-
if (!depExists) {
|
|
233
|
-
warnings.push(`Lane ${lane.fileName} depends on unknown lane: ${dep}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
225
|
// Validate lane file structure
|
|
238
226
|
try {
|
|
239
227
|
const filePath = path.join(taskInfo.path, lane.fileName);
|
|
@@ -259,10 +247,10 @@ export class TaskService {
|
|
|
259
247
|
}
|
|
260
248
|
}
|
|
261
249
|
|
|
262
|
-
// Check for circular dependencies
|
|
263
|
-
const circularDeps = this.
|
|
250
|
+
// Check for circular task-level dependencies
|
|
251
|
+
const circularDeps = this.detectTaskCircularDependencies(taskInfo);
|
|
264
252
|
if (circularDeps.length > 0) {
|
|
265
|
-
errors.push(`Circular dependencies detected: ${circularDeps.join(' -> ')}`);
|
|
253
|
+
errors.push(`Circular task dependencies detected: ${circularDeps.join(' -> ')}`);
|
|
266
254
|
}
|
|
267
255
|
|
|
268
256
|
// Determine overall status
|
|
@@ -289,45 +277,65 @@ export class TaskService {
|
|
|
289
277
|
}
|
|
290
278
|
|
|
291
279
|
/**
|
|
292
|
-
* Detect circular dependencies in
|
|
280
|
+
* Detect circular dependencies in task-level dependency graph
|
|
293
281
|
*/
|
|
294
|
-
private
|
|
282
|
+
private detectTaskCircularDependencies(taskInfo: TaskDirInfo): string[] {
|
|
283
|
+
// Build task graph from all lane files
|
|
284
|
+
const taskGraph = new Map<string, string[]>(); // taskId -> dependencies
|
|
285
|
+
|
|
286
|
+
for (const lane of taskInfo.lanes) {
|
|
287
|
+
try {
|
|
288
|
+
const filePath = path.join(taskInfo.path, lane.fileName);
|
|
289
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
290
|
+
const laneName = lane.fileName.replace('.json', '');
|
|
291
|
+
|
|
292
|
+
if (Array.isArray(content.tasks)) {
|
|
293
|
+
for (const task of content.tasks) {
|
|
294
|
+
const taskId = `${laneName}:${task.name}`;
|
|
295
|
+
const deps = task.dependsOn || [];
|
|
296
|
+
taskGraph.set(taskId, deps);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch {
|
|
300
|
+
// Skip invalid files
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// DFS to detect cycles
|
|
295
305
|
const visited = new Set<string>();
|
|
296
306
|
const stack = new Set<string>();
|
|
297
307
|
const cycle: string[] = [];
|
|
298
|
-
|
|
299
|
-
const dfs = (
|
|
300
|
-
if (stack.has(
|
|
301
|
-
cycle.push(
|
|
302
|
-
return true;
|
|
308
|
+
|
|
309
|
+
const dfs = (taskId: string): boolean => {
|
|
310
|
+
if (stack.has(taskId)) {
|
|
311
|
+
cycle.push(taskId);
|
|
312
|
+
return true;
|
|
303
313
|
}
|
|
304
|
-
if (visited.has(
|
|
314
|
+
if (visited.has(taskId)) {
|
|
305
315
|
return false;
|
|
306
316
|
}
|
|
307
|
-
|
|
308
|
-
visited.add(
|
|
309
|
-
stack.add(
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
317
|
+
|
|
318
|
+
visited.add(taskId);
|
|
319
|
+
stack.add(taskId);
|
|
320
|
+
|
|
321
|
+
const deps = taskGraph.get(taskId) || [];
|
|
322
|
+
for (const dep of deps) {
|
|
323
|
+
if (dfs(dep)) {
|
|
324
|
+
cycle.unshift(taskId);
|
|
325
|
+
return true;
|
|
318
326
|
}
|
|
319
327
|
}
|
|
320
|
-
|
|
321
|
-
stack.delete(
|
|
328
|
+
|
|
329
|
+
stack.delete(taskId);
|
|
322
330
|
return false;
|
|
323
331
|
};
|
|
324
|
-
|
|
325
|
-
for (const
|
|
326
|
-
if (dfs(
|
|
332
|
+
|
|
333
|
+
for (const taskId of taskGraph.keys()) {
|
|
334
|
+
if (dfs(taskId)) {
|
|
327
335
|
break;
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
|
-
|
|
338
|
+
|
|
331
339
|
return cycle;
|
|
332
340
|
}
|
|
333
341
|
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# CursorFlow Automatic Review
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
CursorFlow features an integrated AI-powered code review system. It automatically validates task results against your defined acceptance criteria and can provide feedback to the agent for iterative improvements.
|
|
5
|
-
|
|
6
|
-
## Configuration
|
|
7
|
-
|
|
8
|
-
### Project-wide Settings
|
|
9
|
-
Enable and configure the review process in your `cursorflow.config.js`:
|
|
10
|
-
|
|
11
|
-
```javascript
|
|
12
|
-
module.exports = {
|
|
13
|
-
enableReview: true, // Enable automatic reviews
|
|
14
|
-
reviewModel: 'sonnet-4.5-thinking', // Model to use for reviewing
|
|
15
|
-
maxReviewIterations: 3, // Number of fix/re-review cycles
|
|
16
|
-
// ...
|
|
17
|
-
};
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### Task-level Criteria
|
|
21
|
-
Add `acceptanceCriteria` to your task JSON to guide the reviewer:
|
|
22
|
-
|
|
23
|
-
```json
|
|
24
|
-
{
|
|
25
|
-
"tasks": [
|
|
26
|
-
{
|
|
27
|
-
"name": "implement-logic",
|
|
28
|
-
"acceptanceCriteria": [
|
|
29
|
-
"No build errors",
|
|
30
|
-
"Tests in src/tests/ pass",
|
|
31
|
-
"Code follows project style guide"
|
|
32
|
-
],
|
|
33
|
-
"prompt": "..."
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## The Review Process
|
|
40
|
-
|
|
41
|
-
1. **Completion**: When an agent finishes a task, CursorFlow spawns a reviewer agent.
|
|
42
|
-
2. **Analysis**: The reviewer checks the worktree against the `acceptanceCriteria` using the `reviewModel`.
|
|
43
|
-
3. **Verdict**:
|
|
44
|
-
- **Approved**: The lane proceeds to the next task.
|
|
45
|
-
- **Needs Changes**: The reviewer's feedback is sent back to the original agent.
|
|
46
|
-
4. **Fix Loop**: The agent attempts to fix the issues based on the feedback. This repeats up to `maxReviewIterations`.
|
|
47
|
-
|
|
48
|
-
## Monitoring Reviews
|
|
49
|
-
You can track the review process in real-time using `cursorflow monitor`.
|
|
50
|
-
- The lane status will change to **`reviewing`** (`👀`).
|
|
51
|
-
- Reviewer feedback appears in the conversation history as a **`REVIEWER`** message.
|
|
52
|
-
|
|
53
|
-
## Best Practices
|
|
54
|
-
- **Specific Criteria**: Use clear, measurable criteria (e.g., "Build succeeds with `npm run build`").
|
|
55
|
-
- **Thinking Models**: Use reasoning-heavy models like `sonnet-4.5-thinking` for reviews to get better results.
|
|
56
|
-
- **Reasonable Iterations**: Set `maxReviewIterations` to 2 or 3 to prevent infinite loops.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# CursorFlow Runs
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
List and view detailed information about CursorFlow runs.
|
|
5
|
-
|
|
6
|
-
## Usage
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
cursorflow runs [run-id] [options]
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Options
|
|
13
|
-
|
|
14
|
-
| Option | Description |
|
|
15
|
-
|--------|-------------|
|
|
16
|
-
| `[run-id]` | View details of a specific run |
|
|
17
|
-
| `--running` | Filter to show only running runs |
|
|
18
|
-
| `--status <status>` | Filter by status: `running`, `completed`, `failed`, `partial`, `pending` |
|
|
19
|
-
| `--json` | Output in JSON format |
|
|
20
|
-
| `--help`, `-h` | Show help |
|
|
21
|
-
|
|
22
|
-
## Examples
|
|
23
|
-
|
|
24
|
-
### List all runs
|
|
25
|
-
```bash
|
|
26
|
-
cursorflow runs
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### List only running runs
|
|
30
|
-
```bash
|
|
31
|
-
cursorflow runs --running
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### View details of a specific run
|
|
35
|
-
```bash
|
|
36
|
-
cursorflow runs run-1734873132
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Export runs to JSON
|
|
40
|
-
```bash
|
|
41
|
-
cursorflow runs --json > runs.json
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Output Format
|
|
45
|
-
|
|
46
|
-
### List View
|
|
47
|
-
```
|
|
48
|
-
Run ID Task Status Lanes Duration
|
|
49
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
50
|
-
▶ run-20251222-153012 AuthSystem 🔄 running 2/3 15m 23s
|
|
51
|
-
run-20251222-143052 UserProfile ✅ done 3/3 45m 12s
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Detail View
|
|
55
|
-
The detail view includes:
|
|
56
|
-
- Task name and overall status
|
|
57
|
-
- Start time and duration
|
|
58
|
-
- Individual lane statuses and PIDs
|
|
59
|
-
- Associated Git branches and worktrees
|