@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,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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
85
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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('
|
|
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('-
|
|
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
|
|
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
|
|
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
|
-
};
|