@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +17 -7
  2. package/README.md +33 -2
  3. package/commands/cursorflow-doctor.md +24 -0
  4. package/commands/cursorflow-signal.md +19 -0
  5. package/dist/cli/clean.d.ts +5 -0
  6. package/dist/cli/clean.js +57 -0
  7. package/dist/cli/clean.js.map +1 -0
  8. package/dist/cli/doctor.d.ts +15 -0
  9. package/dist/cli/doctor.js +139 -0
  10. package/dist/cli/doctor.js.map +1 -0
  11. package/dist/cli/index.d.ts +6 -0
  12. package/dist/cli/index.js +125 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/init.d.ts +7 -0
  15. package/dist/cli/init.js +302 -0
  16. package/dist/cli/init.js.map +1 -0
  17. package/dist/cli/monitor.d.ts +8 -0
  18. package/dist/cli/monitor.js +210 -0
  19. package/dist/cli/monitor.js.map +1 -0
  20. package/dist/cli/resume.d.ts +5 -0
  21. package/dist/cli/resume.js +128 -0
  22. package/dist/cli/resume.js.map +1 -0
  23. package/dist/cli/run.d.ts +5 -0
  24. package/dist/cli/run.js +128 -0
  25. package/dist/cli/run.js.map +1 -0
  26. package/dist/cli/setup-commands.d.ts +23 -0
  27. package/dist/cli/setup-commands.js +234 -0
  28. package/dist/cli/setup-commands.js.map +1 -0
  29. package/dist/cli/signal.d.ts +7 -0
  30. package/dist/cli/signal.js +99 -0
  31. package/dist/cli/signal.js.map +1 -0
  32. package/dist/core/orchestrator.d.ts +47 -0
  33. package/dist/core/orchestrator.js +192 -0
  34. package/dist/core/orchestrator.js.map +1 -0
  35. package/dist/core/reviewer.d.ts +60 -0
  36. package/dist/core/reviewer.js +239 -0
  37. package/dist/core/reviewer.js.map +1 -0
  38. package/dist/core/runner.d.ts +51 -0
  39. package/dist/core/runner.js +499 -0
  40. package/dist/core/runner.js.map +1 -0
  41. package/dist/utils/config.d.ts +31 -0
  42. package/dist/utils/config.js +198 -0
  43. package/dist/utils/config.js.map +1 -0
  44. package/dist/utils/cursor-agent.d.ts +61 -0
  45. package/dist/utils/cursor-agent.js +263 -0
  46. package/dist/utils/cursor-agent.js.map +1 -0
  47. package/dist/utils/doctor.d.ts +63 -0
  48. package/dist/utils/doctor.js +280 -0
  49. package/dist/utils/doctor.js.map +1 -0
  50. package/dist/utils/git.d.ts +131 -0
  51. package/dist/utils/git.js +272 -0
  52. package/dist/utils/git.js.map +1 -0
  53. package/dist/utils/logger.d.ts +68 -0
  54. package/dist/utils/logger.js +158 -0
  55. package/dist/utils/logger.js.map +1 -0
  56. package/dist/utils/state.d.ts +65 -0
  57. package/dist/utils/state.js +216 -0
  58. package/dist/utils/state.js.map +1 -0
  59. package/dist/utils/types.d.ts +118 -0
  60. package/dist/utils/types.js +6 -0
  61. package/dist/utils/types.js.map +1 -0
  62. package/examples/README.md +155 -0
  63. package/examples/demo-project/README.md +262 -0
  64. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +18 -0
  65. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +18 -0
  66. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +109 -0
  67. package/package.json +71 -61
  68. package/scripts/ai-security-check.js +11 -4
  69. package/scripts/local-security-gate.sh +76 -0
  70. package/src/cli/{clean.js → clean.ts} +11 -5
  71. package/src/cli/doctor.ts +127 -0
  72. package/src/cli/{index.js → index.ts} +27 -16
  73. package/src/cli/{init.js → init.ts} +26 -18
  74. package/src/cli/{monitor.js → monitor.ts} +57 -44
  75. package/src/cli/resume.ts +119 -0
  76. package/src/cli/run.ts +109 -0
  77. package/src/cli/{setup-commands.js → setup-commands.ts} +38 -18
  78. package/src/cli/signal.ts +89 -0
  79. package/src/core/{orchestrator.js → orchestrator.ts} +44 -26
  80. package/src/core/{reviewer.js → reviewer.ts} +36 -29
  81. package/src/core/{runner.js → runner.ts} +125 -76
  82. package/src/utils/{config.js → config.ts} +17 -25
  83. package/src/utils/{cursor-agent.js → cursor-agent.ts} +38 -47
  84. package/src/utils/doctor.ts +312 -0
  85. package/src/utils/{git.js → git.ts} +70 -56
  86. package/src/utils/{logger.js → logger.ts} +170 -178
  87. package/src/utils/{state.js → state.ts} +30 -38
  88. package/src/utils/types.ts +134 -0
  89. package/src/cli/resume.js +0 -31
  90. package/src/cli/run.js +0 -51
@@ -1,28 +1,45 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * Orchestrator - Parallel lane execution with dependency management
4
3
  *
5
4
  * Adapted from admin-domains-orchestrator.js
6
5
  */
7
6
 
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { spawn } = require('child_process');
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { spawn, ChildProcess } from 'child_process';
11
10
 
12
- const logger = require('../utils/logger');
13
- const { loadState, saveState } = require('../utils/state');
14
- const { runTasks } = require('./runner');
11
+ import * as logger from '../utils/logger';
12
+ import { loadState } from '../utils/state';
13
+ import { LaneState } from '../utils/types';
14
+
15
+ export interface LaneInfo {
16
+ name: string;
17
+ path: string;
18
+ }
19
+
20
+ export interface SpawnLaneResult {
21
+ child: ChildProcess;
22
+ logPath: string;
23
+ }
15
24
 
16
25
  /**
17
26
  * Spawn a lane process
18
27
  */
19
- function spawnLane({ laneName, tasksFile, laneRunDir, executor }) {
28
+ export function spawnLane({ tasksFile, laneRunDir, executor }: {
29
+ laneName: string;
30
+ tasksFile: string;
31
+ laneRunDir: string;
32
+ executor: string;
33
+ }): SpawnLaneResult {
20
34
  fs.mkdirSync(laneRunDir, { recursive: true});
21
35
  const logPath = path.join(laneRunDir, 'terminal.log');
22
36
  const logFd = fs.openSync(logPath, 'a');
23
37
 
38
+ // Use extension-less resolve to handle both .ts (dev) and .js (dist)
39
+ const runnerPath = require.resolve('./runner');
40
+
24
41
  const args = [
25
- require.resolve('./runner.js'),
42
+ runnerPath,
26
43
  tasksFile,
27
44
  '--run-dir', laneRunDir,
28
45
  '--executor', executor,
@@ -46,7 +63,7 @@ function spawnLane({ laneName, tasksFile, laneRunDir, executor }) {
46
63
  /**
47
64
  * Wait for child process to exit
48
65
  */
49
- function waitChild(proc) {
66
+ export function waitChild(proc: ChildProcess): Promise<number> {
50
67
  return new Promise((resolve) => {
51
68
  if (proc.exitCode !== null) {
52
69
  resolve(proc.exitCode);
@@ -61,7 +78,7 @@ function waitChild(proc) {
61
78
  /**
62
79
  * List lane task files in directory
63
80
  */
64
- function listLaneFiles(tasksDir) {
81
+ export function listLaneFiles(tasksDir: string): LaneInfo[] {
65
82
  if (!fs.existsSync(tasksDir)) {
66
83
  return [];
67
84
  }
@@ -79,16 +96,19 @@ function listLaneFiles(tasksDir) {
79
96
  /**
80
97
  * Monitor lane states
81
98
  */
82
- function printLaneStatus(lanes, laneRunDirs) {
99
+ export function printLaneStatus(lanes: LaneInfo[], laneRunDirs: Record<string, string>): void {
83
100
  const rows = lanes.map(lane => {
84
- const statePath = path.join(laneRunDirs[lane.name], 'state.json');
85
- const state = loadState(statePath);
101
+ const dir = laneRunDirs[lane.name];
102
+ if (!dir) return { lane: lane.name, status: '(unknown)', task: '-' };
103
+
104
+ const statePath = path.join(dir, 'state.json');
105
+ const state = loadState<LaneState>(statePath);
86
106
 
87
107
  if (!state) {
88
108
  return { lane: lane.name, status: '(no state)', task: '-' };
89
109
  }
90
110
 
91
- const idx = state.currentTaskIndex + 1;
111
+ const idx = (state.currentTaskIndex || 0) + 1;
92
112
  return {
93
113
  lane: lane.name,
94
114
  status: state.status || 'unknown',
@@ -105,7 +125,11 @@ function printLaneStatus(lanes, laneRunDirs) {
105
125
  /**
106
126
  * Run orchestration
107
127
  */
108
- async function orchestrate(tasksDir, options = {}) {
128
+ export async function orchestrate(tasksDir: string, options: {
129
+ runDir?: string;
130
+ executor?: string;
131
+ pollInterval?: number;
132
+ } = {}): Promise<{ lanes: LaneInfo[]; exitCodes: Record<string, number>; runRoot: string }> {
109
133
  const lanes = listLaneFiles(tasksDir);
110
134
 
111
135
  if (lanes.length === 0) {
@@ -115,7 +139,7 @@ async function orchestrate(tasksDir, options = {}) {
115
139
  const runRoot = options.runDir || `_cursorflow/logs/runs/run-${Date.now()}`;
116
140
  fs.mkdirSync(runRoot, { recursive: true });
117
141
 
118
- const laneRunDirs = {};
142
+ const laneRunDirs: Record<string, string> = {};
119
143
  for (const lane of lanes) {
120
144
  laneRunDirs[lane.name] = path.join(runRoot, 'lanes', lane.name);
121
145
  }
@@ -126,13 +150,13 @@ async function orchestrate(tasksDir, options = {}) {
126
150
  logger.info(`Lanes: ${lanes.length}`);
127
151
 
128
152
  // Spawn all lanes
129
- const running = [];
153
+ const running: { lane: string; child: ChildProcess; logPath: string }[] = [];
130
154
 
131
155
  for (const lane of lanes) {
132
156
  const { child, logPath } = spawnLane({
133
157
  laneName: lane.name,
134
158
  tasksFile: lane.path,
135
- laneRunDir: laneRunDirs[lane.name],
159
+ laneRunDir: laneRunDirs[lane.name]!,
136
160
  executor: options.executor || 'cursor-agent',
137
161
  });
138
162
 
@@ -146,7 +170,7 @@ async function orchestrate(tasksDir, options = {}) {
146
170
  }, options.pollInterval || 60000);
147
171
 
148
172
  // Wait for all lanes
149
- const exitCodes = {};
173
+ const exitCodes: Record<string, number> = {};
150
174
 
151
175
  for (const r of running) {
152
176
  exitCodes[r.lane] = await waitChild(r.child);
@@ -177,9 +201,3 @@ async function orchestrate(tasksDir, options = {}) {
177
201
  logger.success('All lanes completed successfully!');
178
202
  return { lanes, exitCodes, runRoot };
179
203
  }
180
-
181
- module.exports = {
182
- orchestrate,
183
- spawnLane,
184
- listLaneFiles,
185
- };
@@ -1,18 +1,18 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * Reviewer - Code review agent
4
3
  *
5
4
  * Adapted from reviewer-agent.js
6
5
  */
7
6
 
8
- const logger = require('../utils/logger');
9
- const { appendLog, createConversationEntry } = require('../utils/state');
10
- const path = require('path');
7
+ import * as logger from '../utils/logger';
8
+ import { appendLog, createConversationEntry } from '../utils/state';
9
+ import * as path from 'path';
10
+ import { ReviewResult, ReviewIssue, TaskResult, RunnerConfig, AgentSendResult } from '../utils/types';
11
11
 
12
12
  /**
13
13
  * Build review prompt
14
14
  */
15
- function buildReviewPrompt({ taskName, taskBranch, acceptanceCriteria = [] }) {
15
+ export function buildReviewPrompt({ taskName, taskBranch, acceptanceCriteria = [] }: { taskName: string; taskBranch: string; acceptanceCriteria?: string[] }): string {
16
16
  const criteriaList = acceptanceCriteria.length > 0
17
17
  ? acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join('\n')
18
18
  : 'Work should be completed properly.';
@@ -61,29 +61,29 @@ IMPORTANT: You MUST respond in the exact JSON format above. "status" must be eit
61
61
  /**
62
62
  * Parse review result
63
63
  */
64
- function parseReviewResult(text) {
64
+ export function parseReviewResult(text: string): ReviewResult {
65
65
  const t = String(text || '');
66
66
 
67
67
  // Try JSON block
68
68
  const jsonMatch = t.match(/```json\n([\s\S]*?)\n```/);
69
69
  if (jsonMatch) {
70
70
  try {
71
- const parsed = JSON.parse(jsonMatch[1]);
71
+ const parsed = JSON.parse(jsonMatch[1]!);
72
72
  return {
73
73
  status: parsed.status || 'needs_changes',
74
74
  buildSuccess: parsed.buildSuccess !== false,
75
- issues: Array.isArray(parsed.issues) ? parsed.issues : [],
76
- suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions : [],
75
+ issues: Array.isArray(parsed.issues) ? (parsed.issues as ReviewIssue[]) : [],
76
+ suggestions: Array.isArray(parsed.suggestions) ? (parsed.suggestions as string[]) : [],
77
77
  summary: parsed.summary || '',
78
78
  raw: t,
79
79
  };
80
- } catch (err) {
80
+ } catch (err: any) {
81
81
  logger.warn(`JSON parse failed: ${err.message}`);
82
82
  }
83
83
  }
84
84
 
85
85
  // Fallback parsing
86
- const hasApproved = t.toLowerCase().includes('"status": "approved"');
86
+ const hasApproved = t.toLowerCase().includes('approved');
87
87
  const hasIssues = t.toLowerCase().includes('needs_changes') ||
88
88
  t.toLowerCase().includes('error') ||
89
89
  t.toLowerCase().includes('failed');
@@ -101,8 +101,8 @@ function parseReviewResult(text) {
101
101
  /**
102
102
  * Build feedback prompt
103
103
  */
104
- function buildFeedbackPrompt(review) {
105
- const lines = [];
104
+ export function buildFeedbackPrompt(review: ReviewResult): string {
105
+ const lines: string[] = [];
106
106
  lines.push('# Code Review Feedback');
107
107
  lines.push('');
108
108
  lines.push('The reviewer found the following issues. Please fix them:');
@@ -110,7 +110,7 @@ function buildFeedbackPrompt(review) {
110
110
 
111
111
  if (!review.buildSuccess) {
112
112
  lines.push('## CRITICAL: Build Failed');
113
- lines.push('- `pnpm build` failed. Fix build errors first.');
113
+ lines.push('- \`pnpm build\` failed. Fix build errors first.');
114
114
  lines.push('');
115
115
  }
116
116
 
@@ -132,7 +132,7 @@ function buildFeedbackPrompt(review) {
132
132
 
133
133
  lines.push('## Requirements');
134
134
  lines.push('1. Fix all issues listed above');
135
- lines.push('2. Ensure `pnpm build` succeeds');
135
+ lines.push('2. Ensure \`pnpm build\` succeeds');
136
136
  lines.push('3. Commit and push your changes');
137
137
  lines.push('');
138
138
  lines.push('**Let me know when fixes are complete.**');
@@ -143,7 +143,14 @@ function buildFeedbackPrompt(review) {
143
143
  /**
144
144
  * Review task
145
145
  */
146
- async function reviewTask({ taskResult, worktreeDir, runDir, config, cursorAgentSend, cursorAgentCreateChat }) {
146
+ export async function reviewTask({ taskResult, worktreeDir, runDir, config, cursorAgentSend, cursorAgentCreateChat }: {
147
+ taskResult: TaskResult;
148
+ worktreeDir: string;
149
+ runDir: string;
150
+ config: RunnerConfig;
151
+ cursorAgentSend: (options: { workspaceDir: string; chatId: string; prompt: string; model?: string }) => AgentSendResult;
152
+ cursorAgentCreateChat: () => string;
153
+ }): Promise<ReviewResult> {
147
154
  const reviewPrompt = buildReviewPrompt({
148
155
  taskName: taskResult.taskName,
149
156
  taskBranch: taskResult.taskBranch,
@@ -160,11 +167,11 @@ async function reviewTask({ taskResult, worktreeDir, runDir, config, cursorAgent
160
167
  model: config.reviewModel || 'sonnet-4.5-thinking',
161
168
  });
162
169
 
163
- const review = parseReviewResult(reviewResult.resultText);
170
+ const review = parseReviewResult(reviewResult.resultText || '');
164
171
 
165
172
  // Log review
166
173
  const convoPath = path.join(runDir, 'conversation.jsonl');
167
- appendLog(convoPath, createConversationEntry('reviewer', reviewResult.resultText, {
174
+ appendLog(convoPath, createConversationEntry('reviewer', reviewResult.resultText || 'No result', {
168
175
  task: taskResult.taskName,
169
176
  model: config.reviewModel,
170
177
  }));
@@ -177,10 +184,18 @@ async function reviewTask({ taskResult, worktreeDir, runDir, config, cursorAgent
177
184
  /**
178
185
  * Review loop with feedback
179
186
  */
180
- async function runReviewLoop({ taskResult, worktreeDir, runDir, config, workChatId, cursorAgentSend, cursorAgentCreateChat }) {
187
+ export async function runReviewLoop({ taskResult, worktreeDir, runDir, config, workChatId, cursorAgentSend, cursorAgentCreateChat }: {
188
+ taskResult: TaskResult;
189
+ worktreeDir: string;
190
+ runDir: string;
191
+ config: RunnerConfig;
192
+ workChatId: string;
193
+ cursorAgentSend: (options: { workspaceDir: string; chatId: string; prompt: string; model?: string }) => AgentSendResult;
194
+ cursorAgentCreateChat: () => string;
195
+ }): Promise<{ approved: boolean; review: ReviewResult; iterations: number; error?: string }> {
181
196
  const maxIterations = config.maxReviewIterations || 3;
182
197
  let iteration = 0;
183
- let currentReview = null;
198
+ let currentReview: ReviewResult | null = null;
184
199
 
185
200
  while (iteration < maxIterations) {
186
201
  currentReview = await reviewTask({
@@ -221,13 +236,5 @@ async function runReviewLoop({ taskResult, worktreeDir, runDir, config, workChat
221
236
  }
222
237
  }
223
238
 
224
- return { approved: false, review: currentReview, iterations: iteration };
239
+ return { approved: false, review: currentReview!, iterations: iteration };
225
240
  }
226
-
227
- module.exports = {
228
- buildReviewPrompt,
229
- parseReviewResult,
230
- buildFeedbackPrompt,
231
- reviewTask,
232
- runReviewLoop,
233
- };