@litmers/cursorflow-orchestrator 0.1.31 → 0.1.34

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 (129) hide show
  1. package/README.md +144 -52
  2. package/commands/cursorflow-add.md +159 -0
  3. package/commands/cursorflow-monitor.md +23 -2
  4. package/commands/cursorflow-new.md +87 -0
  5. package/dist/cli/add.d.ts +7 -0
  6. package/dist/cli/add.js +377 -0
  7. package/dist/cli/add.js.map +1 -0
  8. package/dist/cli/clean.js +1 -0
  9. package/dist/cli/clean.js.map +1 -1
  10. package/dist/cli/config.d.ts +7 -0
  11. package/dist/cli/config.js +181 -0
  12. package/dist/cli/config.js.map +1 -0
  13. package/dist/cli/index.js +34 -30
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/logs.js +7 -33
  16. package/dist/cli/logs.js.map +1 -1
  17. package/dist/cli/monitor.js +51 -62
  18. package/dist/cli/monitor.js.map +1 -1
  19. package/dist/cli/new.d.ts +7 -0
  20. package/dist/cli/new.js +232 -0
  21. package/dist/cli/new.js.map +1 -0
  22. package/dist/cli/prepare.js +95 -193
  23. package/dist/cli/prepare.js.map +1 -1
  24. package/dist/cli/resume.js +11 -47
  25. package/dist/cli/resume.js.map +1 -1
  26. package/dist/cli/run.js +27 -22
  27. package/dist/cli/run.js.map +1 -1
  28. package/dist/cli/tasks.js +1 -2
  29. package/dist/cli/tasks.js.map +1 -1
  30. package/dist/core/failure-policy.d.ts +9 -0
  31. package/dist/core/failure-policy.js +9 -0
  32. package/dist/core/failure-policy.js.map +1 -1
  33. package/dist/core/orchestrator.d.ts +20 -6
  34. package/dist/core/orchestrator.js +213 -333
  35. package/dist/core/orchestrator.js.map +1 -1
  36. package/dist/core/runner/agent.d.ts +27 -0
  37. package/dist/core/runner/agent.js +294 -0
  38. package/dist/core/runner/agent.js.map +1 -0
  39. package/dist/core/runner/index.d.ts +5 -0
  40. package/dist/core/runner/index.js +22 -0
  41. package/dist/core/runner/index.js.map +1 -0
  42. package/dist/core/runner/pipeline.d.ts +9 -0
  43. package/dist/core/runner/pipeline.js +539 -0
  44. package/dist/core/runner/pipeline.js.map +1 -0
  45. package/dist/core/runner/prompt.d.ts +25 -0
  46. package/dist/core/runner/prompt.js +175 -0
  47. package/dist/core/runner/prompt.js.map +1 -0
  48. package/dist/core/runner/task.d.ts +26 -0
  49. package/dist/core/runner/task.js +283 -0
  50. package/dist/core/runner/task.js.map +1 -0
  51. package/dist/core/runner/utils.d.ts +37 -0
  52. package/dist/core/runner/utils.js +161 -0
  53. package/dist/core/runner/utils.js.map +1 -0
  54. package/dist/core/runner.d.ts +2 -96
  55. package/dist/core/runner.js +11 -1136
  56. package/dist/core/runner.js.map +1 -1
  57. package/dist/core/stall-detection.d.ts +326 -0
  58. package/dist/core/stall-detection.js +781 -0
  59. package/dist/core/stall-detection.js.map +1 -0
  60. package/dist/types/config.d.ts +6 -6
  61. package/dist/types/flow.d.ts +84 -0
  62. package/dist/types/flow.js +10 -0
  63. package/dist/types/flow.js.map +1 -0
  64. package/dist/types/index.d.ts +1 -0
  65. package/dist/types/index.js +3 -3
  66. package/dist/types/index.js.map +1 -1
  67. package/dist/types/lane.d.ts +0 -2
  68. package/dist/types/logging.d.ts +5 -1
  69. package/dist/types/task.d.ts +7 -11
  70. package/dist/utils/config.js +7 -15
  71. package/dist/utils/config.js.map +1 -1
  72. package/dist/utils/dependency.d.ts +36 -1
  73. package/dist/utils/dependency.js +256 -1
  74. package/dist/utils/dependency.js.map +1 -1
  75. package/dist/utils/enhanced-logger.d.ts +45 -82
  76. package/dist/utils/enhanced-logger.js +238 -844
  77. package/dist/utils/enhanced-logger.js.map +1 -1
  78. package/dist/utils/git.d.ts +29 -0
  79. package/dist/utils/git.js +115 -5
  80. package/dist/utils/git.js.map +1 -1
  81. package/dist/utils/state.js +0 -2
  82. package/dist/utils/state.js.map +1 -1
  83. package/dist/utils/task-service.d.ts +2 -2
  84. package/dist/utils/task-service.js +40 -31
  85. package/dist/utils/task-service.js.map +1 -1
  86. package/package.json +4 -3
  87. package/src/cli/add.ts +397 -0
  88. package/src/cli/clean.ts +1 -0
  89. package/src/cli/config.ts +177 -0
  90. package/src/cli/index.ts +36 -32
  91. package/src/cli/logs.ts +7 -31
  92. package/src/cli/monitor.ts +55 -71
  93. package/src/cli/new.ts +235 -0
  94. package/src/cli/prepare.ts +98 -205
  95. package/src/cli/resume.ts +13 -56
  96. package/src/cli/run.ts +311 -306
  97. package/src/cli/tasks.ts +1 -2
  98. package/src/core/failure-policy.ts +9 -0
  99. package/src/core/orchestrator.ts +277 -378
  100. package/src/core/runner/agent.ts +314 -0
  101. package/src/core/runner/index.ts +6 -0
  102. package/src/core/runner/pipeline.ts +567 -0
  103. package/src/core/runner/prompt.ts +174 -0
  104. package/src/core/runner/task.ts +320 -0
  105. package/src/core/runner/utils.ts +142 -0
  106. package/src/core/runner.ts +8 -1347
  107. package/src/core/stall-detection.ts +936 -0
  108. package/src/types/config.ts +6 -6
  109. package/src/types/flow.ts +91 -0
  110. package/src/types/index.ts +15 -3
  111. package/src/types/lane.ts +0 -2
  112. package/src/types/logging.ts +5 -1
  113. package/src/types/task.ts +7 -11
  114. package/src/utils/config.ts +8 -16
  115. package/src/utils/dependency.ts +311 -2
  116. package/src/utils/enhanced-logger.ts +263 -927
  117. package/src/utils/git.ts +145 -5
  118. package/src/utils/state.ts +0 -2
  119. package/src/utils/task-service.ts +48 -40
  120. package/commands/cursorflow-review.md +0 -56
  121. package/commands/cursorflow-runs.md +0 -59
  122. package/dist/cli/runs.d.ts +0 -5
  123. package/dist/cli/runs.js +0 -214
  124. package/dist/cli/runs.js.map +0 -1
  125. package/dist/core/reviewer.d.ts +0 -66
  126. package/dist/core/reviewer.js +0 -265
  127. package/dist/core/reviewer.js.map +0 -1
  128. package/src/cli/runs.ts +0 -212
  129. package/src/core/reviewer.ts +0 -285
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
- console.log(`[DEBUG_GIT] Running: git ${args.join(' ')} (cwd: ${cwd || process.cwd()})`);
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
- console.log(`[DEBUG_GIT] Running: git ${args.join(' ')} (result mode, cwd: ${cwd || process.cwd()})`);
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
- const unambiguousBase = (baseBranch.startsWith('refs/') || baseBranch.includes('/'))
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
  */
@@ -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.detectCircularDependencies(taskInfo.lanes);
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 lane dependency graph
280
+ * Detect circular dependencies in task-level dependency graph
293
281
  */
294
- private detectCircularDependencies(lanes: LaneFileInfo[]): string[] {
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 = (laneName: string): boolean => {
300
- if (stack.has(laneName)) {
301
- cycle.push(laneName);
302
- return true; // Cycle found
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(laneName)) {
314
+ if (visited.has(taskId)) {
305
315
  return false;
306
316
  }
307
-
308
- visited.add(laneName);
309
- stack.add(laneName);
310
-
311
- const lane = lanes.find(l => l.laneName === laneName);
312
- if (lane) {
313
- for (const dep of lane.dependsOn) {
314
- if (dfs(dep)) {
315
- cycle.unshift(laneName);
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(laneName);
328
+
329
+ stack.delete(taskId);
322
330
  return false;
323
331
  };
324
-
325
- for (const lane of lanes) {
326
- if (dfs(lane.laneName)) {
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
@@ -1,5 +0,0 @@
1
- /**
2
- * CursorFlow runs command - List and view run details
3
- */
4
- declare function runs(args: string[]): Promise<void>;
5
- export = runs;
package/dist/cli/runs.js DELETED
@@ -1,214 +0,0 @@
1
- "use strict";
2
- /**
3
- * CursorFlow runs command - List and view run details
4
- */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
- const fs = __importStar(require("fs"));
39
- const logger = __importStar(require("../utils/logger"));
40
- const config_1 = require("../utils/config");
41
- const run_service_1 = require("../utils/run-service");
42
- const path_1 = require("../utils/path");
43
- function printHelp() {
44
- console.log(`
45
- Usage: cursorflow runs [run-id] [options]
46
-
47
- List all runs or view details of a specific run.
48
-
49
- Options:
50
- [run-id] View details of a specific run
51
- --running Filter to show only running runs
52
- --status <status> Filter by status: running, completed, failed, partial, pending
53
- --json Output in JSON format
54
- --help, -h Show help
55
-
56
- Examples:
57
- cursorflow runs # List all runs
58
- cursorflow runs --running # List only running runs
59
- cursorflow runs run-123 # View details of run-123
60
- `);
61
- }
62
- function parseArgs(args) {
63
- const statusIdx = args.indexOf('--status');
64
- const running = args.includes('--running');
65
- // Find run ID (first non-option argument)
66
- const runId = args.find((arg, i) => {
67
- if (arg.startsWith('--') || arg.startsWith('-'))
68
- return false;
69
- // Skip values for options
70
- const prevArg = args[i - 1];
71
- if (prevArg && ['--status'].includes(prevArg)) {
72
- return false;
73
- }
74
- return true;
75
- });
76
- return {
77
- runId,
78
- status: running ? 'running' : (statusIdx >= 0 ? args[statusIdx + 1] : undefined),
79
- json: args.includes('--json'),
80
- help: args.includes('--help') || args.includes('-h'),
81
- };
82
- }
83
- /**
84
- * Format duration in a human-readable way
85
- */
86
- function formatDuration(ms) {
87
- if (ms < 1000)
88
- return `${ms}ms`;
89
- const seconds = Math.floor(ms / 1000);
90
- if (seconds < 60)
91
- return `${seconds}s`;
92
- const minutes = Math.floor(seconds / 60);
93
- const remainingSeconds = seconds % 60;
94
- if (minutes < 60)
95
- return `${minutes}m ${remainingSeconds}s`;
96
- const hours = Math.floor(minutes / 60);
97
- const remainingMinutes = minutes % 60;
98
- return `${hours}h ${remainingMinutes}m`;
99
- }
100
- /**
101
- * Get status color and emoji
102
- */
103
- function getStatusDisplay(status) {
104
- switch (status) {
105
- case 'running':
106
- return `${logger.COLORS.blue}🔄 running${logger.COLORS.reset}`;
107
- case 'completed':
108
- return `${logger.COLORS.green}✅ done ${logger.COLORS.reset}`;
109
- case 'failed':
110
- return `${logger.COLORS.red}❌ failed ${logger.COLORS.reset}`;
111
- case 'partial':
112
- return `${logger.COLORS.yellow}⚠️ partial${logger.COLORS.reset}`;
113
- case 'pending':
114
- return `${logger.COLORS.gray}⏳ pending${logger.COLORS.reset}`;
115
- default:
116
- return status;
117
- }
118
- }
119
- /**
120
- * Display list of runs
121
- */
122
- function displayRunList(runs) {
123
- if (runs.length === 0) {
124
- console.log('No runs found.');
125
- return;
126
- }
127
- console.log(` Run ID Task Status Lanes Duration`);
128
- console.log(`${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
129
- for (const run of runs) {
130
- const isRunning = run.status === 'running';
131
- const prefix = isRunning ? `${logger.COLORS.blue} ▶${logger.COLORS.reset}` : ' ';
132
- const id = run.id.padEnd(20);
133
- const task = (run.taskName || 'unnamed').substring(0, 15).padEnd(15);
134
- const status = getStatusDisplay(run.status);
135
- const completedLanes = run.lanes.filter(l => l.status === 'completed').length;
136
- const totalLanes = run.lanes.length;
137
- const lanes = `${completedLanes}/${totalLanes}`.padEnd(10);
138
- const duration = formatDuration(run.duration);
139
- console.log(`${prefix} ${id} ${task} ${status} ${lanes} ${duration}`);
140
- }
141
- }
142
- /**
143
- * Display detailed information for a single run
144
- */
145
- function displayRunDetail(run) {
146
- logger.section(`Run Details: ${run.id}`);
147
- console.log(`${logger.COLORS.bold}Task:${logger.COLORS.reset} ${run.taskName}`);
148
- console.log(`${logger.COLORS.bold}Status:${logger.COLORS.reset} ${getStatusDisplay(run.status)}`);
149
- console.log(`${logger.COLORS.bold}Start Time:${logger.COLORS.reset} ${new Date(run.startTime).toLocaleString()}`);
150
- console.log(`${logger.COLORS.bold}Duration:${logger.COLORS.reset} ${formatDuration(run.duration)}`);
151
- console.log(`${logger.COLORS.bold}Path:${logger.COLORS.reset} ${run.path}`);
152
- console.log(`\n${logger.COLORS.bold}Lanes:${logger.COLORS.reset}`);
153
- for (const lane of run.lanes) {
154
- const statusColor = lane.status === 'completed' ? logger.COLORS.green :
155
- lane.status === 'failed' ? logger.COLORS.red :
156
- lane.status === 'running' ? logger.COLORS.blue : logger.COLORS.reset;
157
- console.log(` - ${lane.name.padEnd(20)} [${statusColor}${lane.status.toUpperCase()}${logger.COLORS.reset}] Task ${lane.currentTask}/${lane.totalTasks} (PID: ${lane.pid || 'N/A'})`);
158
- }
159
- if (run.branches.length > 0) {
160
- console.log(`\n${logger.COLORS.bold}Branches:${logger.COLORS.reset}`);
161
- for (const branch of run.branches) {
162
- console.log(` - ${branch}`);
163
- }
164
- }
165
- if (run.worktrees.length > 0) {
166
- console.log(`\n${logger.COLORS.bold}Worktrees:${logger.COLORS.reset}`);
167
- for (const wt of run.worktrees) {
168
- console.log(` - ${wt}`);
169
- }
170
- }
171
- console.log(`\nView logs: ${logger.COLORS.cyan}cursorflow logs ${run.id} --all${logger.COLORS.reset}`);
172
- }
173
- async function runs(args) {
174
- const options = parseArgs(args);
175
- if (options.help) {
176
- printHelp();
177
- return;
178
- }
179
- const config = (0, config_1.loadConfig)();
180
- const logsDir = (0, config_1.getLogsDir)(config);
181
- const runsDir = (0, path_1.safeJoin)(logsDir, 'runs');
182
- if (!fs.existsSync(runsDir)) {
183
- if (options.json) {
184
- console.log('[]');
185
- }
186
- else {
187
- console.log('No runs found. (Runs directory does not exist)');
188
- }
189
- return;
190
- }
191
- const runService = new run_service_1.RunService(runsDir);
192
- if (options.runId) {
193
- const run = runService.getRunInfo(options.runId);
194
- if (!run) {
195
- throw new Error(`Run not found: ${options.runId}`);
196
- }
197
- if (options.json) {
198
- console.log(JSON.stringify(run, null, 2));
199
- }
200
- else {
201
- displayRunDetail(run);
202
- }
203
- return;
204
- }
205
- const runsList = runService.listRuns({ status: options.status });
206
- if (options.json) {
207
- console.log(JSON.stringify(runsList, null, 2));
208
- }
209
- else {
210
- displayRunList(runsList);
211
- }
212
- }
213
- module.exports = runs;
214
- //# sourceMappingURL=runs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/cli/runs.ts"],"names":[],"mappings":"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"}