@litmers/cursorflow-orchestrator 0.1.8 → 0.1.12

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 (66) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +113 -319
  3. package/commands/cursorflow-clean.md +24 -135
  4. package/commands/cursorflow-doctor.md +74 -18
  5. package/commands/cursorflow-init.md +33 -50
  6. package/commands/cursorflow-models.md +51 -0
  7. package/commands/cursorflow-monitor.md +56 -118
  8. package/commands/cursorflow-prepare.md +410 -108
  9. package/commands/cursorflow-resume.md +51 -148
  10. package/commands/cursorflow-review.md +38 -202
  11. package/commands/cursorflow-run.md +208 -86
  12. package/commands/cursorflow-signal.md +38 -12
  13. package/dist/cli/clean.d.ts +3 -1
  14. package/dist/cli/clean.js +145 -8
  15. package/dist/cli/clean.js.map +1 -1
  16. package/dist/cli/doctor.js +14 -1
  17. package/dist/cli/doctor.js.map +1 -1
  18. package/dist/cli/index.js +32 -21
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/init.js +5 -4
  21. package/dist/cli/init.js.map +1 -1
  22. package/dist/cli/models.d.ts +7 -0
  23. package/dist/cli/models.js +104 -0
  24. package/dist/cli/models.js.map +1 -0
  25. package/dist/cli/monitor.js +56 -1
  26. package/dist/cli/monitor.js.map +1 -1
  27. package/dist/cli/prepare.d.ts +7 -0
  28. package/dist/cli/prepare.js +748 -0
  29. package/dist/cli/prepare.js.map +1 -0
  30. package/dist/cli/resume.js +56 -0
  31. package/dist/cli/resume.js.map +1 -1
  32. package/dist/cli/run.js +30 -1
  33. package/dist/cli/run.js.map +1 -1
  34. package/dist/cli/signal.js +18 -0
  35. package/dist/cli/signal.js.map +1 -1
  36. package/dist/core/runner.d.ts +9 -1
  37. package/dist/core/runner.js +139 -23
  38. package/dist/core/runner.js.map +1 -1
  39. package/dist/utils/cursor-agent.d.ts +4 -0
  40. package/dist/utils/cursor-agent.js +58 -10
  41. package/dist/utils/cursor-agent.js.map +1 -1
  42. package/dist/utils/doctor.d.ts +10 -0
  43. package/dist/utils/doctor.js +581 -1
  44. package/dist/utils/doctor.js.map +1 -1
  45. package/dist/utils/types.d.ts +11 -0
  46. package/examples/README.md +114 -59
  47. package/examples/demo-project/README.md +61 -79
  48. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +17 -6
  49. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +17 -6
  50. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +66 -25
  51. package/package.json +1 -1
  52. package/scripts/patches/test-cursor-agent.js +203 -0
  53. package/src/cli/clean.ts +156 -9
  54. package/src/cli/doctor.ts +18 -2
  55. package/src/cli/index.ts +33 -21
  56. package/src/cli/init.ts +6 -4
  57. package/src/cli/models.ts +83 -0
  58. package/src/cli/monitor.ts +60 -1
  59. package/src/cli/prepare.ts +844 -0
  60. package/src/cli/resume.ts +66 -0
  61. package/src/cli/run.ts +36 -2
  62. package/src/cli/signal.ts +22 -0
  63. package/src/core/runner.ts +164 -23
  64. package/src/utils/cursor-agent.ts +62 -10
  65. package/src/utils/doctor.ts +633 -5
  66. package/src/utils/types.ts +11 -0
@@ -1,18 +1,29 @@
1
1
  {
2
2
  "baseBranch": "main",
3
- "branchPrefix": "cursorflow/demo-",
4
- "executor": "cursor-agent",
5
- "autoCreatePr": false,
6
- "pollInterval": 10,
3
+ "branchPrefix": "cursorflow/demo-tests-",
4
+ "timeout": 300000,
5
+ "enableIntervention": false,
7
6
  "dependencyPolicy": {
8
7
  "allowDependencyChange": false,
9
8
  "lockfileReadOnly": true
10
9
  },
10
+ "enableReview": true,
11
+ "reviewModel": "sonnet-4.5-thinking",
12
+ "maxReviewIterations": 2,
13
+ "laneNumber": 2,
14
+ "devPort": 3002,
11
15
  "tasks": [
12
16
  {
13
- "name": "add-tests",
17
+ "name": "implement",
14
18
  "model": "sonnet-4.5",
15
- "prompt": "# Task: Add simple tests for utility functions\n\n## Goal\nCreate a `src/utils.test.js` file with basic tests.\n\n## Requirements\n1. Create `src/utils.test.js`\n2. Add simple manual tests (no testing framework needed)\n3. Test all three utility functions:\n - Test `capitalize()` with a few examples\n - Test `sum()` with different arrays\n - Test `unique()` with arrays containing duplicates\n\n## Steps\n1. Create the test file\n2. Import the utils module\n3. Write simple console.log tests that show expected vs actual\n4. Add a success message at the end if all tests pass\n5. Commit with message: \"test: add basic tests for utils\"\n\n## Example Format\n```javascript\nconst utils = require('./utils');\n\nconsole.log('Testing capitalize...');\nconst result1 = utils.capitalize('hello');\nconsole.log('Expected: Hello, Got:', result1);\nconsole.log(result1 === 'Hello' ? '✓ Pass' : '✗ Fail');\n```\n"
19
+ "prompt": "# Task: Add simple tests for utility functions\n\n## Goal\nCreate a `src/utils.test.js` file with basic tests.\n\n## Requirements\n1. Create `src/utils.test.js`\n2. Add simple manual tests (no testing framework needed)\n3. Test all three utility functions:\n - Test `capitalize()` with a few examples\n - Test `sum()` with different arrays\n - Test `unique()` with arrays containing duplicates\n\n## Steps\n1. Create the test file\n2. Import the utils module\n3. Write simple console.log tests that show expected vs actual\n4. Add a success message at the end if all tests pass\n5. Commit with message: \"test: add basic tests for utils\"\n\n## Example Format\n```javascript\nconst utils = require('./utils');\n\nconsole.log('Testing capitalize...');\nconst result1 = utils.capitalize('hello');\nconsole.log('Expected: Hello, Got:', result1);\nconsole.log(result1 === 'Hello' ? '✓ Pass' : '✗ Fail');\n```\n\n## Important\n- Double-check all test cases before finishing",
20
+ "acceptanceCriteria": [
21
+ "src/utils.test.js file exists",
22
+ "Tests for capitalize function",
23
+ "Tests for sum function",
24
+ "Tests for unique function",
25
+ "Clear pass/fail output"
26
+ ]
16
27
  }
17
28
  ]
18
29
  }
@@ -2,53 +2,70 @@
2
2
 
3
3
  This directory contains test tasks for demonstrating CursorFlow with real LLM execution.
4
4
 
5
+ ## Alternative: Generate with Prepare Command
6
+
7
+ Instead of using these pre-made files, you can generate similar tasks using:
8
+
9
+ ```bash
10
+ # Simple approach
11
+ cursorflow prepare CreateUtils --preset simple --prompt "Create src/utils.js with capitalize, sum, unique functions"
12
+ cursorflow prepare AddTests --preset simple --prompt "Create src/utils.test.js with tests for utils"
13
+
14
+ # Or use a single complex task
15
+ cursorflow prepare UtilsWithTests --preset complex --prompt "Create utility module with tests"
16
+ ```
17
+
5
18
  ## Tasks Overview
6
19
 
7
20
  ### 01-create-utils.json
8
21
  Creates a utility module with basic functions:
9
- - capitalize(str)
10
- - sum(arr)
11
- - unique(arr)
22
+ - `capitalize(str)` - Capitalizes first letter
23
+ - `sum(arr)` - Sums array of numbers
24
+ - `unique(arr)` - Returns unique elements
12
25
 
13
26
  **Model**: sonnet-4.5
14
27
  **Estimated time**: 1-2 minutes
28
+ **Acceptance Criteria**: 5 criteria including function verification
15
29
 
16
30
  ### 02-add-tests.json
17
31
  Adds simple tests for the utility functions without requiring any testing framework.
18
32
 
19
33
  **Model**: sonnet-4.5
20
34
  **Estimated time**: 1-2 minutes
35
+ **Acceptance Criteria**: 5 criteria including test coverage
21
36
 
22
37
  ## Running the Demo
23
38
 
24
39
  ### Prerequisites
25
40
  - cursor-agent CLI installed (`npm install -g @cursor/agent`)
26
- - Valid Cursor API key configured
41
+ - Valid Cursor authentication (sign in via Cursor IDE)
27
42
 
28
- ### Run the test
43
+ ### 1. Validate first
44
+ ```bash
45
+ cursorflow doctor --tasks-dir _cursorflow/tasks/demo-test/
46
+ ```
29
47
 
48
+ ### 2. Run the test
30
49
  ```bash
31
- # From your project directory (after cursorflow init)
32
50
  cursorflow run _cursorflow/tasks/demo-test/
33
51
  ```
34
52
 
35
- ### Monitor execution
36
-
37
- In a separate terminal:
53
+ ### 3. Monitor execution
38
54
  ```bash
39
- # Watch mode (updates every 2 seconds)
55
+ # Interactive dashboard (recommended)
56
+ cursorflow monitor latest
57
+
58
+ # Or watch mode
40
59
  cursorflow monitor --watch --interval 2
41
60
  ```
42
61
 
43
- ### Check results
44
-
45
- After completion:
62
+ ### 4. Check results
46
63
  ```bash
47
- # View the logs
48
- ls -la _cursorflow/logs/runs/
49
-
50
- # Check the latest run
64
+ # View lane status
51
65
  cursorflow monitor
66
+
67
+ # Check git branches
68
+ git branch | grep cursorflow
52
69
  ```
53
70
 
54
71
  ## What to Expect
@@ -56,14 +73,16 @@ cursorflow monitor
56
73
  1. **Orchestrator** will create 2 lanes (one per task)
57
74
  2. **Each lane** will:
58
75
  - Create a Git worktree
59
- - Create a branch (`cursorflow/demo-XXXXX--01-create-utils`, etc.)
76
+ - Create a branch (`cursorflow/demo-utils-*`, `cursorflow/demo-tests-*`)
60
77
  - Execute the LLM agent with the prompt
78
+ - Run AI code review (if enabled)
61
79
  - Commit changes
62
80
  - Push the branch
63
81
  3. **Monitor** will show:
64
- - Lane status (running, completed, failed)
82
+ - Lane status (pending, running, completed, failed)
65
83
  - Progress (current task / total tasks)
66
- - Real-time updates in watch mode
84
+ - Dependencies (if any)
85
+ - Real-time updates
67
86
 
68
87
  ## Expected Output Structure
69
88
 
@@ -88,6 +107,16 @@ _cursorflow/
88
107
  └── README.md (this file)
89
108
  ```
90
109
 
110
+ ## Cleanup
111
+
112
+ After testing:
113
+ ```bash
114
+ # Clean branches and worktrees
115
+ cursorflow clean branches --dry-run # Preview
116
+ cursorflow clean branches # Execute
117
+ cursorflow clean worktrees # Clean worktrees
118
+ ```
119
+
91
120
  ## Troubleshooting
92
121
 
93
122
  ### If cursor-agent is not found
@@ -95,15 +124,27 @@ _cursorflow/
95
124
  npm install -g @cursor/agent
96
125
  ```
97
126
 
98
- ### If API key is missing
99
- Check your Cursor IDE settings and ensure you're authenticated.
127
+ ### If authentication fails
128
+ 1. Open Cursor IDE
129
+ 2. Sign in to your account
130
+ 3. Verify AI features work in the IDE
131
+ 4. Run `cursorflow doctor` to check
100
132
 
101
133
  ### If worktree creation fails
102
- Make sure you're in a Git repository with at least one commit.
134
+ Make sure you're in a Git repository with at least one commit:
135
+ ```bash
136
+ git log --oneline -1
137
+ ```
138
+
139
+ ### If branch conflicts occur
140
+ ```bash
141
+ cursorflow doctor --tasks-dir _cursorflow/tasks/demo-test/
142
+ ```
103
143
 
104
144
  ## Notes
105
145
 
106
- - These tasks are designed to be simple and quick
107
- - No external dependencies required
146
+ - These tasks run in parallel (no dependencies)
147
+ - Each task creates its own isolated branch
148
+ - AI code review is enabled by default
108
149
  - All operations are Git-safe (branches only, no main changes)
109
150
  - Logs are preserved for inspection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litmers/cursorflow-orchestrator",
3
- "version": "0.1.8",
3
+ "version": "0.1.12",
4
4
  "description": "Git worktree-based parallel AI agent orchestration system for Cursor",
5
5
  "main": "dist/cli/index.js",
6
6
  "bin": {
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Direct cursor-agent spawn test script
4
+ *
5
+ * This script tests cursor-agent directly to compare behavior with CursorFlow.
6
+ * It helps identify if the issue is in how CursorFlow spawns the agent.
7
+ *
8
+ * Usage:
9
+ * node scripts/patches/test-cursor-agent.js [--stdio-mode <mode>]
10
+ *
11
+ * Modes:
12
+ * - pipe: Full pipe mode (default, same as CursorFlow)
13
+ * - inherit-stdin: Inherit stdin, pipe stdout/stderr
14
+ * - inherit-all: Inherit all (like running directly in terminal)
15
+ */
16
+
17
+ const { spawn, spawnSync } = require('child_process');
18
+ const path = require('path');
19
+
20
+ // Parse args
21
+ const args = process.argv.slice(2);
22
+ const stdioModeIdx = args.indexOf('--stdio-mode');
23
+ const stdioMode = stdioModeIdx >= 0 ? args[stdioModeIdx + 1] : 'pipe';
24
+
25
+ console.log('='.repeat(60));
26
+ console.log('cursor-agent Direct Spawn Test');
27
+ console.log('='.repeat(60));
28
+ console.log(`stdio mode: ${stdioMode}`);
29
+ console.log('');
30
+
31
+ // Step 1: Check cursor-agent is available
32
+ console.log('1. Checking cursor-agent availability...');
33
+ const whichResult = spawnSync('which', ['cursor-agent']);
34
+ if (whichResult.status !== 0) {
35
+ console.error(' ❌ cursor-agent not found in PATH');
36
+ process.exit(1);
37
+ }
38
+ console.log(` ✓ Found at: ${whichResult.stdout.toString().trim()}`);
39
+
40
+ // Step 2: Create a chat session
41
+ console.log('\n2. Creating chat session...');
42
+ const createChatResult = spawnSync('cursor-agent', ['create-chat'], {
43
+ encoding: 'utf8',
44
+ stdio: 'pipe',
45
+ timeout: 30000,
46
+ });
47
+
48
+ if (createChatResult.status !== 0) {
49
+ console.error(' ❌ Failed to create chat:');
50
+ console.error(createChatResult.stderr || createChatResult.stdout);
51
+ process.exit(1);
52
+ }
53
+
54
+ const chatIdLines = createChatResult.stdout.trim().split('\n');
55
+ const chatId = chatIdLines[chatIdLines.length - 1];
56
+ console.log(` ✓ Chat ID: ${chatId}`);
57
+
58
+ // Step 3: Configure stdio based on mode
59
+ let stdioConfig;
60
+ switch (stdioMode) {
61
+ case 'inherit-stdin':
62
+ stdioConfig = ['inherit', 'pipe', 'pipe'];
63
+ break;
64
+ case 'inherit-all':
65
+ stdioConfig = 'inherit';
66
+ break;
67
+ case 'ignore-stdin':
68
+ stdioConfig = ['ignore', 'pipe', 'pipe'];
69
+ break;
70
+ case 'pipe':
71
+ default:
72
+ stdioConfig = ['pipe', 'pipe', 'pipe'];
73
+ break;
74
+ }
75
+
76
+ console.log(`\n3. Testing with stdio: ${JSON.stringify(stdioConfig)}`);
77
+
78
+ // Step 4: Send a simple prompt
79
+ const testPrompt = 'Just respond with: Hello World. Nothing else.';
80
+ const workspace = process.cwd();
81
+
82
+ const agentArgs = [
83
+ '--print',
84
+ '--output-format', 'json',
85
+ '--workspace', workspace,
86
+ '--model', 'gemini-3-flash',
87
+ '--resume', chatId,
88
+ testPrompt,
89
+ ];
90
+
91
+ console.log(` Workspace: ${workspace}`);
92
+ console.log(` Prompt: "${testPrompt}"`);
93
+ console.log('');
94
+ console.log('4. Spawning cursor-agent...');
95
+ console.log(` Command: cursor-agent ${agentArgs.join(' ').substring(0, 80)}...`);
96
+ console.log('');
97
+
98
+ const startTime = Date.now();
99
+
100
+ const child = spawn('cursor-agent', agentArgs, {
101
+ stdio: stdioConfig,
102
+ env: {
103
+ ...process.env,
104
+ // Disable Python buffering if cursor-agent uses Python
105
+ PYTHONUNBUFFERED: '1',
106
+ // Disable Node.js experimental warnings
107
+ NODE_OPTIONS: '',
108
+ },
109
+ });
110
+
111
+ let fullStdout = '';
112
+ let fullStderr = '';
113
+ let gotData = false;
114
+
115
+ // Track first byte time
116
+ let firstByteTime = null;
117
+
118
+ if (child.stdout) {
119
+ child.stdout.on('data', (data) => {
120
+ if (!firstByteTime) {
121
+ firstByteTime = Date.now();
122
+ console.log(` [+${((firstByteTime - startTime) / 1000).toFixed(2)}s] First stdout byte received`);
123
+ }
124
+ gotData = true;
125
+ const str = data.toString();
126
+ fullStdout += str;
127
+ process.stdout.write(` [stdout] ${str}`);
128
+ });
129
+ }
130
+
131
+ if (child.stderr) {
132
+ child.stderr.on('data', (data) => {
133
+ const str = data.toString();
134
+ fullStderr += str;
135
+ process.stderr.write(` [stderr] ${str}`);
136
+ });
137
+ }
138
+
139
+ // Set timeout
140
+ const timeout = setTimeout(() => {
141
+ console.log('\n ⏰ TIMEOUT after 60 seconds');
142
+ console.log(` Got any data: ${gotData}`);
143
+ console.log(` Stdout length: ${fullStdout.length}`);
144
+ console.log(` Stderr length: ${fullStderr.length}`);
145
+ child.kill('SIGTERM');
146
+ }, 60000);
147
+
148
+ child.on('close', (code) => {
149
+ clearTimeout(timeout);
150
+ const elapsed = (Date.now() - startTime) / 1000;
151
+
152
+ console.log('\n' + '-'.repeat(60));
153
+ console.log('RESULT');
154
+ console.log('-'.repeat(60));
155
+ console.log(`Exit code: ${code}`);
156
+ console.log(`Duration: ${elapsed.toFixed(2)}s`);
157
+ console.log(`First byte: ${firstByteTime ? ((firstByteTime - startTime) / 1000).toFixed(2) + 's' : 'never'}`);
158
+ console.log(`Stdout bytes: ${fullStdout.length}`);
159
+ console.log(`Stderr bytes: ${fullStderr.length}`);
160
+
161
+ if (fullStdout.length > 0) {
162
+ console.log('\nStdout content:');
163
+ console.log(fullStdout);
164
+ }
165
+
166
+ if (fullStderr.length > 0) {
167
+ console.log('\nStderr content:');
168
+ console.log(fullStderr);
169
+ }
170
+
171
+ // Try to parse JSON
172
+ const lines = fullStdout.split('\n').filter(Boolean);
173
+ for (let i = lines.length - 1; i >= 0; i--) {
174
+ const line = lines[i].trim();
175
+ if (line.startsWith('{') && line.endsWith('}')) {
176
+ try {
177
+ const json = JSON.parse(line);
178
+ console.log('\nParsed JSON response:');
179
+ console.log(JSON.stringify(json, null, 2));
180
+ break;
181
+ } catch {
182
+ continue;
183
+ }
184
+ }
185
+ }
186
+
187
+ console.log('\n' + '='.repeat(60));
188
+ if (code === 0 && fullStdout.includes('result')) {
189
+ console.log('✓ SUCCESS - cursor-agent responded correctly');
190
+ } else if (gotData) {
191
+ console.log('⚠ PARTIAL - Got data but may not have completed');
192
+ } else {
193
+ console.log('❌ FAILURE - No data received');
194
+ }
195
+ console.log('='.repeat(60));
196
+ });
197
+
198
+ child.on('error', (err) => {
199
+ clearTimeout(timeout);
200
+ console.error(`\n❌ Spawn error: ${err.message}`);
201
+ process.exit(1);
202
+ });
203
+
package/src/cli/clean.ts CHANGED
@@ -1,36 +1,183 @@
1
1
  /**
2
- * CursorFlow clean command (stub)
2
+ * CursorFlow clean command
3
+ *
4
+ * Clean up worktrees, branches, and logs created by CursorFlow
3
5
  */
4
6
 
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
5
9
  import * as logger from '../utils/logger';
10
+ import * as git from '../utils/git';
11
+ import { loadConfig, getLogsDir } from '../utils/config';
6
12
 
7
13
  interface CleanOptions {
8
14
  type?: string;
9
15
  pattern: string | null;
10
16
  dryRun: boolean;
11
17
  force: boolean;
18
+ all: boolean;
19
+ help: boolean;
20
+ }
21
+
22
+ function printHelp(): void {
23
+ console.log(`
24
+ Usage: cursorflow clean <type> [options]
25
+
26
+ Clean up resources created by CursorFlow.
27
+
28
+ Types:
29
+ branches Remove local feature branches
30
+ worktrees Remove temporary Git worktrees
31
+ logs Clear log directories
32
+ all Remove all of the above (default)
33
+
34
+ Options:
35
+ --dry-run Show what would be removed without deleting
36
+ --force Force removal (ignore uncommitted changes)
37
+ --help, -h Show help
38
+ `);
12
39
  }
13
40
 
14
41
  function parseArgs(args: string[]): CleanOptions {
15
42
  return {
16
- type: args[0], // branches | worktrees | logs | all
43
+ type: args.find(a => ['branches', 'worktrees', 'logs', 'all'].includes(a)),
17
44
  pattern: null,
18
45
  dryRun: args.includes('--dry-run'),
19
46
  force: args.includes('--force'),
47
+ all: args.includes('--all'),
48
+ help: args.includes('--help') || args.includes('-h'),
20
49
  };
21
50
  }
22
51
 
23
52
  async function clean(args: string[]): Promise<void> {
24
- logger.section('🧹 Cleaning CursorFlow Resources');
25
-
26
53
  const options = parseArgs(args);
27
54
 
28
- logger.info('This command will be fully implemented in the next phase');
29
- logger.info(`Clean type: ${options.type}`);
30
- logger.info(`Dry run: ${options.dryRun}`);
55
+ if (options.help) {
56
+ printHelp();
57
+ return;
58
+ }
59
+
60
+ const config = loadConfig();
61
+ const repoRoot = git.getRepoRoot();
62
+
63
+ logger.section('🧹 Cleaning CursorFlow Resources');
64
+
65
+ const type = options.type || 'all';
66
+
67
+ if (type === 'all') {
68
+ await cleanWorktrees(config, repoRoot, options);
69
+ await cleanBranches(config, repoRoot, options);
70
+ await cleanLogs(config, options);
71
+ } else if (type === 'worktrees') {
72
+ await cleanWorktrees(config, repoRoot, options);
73
+ } else if (type === 'branches') {
74
+ await cleanBranches(config, repoRoot, options);
75
+ } else if (type === 'logs') {
76
+ await cleanLogs(config, options);
77
+ }
78
+
79
+ logger.success('\n✨ Cleaning complete!');
80
+ }
81
+
82
+ async function cleanWorktrees(config: any, repoRoot: string, options: CleanOptions) {
83
+ logger.info('\nChecking worktrees...');
84
+ const worktrees = git.listWorktrees(repoRoot);
85
+
86
+ const worktreeRoot = path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees');
87
+ const toRemove = worktrees.filter(wt => {
88
+ // Skip main worktree
89
+ if (wt.path === repoRoot) return false;
90
+
91
+ const isInsideRoot = wt.path.startsWith(worktreeRoot);
92
+ const hasPrefix = path.basename(wt.path).startsWith(config.worktreePrefix || 'cursorflow-');
93
+
94
+ return isInsideRoot || hasPrefix;
95
+ });
96
+
97
+ if (toRemove.length === 0) {
98
+ logger.info(' No worktrees found to clean.');
99
+ return;
100
+ }
101
+
102
+ for (const wt of toRemove) {
103
+ if (options.dryRun) {
104
+ logger.info(` [DRY RUN] Would remove worktree: ${wt.path} (${wt.branch || 'no branch'})`);
105
+ } else {
106
+ try {
107
+ logger.info(` Removing worktree: ${wt.path}...`);
108
+ git.removeWorktree(wt.path, { cwd: repoRoot, force: options.force });
109
+
110
+ // Git worktree remove might leave the directory if it has untracked files
111
+ if (fs.existsSync(wt.path)) {
112
+ if (options.force) {
113
+ fs.rmSync(wt.path, { recursive: true, force: true });
114
+ logger.info(` (Forced removal of directory)`);
115
+ } else {
116
+ logger.warn(` Directory still exists: ${wt.path} (contains untracked files). Use --force to delete anyway.`);
117
+ }
118
+ }
119
+ } catch (e: any) {
120
+ logger.error(` Failed to remove worktree ${wt.path}: ${e.message}`);
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ async function cleanBranches(config: any, repoRoot: string, options: CleanOptions) {
127
+ logger.info('\nChecking branches...');
31
128
 
32
- logger.warn('\n⚠️ Implementation pending');
33
- logger.info('This will clean branches, worktrees, and logs');
129
+ // List all local branches
130
+ const result = git.runGitResult(['branch', '--list'], { cwd: repoRoot });
131
+ if (!result.success) return;
132
+
133
+ const branches = result.stdout
134
+ .split('\n')
135
+ .map(b => b.replace('*', '').trim())
136
+ .filter(b => b && b !== 'main' && b !== 'master');
137
+
138
+ const prefix = config.branchPrefix || 'feature/';
139
+ const toDelete = branches.filter(b => b.startsWith(prefix));
140
+
141
+ if (toDelete.length === 0) {
142
+ logger.info(' No branches found to clean.');
143
+ return;
144
+ }
145
+
146
+ for (const branch of toDelete) {
147
+ if (options.dryRun) {
148
+ logger.info(` [DRY RUN] Would delete branch: ${branch}`);
149
+ } else {
150
+ try {
151
+ logger.info(` Deleting branch: ${branch}...`);
152
+ git.deleteBranch(branch, { cwd: repoRoot, force: options.force || options.all });
153
+ } catch (e: any) {
154
+ logger.warn(` Could not delete branch ${branch}: ${e.message}. Use --force if it's not merged.`);
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ async function cleanLogs(config: any, options: CleanOptions) {
161
+ const logsDir = getLogsDir(config);
162
+ logger.info(`\nChecking logs in ${logsDir}...`);
163
+
164
+ if (!fs.existsSync(logsDir)) {
165
+ logger.info(' Logs directory does not exist.');
166
+ return;
167
+ }
168
+
169
+ if (options.dryRun) {
170
+ logger.info(` [DRY RUN] Would remove logs directory: ${logsDir}`);
171
+ } else {
172
+ try {
173
+ logger.info(` Removing logs...`);
174
+ fs.rmSync(logsDir, { recursive: true, force: true });
175
+ fs.mkdirSync(logsDir, { recursive: true });
176
+ logger.info(` Logs cleared.`);
177
+ } catch (e: any) {
178
+ logger.error(` Failed to clean logs: ${e.message}`);
179
+ }
180
+ }
34
181
  }
35
182
 
36
183
  export = clean;
package/src/cli/doctor.ts CHANGED
@@ -13,13 +13,15 @@
13
13
  */
14
14
 
15
15
  import * as logger from '../utils/logger';
16
- import { runDoctor } from '../utils/doctor';
16
+ import { runDoctor, saveDoctorStatus } from '../utils/doctor';
17
+ import { runInteractiveAgentTest } from '../utils/cursor-agent';
17
18
 
18
19
  interface DoctorCliOptions {
19
20
  json: boolean;
20
21
  tasksDir: string | null;
21
22
  executor: string | null;
22
23
  includeCursorAgentChecks: boolean;
24
+ testAgent: boolean;
23
25
  }
24
26
 
25
27
  function printHelp(): void {
@@ -33,12 +35,13 @@ Options:
33
35
  --tasks-dir <path> Also validate lane files (run preflight)
34
36
  --executor <type> cursor-agent | cloud
35
37
  --no-cursor Skip Cursor Agent install/auth checks
38
+ --test-agent Run interactive agent test (to approve MCP/permissions)
36
39
  --help, -h Show help
37
40
 
38
41
  Examples:
39
42
  cursorflow doctor
43
+ cursorflow doctor --test-agent
40
44
  cursorflow doctor --tasks-dir _cursorflow/tasks/demo-test/
41
- cursorflow doctor --json
42
45
  `);
43
46
  }
44
47
 
@@ -51,6 +54,7 @@ function parseArgs(args: string[]): DoctorCliOptions {
51
54
  tasksDir: tasksDirIdx >= 0 ? (args[tasksDirIdx + 1] || null) : null,
52
55
  executor: executorIdx >= 0 ? (args[executorIdx + 1] || null) : null,
53
56
  includeCursorAgentChecks: !args.includes('--no-cursor'),
57
+ testAgent: args.includes('--test-agent'),
54
58
  };
55
59
 
56
60
  if (args.includes('--help') || args.includes('-h')) {
@@ -69,6 +73,8 @@ function printHumanReport(report: ReturnType<typeof runDoctor>): void {
69
73
 
70
74
  if (report.issues.length === 0) {
71
75
  logger.success('All checks passed');
76
+ console.log('\n💡 Tip: If this is your first run, we recommend running:');
77
+ console.log(' cursorflow doctor --test-agent\n');
72
78
  return;
73
79
  }
74
80
 
@@ -106,6 +112,11 @@ function printHumanReport(report: ReturnType<typeof runDoctor>): void {
106
112
  async function doctor(args: string[]): Promise<void> {
107
113
  const options = parseArgs(args);
108
114
 
115
+ if (options.testAgent) {
116
+ const success = runInteractiveAgentTest();
117
+ process.exit(success ? 0 : 1);
118
+ }
119
+
109
120
  const report = runDoctor({
110
121
  cwd: process.cwd(),
111
122
  tasksDir: options.tasksDir || undefined,
@@ -119,6 +130,11 @@ async function doctor(args: string[]): Promise<void> {
119
130
  printHumanReport(report);
120
131
  }
121
132
 
133
+ // Save successful doctor run status
134
+ if (report.ok && report.context.repoRoot) {
135
+ saveDoctorStatus(report.context.repoRoot, report);
136
+ }
137
+
122
138
  process.exit(report.ok ? 0 : 1);
123
139
  }
124
140