@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,31 +1,43 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Core Runner - Execute tasks sequentially in a lane
|
|
4
3
|
*
|
|
5
4
|
* Adapted from sequential-agent-runner.js
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { execSync, spawnSync } from 'child_process';
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
import * as git from '../utils/git';
|
|
12
|
+
import * as logger from '../utils/logger';
|
|
13
|
+
import { ensureCursorAgent, checkCursorAuth, printAuthHelp } from '../utils/cursor-agent';
|
|
14
|
+
import { saveState, appendLog, createConversationEntry } from '../utils/state';
|
|
15
|
+
import {
|
|
16
|
+
RunnerConfig,
|
|
17
|
+
Task,
|
|
18
|
+
TaskExecutionResult,
|
|
19
|
+
AgentSendResult,
|
|
20
|
+
DependencyPolicy,
|
|
21
|
+
DependencyRequestPlan,
|
|
22
|
+
LaneState
|
|
23
|
+
} from '../utils/types';
|
|
16
24
|
|
|
17
25
|
/**
|
|
18
26
|
* Execute cursor-agent command with timeout and better error handling
|
|
19
27
|
*/
|
|
20
|
-
function cursorAgentCreateChat() {
|
|
21
|
-
const { execSync } = require('child_process');
|
|
22
|
-
|
|
28
|
+
export function cursorAgentCreateChat(): string {
|
|
23
29
|
try {
|
|
24
|
-
const
|
|
30
|
+
const res = spawnSync('cursor-agent', ['create-chat'], {
|
|
25
31
|
encoding: 'utf8',
|
|
26
32
|
stdio: 'pipe',
|
|
27
33
|
timeout: 30000, // 30 second timeout
|
|
28
34
|
});
|
|
35
|
+
|
|
36
|
+
if (res.error || res.status !== 0) {
|
|
37
|
+
throw res.error || new Error(res.stderr || 'Failed to create chat');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const out = res.stdout;
|
|
29
41
|
const lines = out.split('\n').filter(Boolean);
|
|
30
42
|
const chatId = lines[lines.length - 1] || null;
|
|
31
43
|
|
|
@@ -35,7 +47,7 @@ function cursorAgentCreateChat() {
|
|
|
35
47
|
|
|
36
48
|
logger.info(`Created chat session: ${chatId}`);
|
|
37
49
|
return chatId;
|
|
38
|
-
} catch (error) {
|
|
50
|
+
} catch (error: any) {
|
|
39
51
|
// Check for common errors
|
|
40
52
|
if (error.message.includes('ENOENT')) {
|
|
41
53
|
throw new Error('cursor-agent CLI not found. Install with: npm install -g @cursor/agent');
|
|
@@ -77,14 +89,14 @@ function cursorAgentCreateChat() {
|
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
91
|
|
|
80
|
-
function parseJsonFromStdout(stdout) {
|
|
92
|
+
function parseJsonFromStdout(stdout: string): any {
|
|
81
93
|
const text = String(stdout || '').trim();
|
|
82
94
|
if (!text) return null;
|
|
83
95
|
const lines = text.split('\n').filter(Boolean);
|
|
84
96
|
|
|
85
97
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
86
|
-
const line = lines[i]
|
|
87
|
-
if (line
|
|
98
|
+
const line = lines[i]?.trim();
|
|
99
|
+
if (line?.startsWith('{') && line?.endsWith('}')) {
|
|
88
100
|
try {
|
|
89
101
|
return JSON.parse(line);
|
|
90
102
|
} catch {
|
|
@@ -95,9 +107,12 @@ function parseJsonFromStdout(stdout) {
|
|
|
95
107
|
return null;
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
function cursorAgentSend({ workspaceDir, chatId, prompt, model }
|
|
99
|
-
|
|
100
|
-
|
|
110
|
+
export function cursorAgentSend({ workspaceDir, chatId, prompt, model }: {
|
|
111
|
+
workspaceDir: string;
|
|
112
|
+
chatId: string;
|
|
113
|
+
prompt: string;
|
|
114
|
+
model?: string;
|
|
115
|
+
}): AgentSendResult {
|
|
101
116
|
const args = [
|
|
102
117
|
'--print',
|
|
103
118
|
'--output-format', 'json',
|
|
@@ -117,7 +132,7 @@ function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
|
|
|
117
132
|
|
|
118
133
|
// Check for timeout
|
|
119
134
|
if (res.error) {
|
|
120
|
-
if (res.error.code === 'ETIMEDOUT') {
|
|
135
|
+
if ((res.error as any).code === 'ETIMEDOUT') {
|
|
121
136
|
return {
|
|
122
137
|
ok: false,
|
|
123
138
|
exitCode: -1,
|
|
@@ -166,14 +181,14 @@ function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
|
|
|
166
181
|
|
|
167
182
|
return {
|
|
168
183
|
ok: false,
|
|
169
|
-
exitCode: res.status,
|
|
184
|
+
exitCode: res.status ?? -1,
|
|
170
185
|
error: errorMsg,
|
|
171
186
|
};
|
|
172
187
|
}
|
|
173
188
|
|
|
174
189
|
return {
|
|
175
190
|
ok: !json.is_error,
|
|
176
|
-
exitCode: res.status,
|
|
191
|
+
exitCode: res.status ?? 0,
|
|
177
192
|
sessionId: json.session_id || chatId,
|
|
178
193
|
resultText: json.result || '',
|
|
179
194
|
};
|
|
@@ -182,12 +197,12 @@ function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
|
|
|
182
197
|
/**
|
|
183
198
|
* Extract dependency change request from agent response
|
|
184
199
|
*/
|
|
185
|
-
function extractDependencyRequest(text) {
|
|
200
|
+
export function extractDependencyRequest(text: string): { required: boolean; plan?: DependencyRequestPlan; raw: string } {
|
|
186
201
|
const t = String(text || '');
|
|
187
202
|
const marker = 'DEPENDENCY_CHANGE_REQUIRED';
|
|
188
203
|
|
|
189
204
|
if (!t.includes(marker)) {
|
|
190
|
-
return { required: false };
|
|
205
|
+
return { required: false, raw: t };
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
const after = t.split(marker).slice(1).join(marker);
|
|
@@ -197,7 +212,7 @@ function extractDependencyRequest(text) {
|
|
|
197
212
|
try {
|
|
198
213
|
return {
|
|
199
214
|
required: true,
|
|
200
|
-
plan: JSON.parse(match[0]),
|
|
215
|
+
plan: JSON.parse(match[0]!) as DependencyRequestPlan,
|
|
201
216
|
raw: t,
|
|
202
217
|
};
|
|
203
218
|
} catch {
|
|
@@ -211,7 +226,7 @@ function extractDependencyRequest(text) {
|
|
|
211
226
|
/**
|
|
212
227
|
* Wrap prompt with dependency policy
|
|
213
228
|
*/
|
|
214
|
-
function wrapPromptForDependencyPolicy(prompt, policy) {
|
|
229
|
+
export function wrapPromptForDependencyPolicy(prompt: string, policy: DependencyPolicy): string {
|
|
215
230
|
if (policy.allowDependencyChange && !policy.lockfileReadOnly) {
|
|
216
231
|
return prompt;
|
|
217
232
|
}
|
|
@@ -243,8 +258,8 @@ ${prompt}`;
|
|
|
243
258
|
/**
|
|
244
259
|
* Apply file permissions based on dependency policy
|
|
245
260
|
*/
|
|
246
|
-
function applyDependencyFilePermissions(worktreeDir, policy) {
|
|
247
|
-
const targets = [];
|
|
261
|
+
export function applyDependencyFilePermissions(worktreeDir: string, policy: DependencyPolicy): void {
|
|
262
|
+
const targets: string[] = [];
|
|
248
263
|
|
|
249
264
|
if (!policy.allowDependencyChange) {
|
|
250
265
|
targets.push('package.json');
|
|
@@ -271,16 +286,24 @@ function applyDependencyFilePermissions(worktreeDir, policy) {
|
|
|
271
286
|
/**
|
|
272
287
|
* Run a single task
|
|
273
288
|
*/
|
|
274
|
-
async function runTask({
|
|
289
|
+
export async function runTask({
|
|
275
290
|
task,
|
|
276
291
|
config,
|
|
277
292
|
index,
|
|
278
293
|
worktreeDir,
|
|
279
|
-
pipelineBranch,
|
|
280
294
|
taskBranch,
|
|
281
295
|
chatId,
|
|
282
296
|
runDir,
|
|
283
|
-
}
|
|
297
|
+
}: {
|
|
298
|
+
task: Task;
|
|
299
|
+
config: RunnerConfig;
|
|
300
|
+
index: number;
|
|
301
|
+
worktreeDir: string;
|
|
302
|
+
pipelineBranch: string;
|
|
303
|
+
taskBranch: string;
|
|
304
|
+
chatId: string;
|
|
305
|
+
runDir: string;
|
|
306
|
+
}): Promise<TaskExecutionResult> {
|
|
284
307
|
const model = task.model || config.model || 'sonnet-4.5';
|
|
285
308
|
const convoPath = path.join(runDir, 'conversation.jsonl');
|
|
286
309
|
|
|
@@ -310,7 +333,7 @@ async function runTask({
|
|
|
310
333
|
model,
|
|
311
334
|
});
|
|
312
335
|
|
|
313
|
-
appendLog(convoPath, createConversationEntry('assistant', r1.resultText || r1.error, {
|
|
336
|
+
appendLog(convoPath, createConversationEntry('assistant', r1.resultText || r1.error || 'No response', {
|
|
314
337
|
task: task.name,
|
|
315
338
|
model,
|
|
316
339
|
}));
|
|
@@ -325,7 +348,7 @@ async function runTask({
|
|
|
325
348
|
}
|
|
326
349
|
|
|
327
350
|
// Check for dependency request
|
|
328
|
-
const depReq = extractDependencyRequest(r1.resultText);
|
|
351
|
+
const depReq = extractDependencyRequest(r1.resultText || '');
|
|
329
352
|
if (depReq.required && !config.dependencyPolicy.allowDependencyChange) {
|
|
330
353
|
return {
|
|
331
354
|
taskName: task.name,
|
|
@@ -348,8 +371,8 @@ async function runTask({
|
|
|
348
371
|
/**
|
|
349
372
|
* Run all tasks in sequence
|
|
350
373
|
*/
|
|
351
|
-
async function runTasks(config, runDir) {
|
|
352
|
-
const
|
|
374
|
+
export async function runTasks(tasksFile: string, config: RunnerConfig, runDir: string, options: { startIndex?: number } = {}): Promise<TaskExecutionResult[]> {
|
|
375
|
+
const startIndex = options.startIndex || 0;
|
|
353
376
|
|
|
354
377
|
// Ensure cursor-agent is installed
|
|
355
378
|
ensureCursorAgent();
|
|
@@ -379,41 +402,68 @@ async function runTasks(config, runDir) {
|
|
|
379
402
|
logger.success('✓ Cursor authentication OK');
|
|
380
403
|
|
|
381
404
|
const repoRoot = git.getRepoRoot();
|
|
382
|
-
const pipelineBranch = config.pipelineBranch || `${config.branchPrefix}${Date.now().toString(36)}`;
|
|
383
|
-
const worktreeDir = path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees', pipelineBranch);
|
|
384
405
|
|
|
385
|
-
|
|
406
|
+
// Load existing state if resuming
|
|
407
|
+
const statePath = path.join(runDir, 'state.json');
|
|
408
|
+
let state: LaneState | null = null;
|
|
409
|
+
|
|
410
|
+
if (startIndex > 0 && fs.existsSync(statePath)) {
|
|
411
|
+
state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const pipelineBranch = state?.pipelineBranch || config.pipelineBranch || `${config.branchPrefix || 'cursorflow/'}${Date.now().toString(36)}`;
|
|
415
|
+
const worktreeDir = state?.worktreeDir || path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees', pipelineBranch);
|
|
416
|
+
|
|
417
|
+
if (startIndex === 0) {
|
|
418
|
+
logger.section('🚀 Starting Pipeline');
|
|
419
|
+
} else {
|
|
420
|
+
logger.section(`🔁 Resuming Pipeline from task ${startIndex + 1}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
386
423
|
logger.info(`Pipeline Branch: ${pipelineBranch}`);
|
|
387
424
|
logger.info(`Worktree: ${worktreeDir}`);
|
|
388
425
|
logger.info(`Tasks: ${config.tasks.length}`);
|
|
389
426
|
|
|
390
|
-
// Create worktree
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
427
|
+
// Create worktree only if starting fresh
|
|
428
|
+
if (startIndex === 0 || !fs.existsSync(worktreeDir)) {
|
|
429
|
+
git.createWorktree(worktreeDir, pipelineBranch, {
|
|
430
|
+
baseBranch: config.baseBranch || 'main',
|
|
431
|
+
cwd: repoRoot,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
395
434
|
|
|
396
435
|
// Create chat
|
|
397
436
|
logger.info('Creating chat session...');
|
|
398
437
|
const chatId = cursorAgentCreateChat();
|
|
399
438
|
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
439
|
+
// Initialize state if not loaded
|
|
440
|
+
if (!state) {
|
|
441
|
+
state = {
|
|
442
|
+
status: 'running',
|
|
443
|
+
pipelineBranch,
|
|
444
|
+
worktreeDir,
|
|
445
|
+
totalTasks: config.tasks.length,
|
|
446
|
+
currentTaskIndex: 0,
|
|
447
|
+
label: pipelineBranch,
|
|
448
|
+
startTime: Date.now(),
|
|
449
|
+
endTime: null,
|
|
450
|
+
error: null,
|
|
451
|
+
dependencyRequest: null,
|
|
452
|
+
tasksFile, // Store tasks file for resume
|
|
453
|
+
};
|
|
454
|
+
} else {
|
|
455
|
+
state.status = 'running';
|
|
456
|
+
state.error = null;
|
|
457
|
+
state.dependencyRequest = null;
|
|
458
|
+
}
|
|
409
459
|
|
|
410
|
-
saveState(
|
|
460
|
+
saveState(statePath, state);
|
|
411
461
|
|
|
412
462
|
// Run tasks
|
|
413
|
-
const results = [];
|
|
463
|
+
const results: TaskExecutionResult[] = [];
|
|
414
464
|
|
|
415
|
-
for (let i =
|
|
416
|
-
const task = config.tasks[i]
|
|
465
|
+
for (let i = startIndex; i < config.tasks.length; i++) {
|
|
466
|
+
const task = config.tasks[i]!;
|
|
417
467
|
const taskBranch = `${pipelineBranch}--${String(i + 1).padStart(2, '0')}-${task.name}`;
|
|
418
468
|
|
|
419
469
|
const result = await runTask({
|
|
@@ -431,20 +481,21 @@ async function runTasks(config, runDir) {
|
|
|
431
481
|
|
|
432
482
|
// Update state
|
|
433
483
|
state.currentTaskIndex = i + 1;
|
|
434
|
-
saveState(
|
|
484
|
+
saveState(statePath, state);
|
|
435
485
|
|
|
436
486
|
// Handle blocked or error
|
|
437
487
|
if (result.status === 'BLOCKED_DEPENDENCY') {
|
|
438
|
-
state.status = '
|
|
439
|
-
state.dependencyRequest = result.dependencyRequest;
|
|
440
|
-
saveState(
|
|
488
|
+
state.status = 'failed';
|
|
489
|
+
state.dependencyRequest = result.dependencyRequest || null;
|
|
490
|
+
saveState(statePath, state);
|
|
441
491
|
logger.warn('Task blocked on dependency change');
|
|
442
492
|
process.exit(2);
|
|
443
493
|
}
|
|
444
494
|
|
|
445
495
|
if (result.status !== 'FINISHED') {
|
|
446
496
|
state.status = 'failed';
|
|
447
|
-
|
|
497
|
+
state.error = result.error || 'Unknown error';
|
|
498
|
+
saveState(statePath, state);
|
|
448
499
|
logger.error(`Task failed: ${result.error}`);
|
|
449
500
|
process.exit(1);
|
|
450
501
|
}
|
|
@@ -457,17 +508,13 @@ async function runTasks(config, runDir) {
|
|
|
457
508
|
|
|
458
509
|
// Complete
|
|
459
510
|
state.status = 'completed';
|
|
460
|
-
|
|
511
|
+
state.endTime = Date.now();
|
|
512
|
+
saveState(statePath, state);
|
|
461
513
|
|
|
462
514
|
logger.success('All tasks completed!');
|
|
463
515
|
return results;
|
|
464
516
|
}
|
|
465
517
|
|
|
466
|
-
module.exports = {
|
|
467
|
-
runTasks,
|
|
468
|
-
runTask,
|
|
469
|
-
};
|
|
470
|
-
|
|
471
518
|
/**
|
|
472
519
|
* CLI entry point
|
|
473
520
|
*/
|
|
@@ -479,12 +526,14 @@ if (require.main === module) {
|
|
|
479
526
|
process.exit(1);
|
|
480
527
|
}
|
|
481
528
|
|
|
482
|
-
const tasksFile = args[0]
|
|
529
|
+
const tasksFile = args[0]!;
|
|
483
530
|
const runDirIdx = args.indexOf('--run-dir');
|
|
484
|
-
const
|
|
531
|
+
const startIdxIdx = args.indexOf('--start-index');
|
|
532
|
+
// const executorIdx = args.indexOf('--executor');
|
|
485
533
|
|
|
486
|
-
const runDir = runDirIdx >= 0 ? args[runDirIdx + 1] : '.';
|
|
487
|
-
const
|
|
534
|
+
const runDir = runDirIdx >= 0 ? args[runDirIdx + 1]! : '.';
|
|
535
|
+
const startIndex = startIdxIdx >= 0 ? parseInt(args[startIdxIdx + 1] || '0') : 0;
|
|
536
|
+
// const executor = executorIdx >= 0 ? args[executorIdx + 1] : 'cursor-agent';
|
|
488
537
|
|
|
489
538
|
if (!fs.existsSync(tasksFile)) {
|
|
490
539
|
console.error(`Tasks file not found: ${tasksFile}`);
|
|
@@ -492,10 +541,10 @@ if (require.main === module) {
|
|
|
492
541
|
}
|
|
493
542
|
|
|
494
543
|
// Load tasks configuration
|
|
495
|
-
let config;
|
|
544
|
+
let config: RunnerConfig;
|
|
496
545
|
try {
|
|
497
|
-
config = JSON.parse(fs.readFileSync(tasksFile, 'utf8'));
|
|
498
|
-
} catch (error) {
|
|
546
|
+
config = JSON.parse(fs.readFileSync(tasksFile, 'utf8')) as RunnerConfig;
|
|
547
|
+
} catch (error: any) {
|
|
499
548
|
console.error(`Failed to load tasks file: ${error.message}`);
|
|
500
549
|
process.exit(1);
|
|
501
550
|
}
|
|
@@ -507,13 +556,13 @@ if (require.main === module) {
|
|
|
507
556
|
};
|
|
508
557
|
|
|
509
558
|
// Run tasks
|
|
510
|
-
runTasks(config, runDir)
|
|
559
|
+
runTasks(tasksFile, config, runDir, { startIndex })
|
|
511
560
|
.then(() => {
|
|
512
561
|
process.exit(0);
|
|
513
562
|
})
|
|
514
563
|
.catch(error => {
|
|
515
564
|
console.error(`Runner failed: ${error.message}`);
|
|
516
|
-
if (process.env
|
|
565
|
+
if (process.env['DEBUG']) {
|
|
517
566
|
console.error(error.stack);
|
|
518
567
|
}
|
|
519
568
|
process.exit(1);
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Configuration loader for CursorFlow
|
|
4
3
|
*
|
|
5
4
|
* Finds project root and loads user configuration with defaults
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { CursorFlowConfig } from './types';
|
|
10
|
+
export { CursorFlowConfig };
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Find project root by looking for package.json
|
|
13
14
|
*/
|
|
14
|
-
function findProjectRoot(cwd = process.cwd()) {
|
|
15
|
+
export function findProjectRoot(cwd = process.cwd()): string {
|
|
15
16
|
let current = cwd;
|
|
16
17
|
|
|
17
18
|
while (current !== path.parse(current).root) {
|
|
@@ -28,7 +29,7 @@ function findProjectRoot(cwd = process.cwd()) {
|
|
|
28
29
|
/**
|
|
29
30
|
* Load configuration with defaults
|
|
30
31
|
*/
|
|
31
|
-
function loadConfig(projectRoot = null) {
|
|
32
|
+
export function loadConfig(projectRoot: string | null = null): CursorFlowConfig {
|
|
32
33
|
if (!projectRoot) {
|
|
33
34
|
projectRoot = findProjectRoot();
|
|
34
35
|
}
|
|
@@ -36,7 +37,7 @@ function loadConfig(projectRoot = null) {
|
|
|
36
37
|
const configPath = path.join(projectRoot, 'cursorflow.config.js');
|
|
37
38
|
|
|
38
39
|
// Default configuration
|
|
39
|
-
const defaults = {
|
|
40
|
+
const defaults: CursorFlowConfig = {
|
|
40
41
|
// Directories
|
|
41
42
|
tasksDir: '_cursorflow/tasks',
|
|
42
43
|
logsDir: '_cursorflow/logs',
|
|
@@ -46,8 +47,8 @@ function loadConfig(projectRoot = null) {
|
|
|
46
47
|
branchPrefix: 'feature/',
|
|
47
48
|
|
|
48
49
|
// Execution
|
|
49
|
-
executor: 'cursor-agent',
|
|
50
|
-
pollInterval: 60,
|
|
50
|
+
executor: 'cursor-agent',
|
|
51
|
+
pollInterval: 60,
|
|
51
52
|
|
|
52
53
|
// Dependencies
|
|
53
54
|
allowDependencyChange: false,
|
|
@@ -60,12 +61,12 @@ function loadConfig(projectRoot = null) {
|
|
|
60
61
|
|
|
61
62
|
// Lane defaults
|
|
62
63
|
defaultLaneConfig: {
|
|
63
|
-
devPort: 3001,
|
|
64
|
+
devPort: 3001,
|
|
64
65
|
autoCreatePr: false,
|
|
65
66
|
},
|
|
66
67
|
|
|
67
68
|
// Logging
|
|
68
|
-
logLevel: 'info',
|
|
69
|
+
logLevel: 'info',
|
|
69
70
|
verboseGit: false,
|
|
70
71
|
|
|
71
72
|
// Advanced
|
|
@@ -81,7 +82,7 @@ function loadConfig(projectRoot = null) {
|
|
|
81
82
|
try {
|
|
82
83
|
const userConfig = require(configPath);
|
|
83
84
|
return { ...defaults, ...userConfig, projectRoot };
|
|
84
|
-
} catch (error) {
|
|
85
|
+
} catch (error: any) {
|
|
85
86
|
console.warn(`Warning: Failed to load config from ${configPath}: ${error.message}`);
|
|
86
87
|
console.warn('Using default configuration...');
|
|
87
88
|
}
|
|
@@ -93,22 +94,22 @@ function loadConfig(projectRoot = null) {
|
|
|
93
94
|
/**
|
|
94
95
|
* Get absolute path for tasks directory
|
|
95
96
|
*/
|
|
96
|
-
function getTasksDir(config) {
|
|
97
|
+
export function getTasksDir(config: CursorFlowConfig): string {
|
|
97
98
|
return path.join(config.projectRoot, config.tasksDir);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/**
|
|
101
102
|
* Get absolute path for logs directory
|
|
102
103
|
*/
|
|
103
|
-
function getLogsDir(config) {
|
|
104
|
+
export function getLogsDir(config: CursorFlowConfig): string {
|
|
104
105
|
return path.join(config.projectRoot, config.logsDir);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
109
|
* Validate configuration
|
|
109
110
|
*/
|
|
110
|
-
function validateConfig(config) {
|
|
111
|
-
const errors = [];
|
|
111
|
+
export function validateConfig(config: CursorFlowConfig): boolean {
|
|
112
|
+
const errors: string[] = [];
|
|
112
113
|
|
|
113
114
|
if (!config.tasksDir) {
|
|
114
115
|
errors.push('tasksDir is required');
|
|
@@ -136,7 +137,7 @@ function validateConfig(config) {
|
|
|
136
137
|
/**
|
|
137
138
|
* Create default config file
|
|
138
139
|
*/
|
|
139
|
-
function createDefaultConfig(projectRoot, force = false) {
|
|
140
|
+
export function createDefaultConfig(projectRoot: string, force = false): string {
|
|
140
141
|
const configPath = path.join(projectRoot, 'cursorflow.config.js');
|
|
141
142
|
|
|
142
143
|
if (fs.existsSync(configPath) && !force) {
|
|
@@ -184,12 +185,3 @@ function createDefaultConfig(projectRoot, force = false) {
|
|
|
184
185
|
fs.writeFileSync(configPath, template, 'utf8');
|
|
185
186
|
return configPath;
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
-
module.exports = {
|
|
189
|
-
findProjectRoot,
|
|
190
|
-
loadConfig,
|
|
191
|
-
getTasksDir,
|
|
192
|
-
getLogsDir,
|
|
193
|
-
validateConfig,
|
|
194
|
-
createDefaultConfig,
|
|
195
|
-
};
|