@litmers/cursorflow-orchestrator 0.1.5 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/utils/doctor.ts"],"names":[],"mappings":"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"}
@@ -96,6 +96,7 @@ export interface LaneState {
96
96
  error: string | null;
97
97
  dependencyRequest: DependencyRequestPlan | null;
98
98
  updatedAt?: number;
99
+ tasksFile?: string;
99
100
  }
100
101
  export interface ConversationEntry {
101
102
  timestamp: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litmers/cursorflow-orchestrator",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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,127 @@
1
+ /**
2
+ * CursorFlow doctor command
3
+ *
4
+ * Usage:
5
+ * cursorflow doctor [options]
6
+ *
7
+ * Options:
8
+ * --json Output machine-readable JSON
9
+ * --tasks-dir <path> Also validate lane files (run preflight)
10
+ * --executor <type> cursor-agent | cloud
11
+ * --no-cursor Skip Cursor Agent install/auth checks
12
+ * --help, -h Show help
13
+ */
14
+
15
+ import * as logger from '../utils/logger';
16
+ import { runDoctor } from '../utils/doctor';
17
+
18
+ interface DoctorCliOptions {
19
+ json: boolean;
20
+ tasksDir: string | null;
21
+ executor: string | null;
22
+ includeCursorAgentChecks: boolean;
23
+ }
24
+
25
+ function printHelp(): void {
26
+ console.log(`
27
+ Usage: cursorflow doctor [options]
28
+
29
+ Verify your environment is ready for CursorFlow runs.
30
+
31
+ Options:
32
+ --json Output machine-readable JSON
33
+ --tasks-dir <path> Also validate lane files (run preflight)
34
+ --executor <type> cursor-agent | cloud
35
+ --no-cursor Skip Cursor Agent install/auth checks
36
+ --help, -h Show help
37
+
38
+ Examples:
39
+ cursorflow doctor
40
+ cursorflow doctor --tasks-dir _cursorflow/tasks/demo-test/
41
+ cursorflow doctor --json
42
+ `);
43
+ }
44
+
45
+ function parseArgs(args: string[]): DoctorCliOptions {
46
+ const tasksDirIdx = args.indexOf('--tasks-dir');
47
+ const executorIdx = args.indexOf('--executor');
48
+
49
+ const options: DoctorCliOptions = {
50
+ json: args.includes('--json'),
51
+ tasksDir: tasksDirIdx >= 0 ? (args[tasksDirIdx + 1] || null) : null,
52
+ executor: executorIdx >= 0 ? (args[executorIdx + 1] || null) : null,
53
+ includeCursorAgentChecks: !args.includes('--no-cursor'),
54
+ };
55
+
56
+ if (args.includes('--help') || args.includes('-h')) {
57
+ printHelp();
58
+ process.exit(0);
59
+ }
60
+
61
+ return options;
62
+ }
63
+
64
+ function printHumanReport(report: ReturnType<typeof runDoctor>): void {
65
+ logger.section('🩺 CursorFlow Doctor');
66
+ logger.info(`cwd: ${report.context.cwd}`);
67
+ if (report.context.repoRoot) logger.info(`repo: ${report.context.repoRoot}`);
68
+ if (report.context.tasksDir) logger.info(`tasks: ${report.context.tasksDir}`);
69
+
70
+ if (report.issues.length === 0) {
71
+ logger.success('All checks passed');
72
+ return;
73
+ }
74
+
75
+ for (const issue of report.issues) {
76
+ const header = `${issue.title} (${issue.id})`;
77
+ if (issue.severity === 'error') {
78
+ logger.error(header, '❌');
79
+ } else {
80
+ logger.warn(header, '⚠️');
81
+ }
82
+
83
+ console.log(` ${issue.message}`);
84
+
85
+ if (issue.details) {
86
+ console.log(` Details: ${issue.details}`);
87
+ }
88
+
89
+ if (issue.fixes && issue.fixes.length > 0) {
90
+ console.log(' Fix:');
91
+ for (const fix of issue.fixes) {
92
+ console.log(` - ${fix}`);
93
+ }
94
+ }
95
+
96
+ console.log('');
97
+ }
98
+
99
+ if (report.ok) {
100
+ logger.success('Doctor completed with warnings');
101
+ } else {
102
+ logger.error('Doctor found blocking issues');
103
+ }
104
+ }
105
+
106
+ async function doctor(args: string[]): Promise<void> {
107
+ const options = parseArgs(args);
108
+
109
+ const report = runDoctor({
110
+ cwd: process.cwd(),
111
+ tasksDir: options.tasksDir || undefined,
112
+ executor: options.executor || undefined,
113
+ includeCursorAgentChecks: options.includeCursorAgentChecks,
114
+ });
115
+
116
+ if (options.json) {
117
+ console.log(JSON.stringify(report, null, 2));
118
+ } else {
119
+ printHumanReport(report);
120
+ }
121
+
122
+ process.exit(report.ok ? 0 : 1);
123
+ }
124
+
125
+ export = doctor;
126
+
127
+
package/src/cli/index.ts CHANGED
@@ -14,6 +14,8 @@ const COMMANDS: Record<string, CommandFn> = {
14
14
  monitor: require('./monitor'),
15
15
  clean: require('./clean'),
16
16
  resume: require('./resume'),
17
+ doctor: require('./doctor'),
18
+ signal: require('./signal'),
17
19
  };
18
20
 
19
21
  function printHelp(): void {
@@ -28,6 +30,8 @@ Commands:
28
30
  monitor [run-dir] [options] Monitor lane execution
29
31
  clean <type> [options] Clean branches/worktrees/logs
30
32
  resume <lane> [options] Resume interrupted lane
33
+ doctor [options] Check environment and preflight
34
+ signal <lane> <msg> Directly intervene in a running lane
31
35
 
32
36
  Global Options:
33
37
  --config <path> Config file path
@@ -39,6 +43,7 @@ Examples:
39
43
  cursorflow run _cursorflow/tasks/MyFeature/
40
44
  cursorflow monitor --watch
41
45
  cursorflow clean branches --all
46
+ cursorflow doctor
42
47
 
43
48
  Documentation:
44
49
  https://github.com/eungjin-cigro/cursorflow#readme
package/src/cli/resume.ts CHANGED
@@ -1,37 +1,119 @@
1
1
  /**
2
- * CursorFlow resume command (stub)
2
+ * CursorFlow resume command
3
3
  */
4
4
 
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ import { spawn } from 'child_process';
5
8
  import * as logger from '../utils/logger';
9
+ import { loadConfig, getLogsDir } from '../utils/config';
10
+ import { loadState } from '../utils/state';
11
+ import { LaneState } from '../utils/types';
6
12
 
7
13
  interface ResumeOptions {
8
- lane?: string;
14
+ lane: string | null;
9
15
  runDir: string | null;
10
16
  clean: boolean;
11
17
  restart: boolean;
12
18
  }
13
19
 
14
20
  function parseArgs(args: string[]): ResumeOptions {
21
+ const runDirIdx = args.indexOf('--run-dir');
22
+
15
23
  return {
16
- lane: args[0],
17
- runDir: null,
24
+ lane: args.find(a => !a.startsWith('--')) || null,
25
+ runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
18
26
  clean: args.includes('--clean'),
19
27
  restart: args.includes('--restart'),
20
28
  };
21
29
  }
22
30
 
23
- async function resume(args: string[]): Promise<void> {
24
- logger.section('🔁 Resuming Lane');
31
+ /**
32
+ * Find the latest run directory
33
+ */
34
+ function findLatestRunDir(logsDir: string): string | null {
35
+ const runsDir = path.join(logsDir, 'runs');
36
+ if (!fs.existsSync(runsDir)) return null;
25
37
 
38
+ const runs = fs.readdirSync(runsDir)
39
+ .filter(d => d.startsWith('run-'))
40
+ .sort()
41
+ .reverse();
42
+
43
+ return runs.length > 0 ? path.join(runsDir, runs[0]!) : null;
44
+ }
45
+
46
+ async function resume(args: string[]): Promise<void> {
26
47
  const options = parseArgs(args);
48
+ const config = loadConfig();
49
+ const logsDir = getLogsDir(config);
50
+
51
+ if (!options.lane) {
52
+ throw new Error('Lane name required (e.g., cursorflow resume lane-1)');
53
+ }
54
+
55
+ let runDir = options.runDir;
56
+ if (!runDir) {
57
+ runDir = findLatestRunDir(logsDir);
58
+ }
59
+
60
+ if (!runDir || !fs.existsSync(runDir)) {
61
+ throw new Error(`Run directory not found: ${runDir || 'latest'}`);
62
+ }
63
+
64
+ const laneDir = path.join(runDir, 'lanes', options.lane);
65
+ const statePath = path.join(laneDir, 'state.json');
66
+
67
+ if (!fs.existsSync(statePath)) {
68
+ throw new Error(`Lane state not found at ${statePath}. Is the lane name correct?`);
69
+ }
70
+
71
+ const state = loadState<LaneState>(statePath);
72
+ if (!state) {
73
+ throw new Error(`Failed to load state from ${statePath}`);
74
+ }
75
+
76
+ if (!state.tasksFile || !fs.existsSync(state.tasksFile)) {
77
+ throw new Error(`Original tasks file not found: ${state.tasksFile}. Resume impossible without task definition.`);
78
+ }
79
+
80
+ logger.section(`🔁 Resuming Lane: ${options.lane}`);
81
+ logger.info(`Run: ${path.basename(runDir)}`);
82
+ logger.info(`Tasks: ${state.tasksFile}`);
83
+ logger.info(`Starting from task index: ${options.restart ? 0 : state.currentTaskIndex}`);
84
+
85
+ const runnerPath = require.resolve('../core/runner');
86
+ const runnerArgs = [
87
+ runnerPath,
88
+ state.tasksFile,
89
+ '--run-dir', laneDir,
90
+ '--start-index', options.restart ? '0' : String(state.currentTaskIndex),
91
+ ];
92
+
93
+ logger.info(`Spawning runner process...`);
27
94
 
28
- logger.info('This command will be fully implemented in the next phase');
29
- logger.info(`Lane: ${options.lane}`);
30
- logger.info(`Clean: ${options.clean}`);
31
- logger.info(`Restart: ${options.restart}`);
95
+ const child = spawn('node', runnerArgs, {
96
+ stdio: 'inherit',
97
+ env: process.env,
98
+ });
32
99
 
33
- logger.warn('\n⚠️ Implementation pending');
34
- logger.info('This will resume interrupted lanes');
100
+ return new Promise((resolve, reject) => {
101
+ child.on('exit', (code) => {
102
+ if (code === 0) {
103
+ logger.success(`Lane ${options.lane} completed successfully`);
104
+ resolve();
105
+ } else if (code === 2) {
106
+ logger.warn(`Lane ${options.lane} blocked on dependency change`);
107
+ resolve();
108
+ } else {
109
+ reject(new Error(`Lane ${options.lane} failed with exit code ${code}`));
110
+ }
111
+ });
112
+
113
+ child.on('error', (error) => {
114
+ reject(new Error(`Failed to start runner: ${error.message}`));
115
+ });
116
+ });
35
117
  }
36
118
 
37
119
  export = resume;
package/src/cli/run.ts CHANGED
@@ -6,12 +6,15 @@ import * as path from 'path';
6
6
  import * as fs from 'fs';
7
7
  import * as logger from '../utils/logger';
8
8
  import { orchestrate } from '../core/orchestrator';
9
- import { loadConfig } from '../utils/config';
9
+ import { getLogsDir, loadConfig } from '../utils/config';
10
+ import { runDoctor } from '../utils/doctor';
11
+ import { areCommandsInstalled, setupCommands } from './setup-commands';
10
12
 
11
13
  interface RunOptions {
12
14
  tasksDir?: string;
13
15
  dryRun: boolean;
14
16
  executor: string | null;
17
+ skipDoctor: boolean;
15
18
  }
16
19
 
17
20
  function parseArgs(args: string[]): RunOptions {
@@ -22,28 +25,80 @@ function parseArgs(args: string[]): RunOptions {
22
25
  tasksDir,
23
26
  dryRun: args.includes('--dry-run'),
24
27
  executor: executorIdx >= 0 ? args[executorIdx + 1] || null : null,
28
+ skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
25
29
  };
26
30
  }
27
31
 
28
32
  async function run(args: string[]): Promise<void> {
29
33
  const options = parseArgs(args);
30
34
 
35
+ // Auto-setup Cursor commands if missing or outdated
36
+ if (!areCommandsInstalled()) {
37
+ logger.info('Installing missing or outdated Cursor IDE commands...');
38
+ try {
39
+ setupCommands({ silent: true });
40
+ } catch (e) {
41
+ // Non-blocking
42
+ }
43
+ }
44
+
31
45
  if (!options.tasksDir) {
32
46
  console.log('\nUsage: cursorflow run <tasks-dir> [options]');
33
47
  throw new Error('Tasks directory required');
34
48
  }
35
49
 
36
- if (!fs.existsSync(options.tasksDir)) {
37
- throw new Error(`Tasks directory not found: ${options.tasksDir}`);
38
- }
39
-
40
50
  const config = loadConfig();
51
+ const logsDir = getLogsDir(config);
52
+
53
+ // Resolve tasks dir:
54
+ // - Prefer the exact path if it exists relative to cwd
55
+ // - Otherwise, fall back to projectRoot-relative path for better ergonomics
56
+ const tasksDir =
57
+ path.isAbsolute(options.tasksDir)
58
+ ? options.tasksDir
59
+ : (fs.existsSync(options.tasksDir)
60
+ ? path.resolve(process.cwd(), options.tasksDir)
61
+ : path.join(config.projectRoot, options.tasksDir));
62
+
63
+ if (!fs.existsSync(tasksDir)) {
64
+ throw new Error(`Tasks directory not found: ${tasksDir}`);
65
+ }
66
+
67
+ // Preflight checks (doctor)
68
+ if (!options.skipDoctor) {
69
+ const report = runDoctor({
70
+ cwd: process.cwd(),
71
+ tasksDir,
72
+ executor: options.executor || config.executor,
73
+ includeCursorAgentChecks: true,
74
+ });
75
+
76
+ if (!report.ok) {
77
+ logger.section('🛑 Pre-flight check failed');
78
+ for (const issue of report.issues) {
79
+ const header = `${issue.title} (${issue.id})`;
80
+ if (issue.severity === 'error') {
81
+ logger.error(header, '❌');
82
+ } else {
83
+ logger.warn(header, '⚠️');
84
+ }
85
+ console.log(` ${issue.message}`);
86
+ if (issue.details) console.log(` Details: ${issue.details}`);
87
+ if (issue.fixes?.length) {
88
+ console.log(' Fix:');
89
+ for (const fix of issue.fixes) console.log(` - ${fix}`);
90
+ }
91
+ console.log('');
92
+ }
93
+ throw new Error('Pre-flight checks failed. Run `cursorflow doctor` for details.');
94
+ }
95
+ }
41
96
 
42
97
  try {
43
- await orchestrate(options.tasksDir, {
98
+ await orchestrate(tasksDir, {
44
99
  executor: options.executor || config.executor,
45
100
  pollInterval: config.pollInterval * 1000,
46
- runDir: path.join(config.logsDir, 'runs', `run-${Date.now()}`),
101
+ runDir: path.join(logsDir, 'runs', `run-${Date.now()}`),
47
102
  });
48
103
  } catch (error: any) {
49
104
  // Re-throw to be handled by the main entry point
@@ -183,6 +183,25 @@ export function uninstallCommands(options: SetupOptions = {}): { removed: number
183
183
  return { removed };
184
184
  }
185
185
 
186
+ /**
187
+ * Check if commands are already installed
188
+ */
189
+ export function areCommandsInstalled(): boolean {
190
+ const projectRoot = findProjectRoot();
191
+ const targetDir = path.join(projectRoot, '.cursor', 'commands', 'cursorflow');
192
+ const sourceDir = getCommandsSourceDir();
193
+
194
+ if (!fs.existsSync(targetDir) || !fs.existsSync(sourceDir)) {
195
+ return false;
196
+ }
197
+
198
+ const sourceFiles = fs.readdirSync(sourceDir).filter(f => f.endsWith('.md'));
199
+ const targetFiles = fs.readdirSync(targetDir).filter(f => f.endsWith('.md'));
200
+
201
+ // Basic check: do we have all the files from source in target?
202
+ return sourceFiles.every(f => targetFiles.includes(f));
203
+ }
204
+
186
205
  async function main(args: string[]): Promise<any> {
187
206
  const options = parseArgs(args);
188
207
 
@@ -0,0 +1,89 @@
1
+ /**
2
+ * CursorFlow signal command
3
+ *
4
+ * Send a direct message to a running lane
5
+ */
6
+
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+ import * as logger from '../utils/logger';
10
+ import { loadConfig, getLogsDir } from '../utils/config';
11
+ import { appendLog, createConversationEntry } from '../utils/state';
12
+
13
+ interface SignalOptions {
14
+ lane: string | null;
15
+ message: string | null;
16
+ runDir: string | null;
17
+ }
18
+
19
+ function parseArgs(args: string[]): SignalOptions {
20
+ const runDirIdx = args.indexOf('--run-dir');
21
+
22
+ // First non-option is lane, second (or rest joined) is message
23
+ const nonOptions = args.filter(a => !a.startsWith('--'));
24
+
25
+ return {
26
+ lane: nonOptions[0] || null,
27
+ message: nonOptions.slice(1).join(' ') || null,
28
+ runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
29
+ };
30
+ }
31
+
32
+ function findLatestRunDir(logsDir: string): string | null {
33
+ const runsDir = path.join(logsDir, 'runs');
34
+ if (!fs.existsSync(runsDir)) return null;
35
+
36
+ const runs = fs.readdirSync(runsDir)
37
+ .filter(d => d.startsWith('run-'))
38
+ .sort()
39
+ .reverse();
40
+
41
+ return runs.length > 0 ? path.join(runsDir, runs[0]!) : null;
42
+ }
43
+
44
+ async function signal(args: string[]): Promise<void> {
45
+ const options = parseArgs(args);
46
+ const config = loadConfig();
47
+ const logsDir = getLogsDir(config);
48
+
49
+ if (!options.lane) {
50
+ throw new Error('Lane name required: cursorflow signal <lane> "<message>"');
51
+ }
52
+
53
+ if (!options.message) {
54
+ throw new Error('Message required: cursorflow signal <lane> "<message>"');
55
+ }
56
+
57
+ let runDir = options.runDir;
58
+ if (!runDir) {
59
+ runDir = findLatestRunDir(logsDir);
60
+ }
61
+
62
+ if (!runDir || !fs.existsSync(runDir)) {
63
+ throw new Error(`Run directory not found: ${runDir || 'latest'}`);
64
+ }
65
+
66
+ const convoPath = path.join(runDir, 'lanes', options.lane, 'conversation.jsonl');
67
+
68
+ if (!fs.existsSync(convoPath)) {
69
+ throw new Error(`Conversation log not found at ${convoPath}. Is the lane running?`);
70
+ }
71
+
72
+ logger.info(`Sending signal to lane: ${options.lane}`);
73
+ logger.info(`Message: "${options.message}"`);
74
+
75
+ // Append as a "commander" role message
76
+ // Note: We cast to 'system' or similar if 'commander' isn't in the enum,
77
+ // but let's use 'reviewer' or 'system' which agents usually respect,
78
+ // or update the type definition.
79
+ const entry = createConversationEntry('system', `[COMMANDER INTERVENTION]\n${options.message}`, {
80
+ task: 'DIRECT_SIGNAL'
81
+ });
82
+
83
+ appendLog(convoPath, entry);
84
+
85
+ logger.success('Signal sent successfully. The agent will see this message in its next turn or via file monitoring.');
86
+ }
87
+
88
+ export = signal;
89
+