@litmers/cursorflow-orchestrator 0.1.3 → 0.1.6
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 +17 -7
- package/README.md +33 -2
- package/commands/cursorflow-doctor.md +24 -0
- package/commands/cursorflow-signal.md +19 -0
- package/dist/cli/clean.d.ts +5 -0
- package/dist/cli/clean.js +57 -0
- package/dist/cli/clean.js.map +1 -0
- package/dist/cli/doctor.d.ts +15 -0
- package/dist/cli/doctor.js +139 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +125 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +302 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/monitor.d.ts +8 -0
- package/dist/cli/monitor.js +210 -0
- package/dist/cli/monitor.js.map +1 -0
- package/dist/cli/resume.d.ts +5 -0
- package/dist/cli/resume.js +128 -0
- package/dist/cli/resume.js.map +1 -0
- package/dist/cli/run.d.ts +5 -0
- package/dist/cli/run.js +128 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/setup-commands.d.ts +23 -0
- package/dist/cli/setup-commands.js +234 -0
- package/dist/cli/setup-commands.js.map +1 -0
- package/dist/cli/signal.d.ts +7 -0
- package/dist/cli/signal.js +99 -0
- package/dist/cli/signal.js.map +1 -0
- package/dist/core/orchestrator.d.ts +47 -0
- package/dist/core/orchestrator.js +192 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/reviewer.d.ts +60 -0
- package/dist/core/reviewer.js +239 -0
- package/dist/core/reviewer.js.map +1 -0
- package/dist/core/runner.d.ts +51 -0
- package/dist/core/runner.js +499 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/utils/config.d.ts +31 -0
- package/dist/utils/config.js +198 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/cursor-agent.d.ts +61 -0
- package/dist/utils/cursor-agent.js +263 -0
- package/dist/utils/cursor-agent.js.map +1 -0
- package/dist/utils/doctor.d.ts +63 -0
- package/dist/utils/doctor.js +280 -0
- package/dist/utils/doctor.js.map +1 -0
- package/dist/utils/git.d.ts +131 -0
- package/dist/utils/git.js +272 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +68 -0
- package/dist/utils/logger.js +158 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/state.d.ts +65 -0
- package/dist/utils/state.js +216 -0
- package/dist/utils/state.js.map +1 -0
- package/dist/utils/types.d.ts +118 -0
- package/dist/utils/types.js +6 -0
- package/dist/utils/types.js.map +1 -0
- package/examples/README.md +155 -0
- package/examples/demo-project/README.md +262 -0
- package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +18 -0
- package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +18 -0
- package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +109 -0
- package/package.json +71 -61
- package/scripts/ai-security-check.js +11 -4
- package/scripts/local-security-gate.sh +76 -0
- package/src/cli/{clean.js → clean.ts} +11 -5
- package/src/cli/doctor.ts +127 -0
- package/src/cli/{index.js → index.ts} +27 -16
- package/src/cli/{init.js → init.ts} +26 -18
- package/src/cli/{monitor.js → monitor.ts} +57 -44
- package/src/cli/resume.ts +119 -0
- package/src/cli/run.ts +109 -0
- package/src/cli/{setup-commands.js → setup-commands.ts} +38 -18
- package/src/cli/signal.ts +89 -0
- package/src/core/{orchestrator.js → orchestrator.ts} +44 -26
- package/src/core/{reviewer.js → reviewer.ts} +36 -29
- package/src/core/{runner.js → runner.ts} +125 -76
- package/src/utils/{config.js → config.ts} +17 -25
- package/src/utils/{cursor-agent.js → cursor-agent.ts} +38 -47
- package/src/utils/doctor.ts +312 -0
- package/src/utils/{git.js → git.ts} +70 -56
- package/src/utils/{logger.js → logger.ts} +170 -178
- package/src/utils/{state.js → state.ts} +30 -38
- package/src/utils/types.ts +134 -0
- package/src/cli/resume.js +0 -31
- package/src/cli/run.js +0 -51
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Cursor Agent CLI wrapper and utilities
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
import { execSync, spawnSync } from 'child_process';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Check if cursor-agent CLI is installed
|
|
10
9
|
*/
|
|
11
|
-
function checkCursorAgentInstalled() {
|
|
10
|
+
export function checkCursorAgentInstalled(): boolean {
|
|
12
11
|
try {
|
|
13
|
-
|
|
14
|
-
return
|
|
12
|
+
const result = spawnSync('cursor-agent', ['--version'], { stdio: 'pipe' });
|
|
13
|
+
return result.status === 0;
|
|
15
14
|
} catch {
|
|
16
15
|
return false;
|
|
17
16
|
}
|
|
@@ -20,13 +19,13 @@ function checkCursorAgentInstalled() {
|
|
|
20
19
|
/**
|
|
21
20
|
* Get cursor-agent version
|
|
22
21
|
*/
|
|
23
|
-
function getCursorAgentVersion() {
|
|
22
|
+
export function getCursorAgentVersion(): string | null {
|
|
24
23
|
try {
|
|
25
|
-
const
|
|
24
|
+
const result = spawnSync('cursor-agent', ['--version'], {
|
|
26
25
|
encoding: 'utf8',
|
|
27
26
|
stdio: 'pipe',
|
|
28
|
-
})
|
|
29
|
-
return
|
|
27
|
+
});
|
|
28
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
30
29
|
} catch {
|
|
31
30
|
return null;
|
|
32
31
|
}
|
|
@@ -35,7 +34,7 @@ function getCursorAgentVersion() {
|
|
|
35
34
|
/**
|
|
36
35
|
* Ensure cursor-agent is installed, exit with error message if not
|
|
37
36
|
*/
|
|
38
|
-
function ensureCursorAgent() {
|
|
37
|
+
export function ensureCursorAgent(): void {
|
|
39
38
|
if (!checkCursorAgentInstalled()) {
|
|
40
39
|
console.error(`
|
|
41
40
|
❌ cursor-agent CLI is not installed
|
|
@@ -56,7 +55,7 @@ More info: https://docs.cursor.com/agent
|
|
|
56
55
|
/**
|
|
57
56
|
* Print installation guide
|
|
58
57
|
*/
|
|
59
|
-
function printInstallationGuide() {
|
|
58
|
+
export function printInstallationGuide(): void {
|
|
60
59
|
console.log(`
|
|
61
60
|
📦 cursor-agent CLI Installation Guide
|
|
62
61
|
|
|
@@ -83,15 +82,15 @@ After installation, run your command again.
|
|
|
83
82
|
/**
|
|
84
83
|
* Check if CURSOR_API_KEY is set (for cloud execution)
|
|
85
84
|
*/
|
|
86
|
-
function checkCursorApiKey() {
|
|
87
|
-
return !!process.env
|
|
85
|
+
export function checkCursorApiKey(): boolean {
|
|
86
|
+
return !!process.env['CURSOR_API_KEY'];
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
/**
|
|
91
90
|
* Validate cursor-agent setup for given executor type
|
|
92
91
|
*/
|
|
93
|
-
function validateSetup(executor = 'cursor-agent') {
|
|
94
|
-
const errors = [];
|
|
92
|
+
export function validateSetup(executor = 'cursor-agent'): { valid: boolean; errors: string[] } {
|
|
93
|
+
const errors: string[] = [];
|
|
95
94
|
|
|
96
95
|
if (executor === 'cursor-agent') {
|
|
97
96
|
if (!checkCursorAgentInstalled()) {
|
|
@@ -114,20 +113,18 @@ function validateSetup(executor = 'cursor-agent') {
|
|
|
114
113
|
/**
|
|
115
114
|
* Get available models (if cursor-agent supports it)
|
|
116
115
|
*/
|
|
117
|
-
function getAvailableModels() {
|
|
116
|
+
export function getAvailableModels(): string[] {
|
|
118
117
|
try {
|
|
119
118
|
// This is a placeholder - actual implementation depends on cursor-agent API
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
119
|
+
// execSync('cursor-agent --model invalid "test"', {
|
|
120
|
+
// encoding: 'utf8',
|
|
121
|
+
// stdio: 'pipe',
|
|
122
|
+
// });
|
|
124
123
|
|
|
125
|
-
// Parse models from error message
|
|
126
|
-
// This is an example - actual parsing depends on cursor-agent output
|
|
127
124
|
return [];
|
|
128
|
-
} catch (error) {
|
|
125
|
+
} catch (error: any) {
|
|
129
126
|
// Parse from error message
|
|
130
|
-
const output = error.stderr || error.stdout || '';
|
|
127
|
+
const output = (error.stderr || error.stdout || '').toString();
|
|
131
128
|
// Extract model names from output
|
|
132
129
|
return parseModelsFromOutput(output);
|
|
133
130
|
}
|
|
@@ -136,10 +133,10 @@ function getAvailableModels() {
|
|
|
136
133
|
/**
|
|
137
134
|
* Parse model names from cursor-agent output
|
|
138
135
|
*/
|
|
139
|
-
function parseModelsFromOutput(output) {
|
|
136
|
+
export function parseModelsFromOutput(output: string): string[] {
|
|
140
137
|
// This is a placeholder implementation
|
|
141
138
|
// Actual parsing depends on cursor-agent CLI output format
|
|
142
|
-
const models = [];
|
|
139
|
+
const models: string[] = [];
|
|
143
140
|
|
|
144
141
|
// Example parsing logic
|
|
145
142
|
const lines = output.split('\n');
|
|
@@ -147,7 +144,7 @@ function parseModelsFromOutput(output) {
|
|
|
147
144
|
if (line.includes('sonnet') || line.includes('opus') || line.includes('gpt')) {
|
|
148
145
|
const match = line.match(/['"]([^'"]+)['"]/);
|
|
149
146
|
if (match) {
|
|
150
|
-
models.push(match[1]);
|
|
147
|
+
models.push(match[1]!);
|
|
151
148
|
}
|
|
152
149
|
}
|
|
153
150
|
}
|
|
@@ -158,7 +155,7 @@ function parseModelsFromOutput(output) {
|
|
|
158
155
|
/**
|
|
159
156
|
* Test cursor-agent with a simple command
|
|
160
157
|
*/
|
|
161
|
-
function testCursorAgent() {
|
|
158
|
+
export function testCursorAgent(): { success: boolean; output?: string; error?: string } {
|
|
162
159
|
try {
|
|
163
160
|
const result = spawnSync('cursor-agent', ['--help'], {
|
|
164
161
|
encoding: 'utf8',
|
|
@@ -170,7 +167,7 @@ function testCursorAgent() {
|
|
|
170
167
|
output: result.stdout,
|
|
171
168
|
error: result.stderr,
|
|
172
169
|
};
|
|
173
|
-
} catch (error) {
|
|
170
|
+
} catch (error: any) {
|
|
174
171
|
return {
|
|
175
172
|
success: false,
|
|
176
173
|
error: error.message,
|
|
@@ -178,10 +175,18 @@ function testCursorAgent() {
|
|
|
178
175
|
}
|
|
179
176
|
}
|
|
180
177
|
|
|
178
|
+
export interface AuthCheckResult {
|
|
179
|
+
authenticated: boolean;
|
|
180
|
+
message: string;
|
|
181
|
+
details?: string;
|
|
182
|
+
help?: string;
|
|
183
|
+
error?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
181
186
|
/**
|
|
182
187
|
* Check cursor-agent authentication
|
|
183
188
|
*/
|
|
184
|
-
function checkCursorAuth() {
|
|
189
|
+
export function checkCursorAuth(): AuthCheckResult {
|
|
185
190
|
try {
|
|
186
191
|
const result = spawnSync('cursor-agent', ['create-chat'], {
|
|
187
192
|
encoding: 'utf8',
|
|
@@ -196,7 +201,7 @@ function checkCursorAuth() {
|
|
|
196
201
|
};
|
|
197
202
|
}
|
|
198
203
|
|
|
199
|
-
const errorMsg = result.stderr?.trim() || result.stdout?.trim() || '';
|
|
204
|
+
const errorMsg = (result.stderr?.trim() || result.stdout?.trim() || '').toString();
|
|
200
205
|
|
|
201
206
|
// Check for authentication errors
|
|
202
207
|
if (errorMsg.includes('not authenticated') ||
|
|
@@ -227,7 +232,7 @@ function checkCursorAuth() {
|
|
|
227
232
|
message: 'Unknown error',
|
|
228
233
|
details: errorMsg,
|
|
229
234
|
};
|
|
230
|
-
} catch (error) {
|
|
235
|
+
} catch (error: any) {
|
|
231
236
|
if (error.code === 'ETIMEDOUT') {
|
|
232
237
|
return {
|
|
233
238
|
authenticated: false,
|
|
@@ -247,7 +252,7 @@ function checkCursorAuth() {
|
|
|
247
252
|
/**
|
|
248
253
|
* Print authentication help
|
|
249
254
|
*/
|
|
250
|
-
function printAuthHelp() {
|
|
255
|
+
export function printAuthHelp(): void {
|
|
251
256
|
console.log(`
|
|
252
257
|
🔐 Cursor Authentication Required
|
|
253
258
|
|
|
@@ -268,19 +273,5 @@ Common issues:
|
|
|
268
273
|
• VPN or firewall blocking Cursor API
|
|
269
274
|
|
|
270
275
|
For more help, visit: https://docs.cursor.com
|
|
271
|
-
|
|
272
276
|
`);
|
|
273
277
|
}
|
|
274
|
-
|
|
275
|
-
module.exports = {
|
|
276
|
-
checkCursorAgentInstalled,
|
|
277
|
-
getCursorAgentVersion,
|
|
278
|
-
ensureCursorAgent,
|
|
279
|
-
printInstallationGuide,
|
|
280
|
-
checkCursorApiKey,
|
|
281
|
-
validateSetup,
|
|
282
|
-
getAvailableModels,
|
|
283
|
-
testCursorAgent,
|
|
284
|
-
checkCursorAuth,
|
|
285
|
-
printAuthHelp,
|
|
286
|
-
};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow Doctor - environment and preflight checks
|
|
3
|
+
*
|
|
4
|
+
* This module provides actionable diagnostics for common run failures:
|
|
5
|
+
* - Not inside a Git work tree
|
|
6
|
+
* - Missing `origin` remote (required for pushing lane branches)
|
|
7
|
+
* - Missing Git worktree support
|
|
8
|
+
* - Missing base branch referenced by lane task files
|
|
9
|
+
* - Missing/invalid tasks directory
|
|
10
|
+
* - Missing Cursor Agent setup (optional)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
|
|
16
|
+
import * as git from './git';
|
|
17
|
+
import { checkCursorAgentInstalled, checkCursorAuth } from './cursor-agent';
|
|
18
|
+
import { areCommandsInstalled } from '../cli/setup-commands';
|
|
19
|
+
|
|
20
|
+
export type DoctorSeverity = 'error' | 'warn';
|
|
21
|
+
|
|
22
|
+
export interface DoctorIssue {
|
|
23
|
+
/**
|
|
24
|
+
* Stable identifier for machines (NOCC) and tests.
|
|
25
|
+
*/
|
|
26
|
+
id: string;
|
|
27
|
+
severity: DoctorSeverity;
|
|
28
|
+
title: string;
|
|
29
|
+
message: string;
|
|
30
|
+
/**
|
|
31
|
+
* Suggested commands or steps to fix the issue.
|
|
32
|
+
*/
|
|
33
|
+
fixes?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Optional technical details (stderr, stdout, etc.)
|
|
36
|
+
*/
|
|
37
|
+
details?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DoctorContext {
|
|
41
|
+
cwd: string;
|
|
42
|
+
repoRoot?: string;
|
|
43
|
+
tasksDir?: string;
|
|
44
|
+
executor?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DoctorReport {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
issues: DoctorIssue[];
|
|
50
|
+
context: DoctorContext;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DoctorOptions {
|
|
54
|
+
cwd?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Optional tasks directory (used for `cursorflow run` preflight).
|
|
57
|
+
*/
|
|
58
|
+
tasksDir?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Executor type ('cursor-agent' | 'cloud' | ...).
|
|
61
|
+
*/
|
|
62
|
+
executor?: string;
|
|
63
|
+
/**
|
|
64
|
+
* When true (default), include Cursor Agent install/auth checks.
|
|
65
|
+
*/
|
|
66
|
+
includeCursorAgentChecks?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function addIssue(issues: DoctorIssue[], issue: DoctorIssue): void {
|
|
70
|
+
issues.push(issue);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveRepoRoot(cwd: string): string | null {
|
|
74
|
+
const res = git.runGitResult(['rev-parse', '--show-toplevel'], { cwd });
|
|
75
|
+
if (!res.success || !res.stdout) return null;
|
|
76
|
+
return res.stdout;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isInsideGitWorktree(cwd: string): boolean {
|
|
80
|
+
const res = git.runGitResult(['rev-parse', '--is-inside-work-tree'], { cwd });
|
|
81
|
+
return res.success && res.stdout.trim() === 'true';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasAtLeastOneCommit(repoRoot: string): boolean {
|
|
85
|
+
const res = git.runGitResult(['rev-parse', '--verify', 'HEAD'], { cwd: repoRoot });
|
|
86
|
+
return res.success;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function hasOriginRemote(repoRoot: string): boolean {
|
|
90
|
+
const res = git.runGitResult(['remote', 'get-url', 'origin'], { cwd: repoRoot });
|
|
91
|
+
return res.success && !!res.stdout;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hasWorktreeSupport(repoRoot: string): { ok: boolean; details?: string } {
|
|
95
|
+
const res = git.runGitResult(['worktree', 'list'], { cwd: repoRoot });
|
|
96
|
+
if (res.success) return { ok: true };
|
|
97
|
+
return { ok: false, details: res.stderr || res.stdout || 'git worktree failed' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function branchExists(repoRoot: string, branchName: string): boolean {
|
|
101
|
+
// rev-parse --verify works for both branches and tags; restrict to heads first.
|
|
102
|
+
const headRes = git.runGitResult(['show-ref', '--verify', `refs/heads/${branchName}`], { cwd: repoRoot });
|
|
103
|
+
if (headRes.success) return true;
|
|
104
|
+
const anyRes = git.runGitResult(['rev-parse', '--verify', branchName], { cwd: repoRoot });
|
|
105
|
+
return anyRes.success;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readLaneJsonFiles(tasksDir: string): { path: string; json: any }[] {
|
|
109
|
+
const files = fs
|
|
110
|
+
.readdirSync(tasksDir)
|
|
111
|
+
.filter(f => f.endsWith('.json'))
|
|
112
|
+
.sort()
|
|
113
|
+
.map(f => path.join(tasksDir, f));
|
|
114
|
+
|
|
115
|
+
return files.map(p => {
|
|
116
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
117
|
+
const json = JSON.parse(raw);
|
|
118
|
+
return { path: p, json };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function collectBaseBranchesFromLanes(lanes: { path: string; json: any }[], defaultBaseBranch: string): string[] {
|
|
123
|
+
const set = new Set<string>();
|
|
124
|
+
for (const lane of lanes) {
|
|
125
|
+
const baseBranch = String(lane.json?.baseBranch || defaultBaseBranch || 'main').trim();
|
|
126
|
+
if (baseBranch) set.add(baseBranch);
|
|
127
|
+
}
|
|
128
|
+
return Array.from(set);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run doctor checks.
|
|
133
|
+
*
|
|
134
|
+
* If `tasksDir` is provided, additional preflight checks are performed:
|
|
135
|
+
* - tasks directory existence and JSON validity
|
|
136
|
+
* - baseBranch referenced by lanes exists locally
|
|
137
|
+
*/
|
|
138
|
+
export function runDoctor(options: DoctorOptions = {}): DoctorReport {
|
|
139
|
+
const cwd = options.cwd || process.cwd();
|
|
140
|
+
const issues: DoctorIssue[] = [];
|
|
141
|
+
|
|
142
|
+
const context: DoctorContext = {
|
|
143
|
+
cwd,
|
|
144
|
+
executor: options.executor,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// 1) Git repository checks
|
|
148
|
+
if (!isInsideGitWorktree(cwd)) {
|
|
149
|
+
addIssue(issues, {
|
|
150
|
+
id: 'git.not_repo',
|
|
151
|
+
severity: 'error',
|
|
152
|
+
title: 'Not a Git repository',
|
|
153
|
+
message: 'Current directory is not inside a Git work tree. CursorFlow requires Git to create worktrees and branches.',
|
|
154
|
+
fixes: [
|
|
155
|
+
'cd into your repository root (or any subdirectory inside it)',
|
|
156
|
+
'If this is a new project: git init && git commit --allow-empty -m "chore: initial commit"',
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return { ok: false, issues, context };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const repoRoot = resolveRepoRoot(cwd) || undefined;
|
|
164
|
+
context.repoRoot = repoRoot;
|
|
165
|
+
const gitCwd = repoRoot || cwd;
|
|
166
|
+
|
|
167
|
+
if (!hasAtLeastOneCommit(gitCwd)) {
|
|
168
|
+
addIssue(issues, {
|
|
169
|
+
id: 'git.no_commits',
|
|
170
|
+
severity: 'error',
|
|
171
|
+
title: 'Repository has no commits',
|
|
172
|
+
message: 'CursorFlow needs at least one commit (HEAD) to create worktrees and new branches.',
|
|
173
|
+
fixes: ['git commit --allow-empty -m "chore: initial commit"'],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!hasOriginRemote(gitCwd)) {
|
|
178
|
+
addIssue(issues, {
|
|
179
|
+
id: 'git.no_origin',
|
|
180
|
+
severity: 'error',
|
|
181
|
+
title: "Missing remote 'origin'",
|
|
182
|
+
message: "Remote 'origin' is not configured. CursorFlow pushes lane branches to 'origin' during execution.",
|
|
183
|
+
fixes: [
|
|
184
|
+
'git remote add origin <your-repo-url>',
|
|
185
|
+
'git remote -v # verify remotes',
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const wt = hasWorktreeSupport(gitCwd);
|
|
191
|
+
if (!wt.ok) {
|
|
192
|
+
addIssue(issues, {
|
|
193
|
+
id: 'git.worktree_unavailable',
|
|
194
|
+
severity: 'error',
|
|
195
|
+
title: 'Git worktree not available',
|
|
196
|
+
message: 'Git worktree support is required, but `git worktree` failed.',
|
|
197
|
+
fixes: [
|
|
198
|
+
'Upgrade Git (worktrees require Git >= 2.5)',
|
|
199
|
+
'Ensure the repository is not corrupted',
|
|
200
|
+
],
|
|
201
|
+
details: wt.details,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 2) Tasks-dir checks (optional; used by `cursorflow run` preflight)
|
|
206
|
+
if (options.tasksDir) {
|
|
207
|
+
const tasksDirAbs = path.isAbsolute(options.tasksDir)
|
|
208
|
+
? options.tasksDir
|
|
209
|
+
: path.resolve(cwd, options.tasksDir);
|
|
210
|
+
context.tasksDir = tasksDirAbs;
|
|
211
|
+
|
|
212
|
+
if (!fs.existsSync(tasksDirAbs)) {
|
|
213
|
+
addIssue(issues, {
|
|
214
|
+
id: 'tasks.missing_dir',
|
|
215
|
+
severity: 'error',
|
|
216
|
+
title: 'Tasks directory not found',
|
|
217
|
+
message: `Tasks directory does not exist: ${tasksDirAbs}`,
|
|
218
|
+
fixes: [
|
|
219
|
+
'Double-check the path you passed to `cursorflow run`',
|
|
220
|
+
'If needed, run: cursorflow init --example',
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
let lanes: { path: string; json: any }[] = [];
|
|
225
|
+
try {
|
|
226
|
+
lanes = readLaneJsonFiles(tasksDirAbs);
|
|
227
|
+
} catch (error: any) {
|
|
228
|
+
addIssue(issues, {
|
|
229
|
+
id: 'tasks.invalid_json',
|
|
230
|
+
severity: 'error',
|
|
231
|
+
title: 'Invalid lane JSON file',
|
|
232
|
+
message: `Failed to read/parse lane JSON files in: ${tasksDirAbs}`,
|
|
233
|
+
details: error?.message ? String(error.message) : String(error),
|
|
234
|
+
fixes: ['Validate JSON syntax for each lane file (*.json)'],
|
|
235
|
+
});
|
|
236
|
+
lanes = [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (lanes.length === 0) {
|
|
240
|
+
addIssue(issues, {
|
|
241
|
+
id: 'tasks.no_lanes',
|
|
242
|
+
severity: 'error',
|
|
243
|
+
title: 'No lane files found',
|
|
244
|
+
message: `No lane task files (*.json) found in: ${tasksDirAbs}`,
|
|
245
|
+
fixes: ['Ensure the tasks directory contains one or more lane JSON files'],
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
const baseBranches = collectBaseBranchesFromLanes(lanes, 'main');
|
|
249
|
+
for (const baseBranch of baseBranches) {
|
|
250
|
+
if (!branchExists(gitCwd, baseBranch)) {
|
|
251
|
+
addIssue(issues, {
|
|
252
|
+
id: `git.missing_base_branch.${baseBranch}`,
|
|
253
|
+
severity: 'error',
|
|
254
|
+
title: `Missing base branch: ${baseBranch}`,
|
|
255
|
+
message: `Lane files reference baseBranch "${baseBranch}", but it does not exist locally.`,
|
|
256
|
+
fixes: [
|
|
257
|
+
'git fetch origin --prune',
|
|
258
|
+
`Or update lane JSON baseBranch to an existing branch`,
|
|
259
|
+
],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 3) Cursor Agent checks (optional)
|
|
268
|
+
const includeCursor = options.includeCursorAgentChecks !== false;
|
|
269
|
+
if (includeCursor && (options.executor || 'cursor-agent') === 'cursor-agent') {
|
|
270
|
+
if (!checkCursorAgentInstalled()) {
|
|
271
|
+
addIssue(issues, {
|
|
272
|
+
id: 'cursor_agent.missing',
|
|
273
|
+
severity: 'error',
|
|
274
|
+
title: 'cursor-agent CLI not installed',
|
|
275
|
+
message: 'cursor-agent is required for local execution.',
|
|
276
|
+
fixes: ['npm install -g @cursor/agent', 'cursor-agent --version'],
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
const auth = checkCursorAuth();
|
|
280
|
+
if (!auth.authenticated) {
|
|
281
|
+
addIssue(issues, {
|
|
282
|
+
id: 'cursor_agent.not_authenticated',
|
|
283
|
+
severity: 'error',
|
|
284
|
+
title: 'Cursor authentication required',
|
|
285
|
+
message: auth.message,
|
|
286
|
+
details: auth.details || auth.error,
|
|
287
|
+
fixes: [
|
|
288
|
+
'Open Cursor IDE and sign in',
|
|
289
|
+
'Verify AI features work in the IDE',
|
|
290
|
+
'Re-run: cursorflow doctor',
|
|
291
|
+
],
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 4) IDE Integration checks
|
|
298
|
+
if (!areCommandsInstalled()) {
|
|
299
|
+
addIssue(issues, {
|
|
300
|
+
id: 'ide.commands_missing',
|
|
301
|
+
severity: 'warn',
|
|
302
|
+
title: 'Cursor IDE commands not installed',
|
|
303
|
+
message: 'CursorFlow slash commands (e.g., /cursorflow-run) are missing or outdated.',
|
|
304
|
+
fixes: ['cursorflow-setup --force', 'or run `cursorflow run` to auto-install'],
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const ok = issues.every(i => i.severity !== 'error');
|
|
309
|
+
return { ok, issues, context };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|