@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.
- package/CHANGELOG.md +55 -0
- package/README.md +113 -319
- package/commands/cursorflow-clean.md +24 -135
- package/commands/cursorflow-doctor.md +74 -18
- package/commands/cursorflow-init.md +33 -50
- package/commands/cursorflow-models.md +51 -0
- package/commands/cursorflow-monitor.md +56 -118
- package/commands/cursorflow-prepare.md +410 -108
- package/commands/cursorflow-resume.md +51 -148
- package/commands/cursorflow-review.md +38 -202
- package/commands/cursorflow-run.md +208 -86
- package/commands/cursorflow-signal.md +38 -12
- package/dist/cli/clean.d.ts +3 -1
- package/dist/cli/clean.js +145 -8
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/doctor.js +14 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +32 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +5 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/models.d.ts +7 -0
- package/dist/cli/models.js +104 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/monitor.js +56 -1
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.d.ts +7 -0
- package/dist/cli/prepare.js +748 -0
- package/dist/cli/prepare.js.map +1 -0
- package/dist/cli/resume.js +56 -0
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +30 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.js +18 -0
- package/dist/cli/signal.js.map +1 -1
- package/dist/core/runner.d.ts +9 -1
- package/dist/core/runner.js +139 -23
- package/dist/core/runner.js.map +1 -1
- package/dist/utils/cursor-agent.d.ts +4 -0
- package/dist/utils/cursor-agent.js +58 -10
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/doctor.d.ts +10 -0
- package/dist/utils/doctor.js +581 -1
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/types.d.ts +11 -0
- package/examples/README.md +114 -59
- package/examples/demo-project/README.md +61 -79
- package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +17 -6
- package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +17 -6
- package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +66 -25
- package/package.json +1 -1
- package/scripts/patches/test-cursor-agent.js +203 -0
- package/src/cli/clean.ts +156 -9
- package/src/cli/doctor.ts +18 -2
- package/src/cli/index.ts +33 -21
- package/src/cli/init.ts +6 -4
- package/src/cli/models.ts +83 -0
- package/src/cli/monitor.ts +60 -1
- package/src/cli/prepare.ts +844 -0
- package/src/cli/resume.ts +66 -0
- package/src/cli/run.ts +36 -2
- package/src/cli/signal.ts +22 -0
- package/src/core/runner.ts +164 -23
- package/src/utils/cursor-agent.ts +62 -10
- package/src/utils/doctor.ts +633 -5
- package/src/utils/types.ts +11 -0
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"baseBranch": "main",
|
|
3
|
-
"branchPrefix": "cursorflow/demo-",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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": "
|
|
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
|
|
41
|
+
- Valid Cursor authentication (sign in via Cursor IDE)
|
|
27
42
|
|
|
28
|
-
###
|
|
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
|
-
#
|
|
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
|
|
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-
|
|
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
|
-
-
|
|
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
|
|
99
|
-
|
|
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
|
|
107
|
-
-
|
|
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
|
@@ -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
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|