@litmers/cursorflow-orchestrator 0.1.2 → 0.1.5

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 (74) hide show
  1. package/CHANGELOG.md +7 -6
  2. package/dist/cli/clean.d.ts +5 -0
  3. package/dist/cli/clean.js +57 -0
  4. package/dist/cli/clean.js.map +1 -0
  5. package/dist/cli/index.d.ts +6 -0
  6. package/dist/cli/index.js +120 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/cli/init.d.ts +7 -0
  9. package/dist/cli/init.js +302 -0
  10. package/dist/cli/init.js.map +1 -0
  11. package/dist/cli/monitor.d.ts +8 -0
  12. package/dist/cli/monitor.js +210 -0
  13. package/dist/cli/monitor.js.map +1 -0
  14. package/dist/cli/resume.d.ts +5 -0
  15. package/dist/cli/resume.js +58 -0
  16. package/dist/cli/resume.js.map +1 -0
  17. package/dist/cli/run.d.ts +5 -0
  18. package/dist/cli/run.js +74 -0
  19. package/dist/cli/run.js.map +1 -0
  20. package/dist/cli/setup-commands.d.ts +19 -0
  21. package/dist/cli/setup-commands.js +218 -0
  22. package/dist/cli/setup-commands.js.map +1 -0
  23. package/dist/core/orchestrator.d.ts +47 -0
  24. package/dist/core/orchestrator.js +192 -0
  25. package/dist/core/orchestrator.js.map +1 -0
  26. package/dist/core/reviewer.d.ts +60 -0
  27. package/dist/core/reviewer.js +239 -0
  28. package/dist/core/reviewer.js.map +1 -0
  29. package/dist/core/runner.d.ts +49 -0
  30. package/dist/core/runner.js +475 -0
  31. package/dist/core/runner.js.map +1 -0
  32. package/dist/utils/config.d.ts +31 -0
  33. package/dist/utils/config.js +198 -0
  34. package/dist/utils/config.js.map +1 -0
  35. package/dist/utils/cursor-agent.d.ts +61 -0
  36. package/dist/utils/cursor-agent.js +263 -0
  37. package/dist/utils/cursor-agent.js.map +1 -0
  38. package/dist/utils/git.d.ts +131 -0
  39. package/dist/utils/git.js +272 -0
  40. package/dist/utils/git.js.map +1 -0
  41. package/dist/utils/logger.d.ts +68 -0
  42. package/dist/utils/logger.js +158 -0
  43. package/dist/utils/logger.js.map +1 -0
  44. package/dist/utils/state.d.ts +65 -0
  45. package/dist/utils/state.js +216 -0
  46. package/dist/utils/state.js.map +1 -0
  47. package/dist/utils/types.d.ts +117 -0
  48. package/dist/utils/types.js +6 -0
  49. package/dist/utils/types.js.map +1 -0
  50. package/examples/README.md +155 -0
  51. package/examples/demo-project/README.md +262 -0
  52. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +18 -0
  53. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +18 -0
  54. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +109 -0
  55. package/package.json +71 -61
  56. package/scripts/ai-security-check.js +11 -4
  57. package/scripts/local-security-gate.sh +76 -0
  58. package/src/cli/{clean.js → clean.ts} +11 -5
  59. package/src/cli/{index.js → index.ts} +22 -16
  60. package/src/cli/{init.js → init.ts} +26 -18
  61. package/src/cli/{monitor.js → monitor.ts} +57 -44
  62. package/src/cli/{resume.js → resume.ts} +11 -5
  63. package/src/cli/run.ts +54 -0
  64. package/src/cli/{setup-commands.js → setup-commands.ts} +19 -18
  65. package/src/core/{orchestrator.js → orchestrator.ts} +44 -26
  66. package/src/core/{reviewer.js → reviewer.ts} +36 -29
  67. package/src/core/{runner.js → runner.ts} +78 -56
  68. package/src/utils/{config.js → config.ts} +17 -25
  69. package/src/utils/{cursor-agent.js → cursor-agent.ts} +38 -47
  70. package/src/utils/{git.js → git.ts} +70 -56
  71. package/src/utils/{logger.js → logger.ts} +170 -178
  72. package/src/utils/{state.js → state.ts} +30 -38
  73. package/src/utils/types.ts +133 -0
  74. 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
- };
@@ -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
- 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 { execSync, spawnSync } from 'child_process';
11
10
 
12
- const git = require('../utils/git');
13
- const logger = require('../utils/logger');
14
- const { ensureCursorAgent, checkCursorApiKey } = require('../utils/cursor-agent');
15
- const { saveState, loadState, appendLog, createConversationEntry, createGitLogEntry } = require('../utils/state');
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 out = execSync('cursor-agent create-chat', {
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].trim();
87
- if (line.startsWith('{') && line.endsWith('}')) {
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
- const { spawnSync } = require('child_process');
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,9 +371,7 @@ async function runTask({
348
371
  /**
349
372
  * Run all tasks in sequence
350
373
  */
351
- async function runTasks(config, runDir) {
352
- const { checkCursorAuth, printAuthHelp } = require('../utils/cursor-agent');
353
-
374
+ export async function runTasks(config: RunnerConfig, runDir: string): Promise<TaskExecutionResult[]> {
354
375
  // Ensure cursor-agent is installed
355
376
  ensureCursorAgent();
356
377
 
@@ -379,7 +400,7 @@ async function runTasks(config, runDir) {
379
400
  logger.success('✓ Cursor authentication OK');
380
401
 
381
402
  const repoRoot = git.getRepoRoot();
382
- const pipelineBranch = config.pipelineBranch || `${config.branchPrefix}${Date.now().toString(36)}`;
403
+ const pipelineBranch = config.pipelineBranch || `${config.branchPrefix || 'cursorflow/'}${Date.now().toString(36)}`;
383
404
  const worktreeDir = path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees', pipelineBranch);
384
405
 
385
406
  logger.section('🚀 Starting Pipeline');
@@ -398,22 +419,26 @@ async function runTasks(config, runDir) {
398
419
  const chatId = cursorAgentCreateChat();
399
420
 
400
421
  // Save initial state
401
- const state = {
422
+ const state: LaneState = {
402
423
  status: 'running',
403
424
  pipelineBranch,
404
425
  worktreeDir,
405
- chatId,
406
426
  totalTasks: config.tasks.length,
407
427
  currentTaskIndex: 0,
428
+ label: pipelineBranch,
429
+ startTime: Date.now(),
430
+ endTime: null,
431
+ error: null,
432
+ dependencyRequest: null,
408
433
  };
409
434
 
410
435
  saveState(path.join(runDir, 'state.json'), state);
411
436
 
412
437
  // Run tasks
413
- const results = [];
438
+ const results: TaskExecutionResult[] = [];
414
439
 
415
440
  for (let i = 0; i < config.tasks.length; i++) {
416
- const task = config.tasks[i];
441
+ const task = config.tasks[i]!;
417
442
  const taskBranch = `${pipelineBranch}--${String(i + 1).padStart(2, '0')}-${task.name}`;
418
443
 
419
444
  const result = await runTask({
@@ -435,8 +460,8 @@ async function runTasks(config, runDir) {
435
460
 
436
461
  // Handle blocked or error
437
462
  if (result.status === 'BLOCKED_DEPENDENCY') {
438
- state.status = 'blocked_dependency';
439
- state.dependencyRequest = result.dependencyRequest;
463
+ state.status = 'failed'; // Or blocked if we had a blocked status in LaneState
464
+ state.dependencyRequest = result.dependencyRequest || null;
440
465
  saveState(path.join(runDir, 'state.json'), state);
441
466
  logger.warn('Task blocked on dependency change');
442
467
  process.exit(2);
@@ -444,6 +469,7 @@ async function runTasks(config, runDir) {
444
469
 
445
470
  if (result.status !== 'FINISHED') {
446
471
  state.status = 'failed';
472
+ state.error = result.error || 'Unknown error';
447
473
  saveState(path.join(runDir, 'state.json'), state);
448
474
  logger.error(`Task failed: ${result.error}`);
449
475
  process.exit(1);
@@ -457,17 +483,13 @@ async function runTasks(config, runDir) {
457
483
 
458
484
  // Complete
459
485
  state.status = 'completed';
486
+ state.endTime = Date.now();
460
487
  saveState(path.join(runDir, 'state.json'), state);
461
488
 
462
489
  logger.success('All tasks completed!');
463
490
  return results;
464
491
  }
465
492
 
466
- module.exports = {
467
- runTasks,
468
- runTask,
469
- };
470
-
471
493
  /**
472
494
  * CLI entry point
473
495
  */
@@ -479,12 +501,12 @@ if (require.main === module) {
479
501
  process.exit(1);
480
502
  }
481
503
 
482
- const tasksFile = args[0];
504
+ const tasksFile = args[0]!;
483
505
  const runDirIdx = args.indexOf('--run-dir');
484
- const executorIdx = args.indexOf('--executor');
506
+ // const executorIdx = args.indexOf('--executor');
485
507
 
486
- const runDir = runDirIdx >= 0 ? args[runDirIdx + 1] : '.';
487
- const executor = executorIdx >= 0 ? args[executorIdx + 1] : 'cursor-agent';
508
+ const runDir = runDirIdx >= 0 ? args[runDirIdx + 1]! : '.';
509
+ // const executor = executorIdx >= 0 ? args[executorIdx + 1] : 'cursor-agent';
488
510
 
489
511
  if (!fs.existsSync(tasksFile)) {
490
512
  console.error(`Tasks file not found: ${tasksFile}`);
@@ -492,10 +514,10 @@ if (require.main === module) {
492
514
  }
493
515
 
494
516
  // Load tasks configuration
495
- let config;
517
+ let config: RunnerConfig;
496
518
  try {
497
- config = JSON.parse(fs.readFileSync(tasksFile, 'utf8'));
498
- } catch (error) {
519
+ config = JSON.parse(fs.readFileSync(tasksFile, 'utf8')) as RunnerConfig;
520
+ } catch (error: any) {
499
521
  console.error(`Failed to load tasks file: ${error.message}`);
500
522
  process.exit(1);
501
523
  }
@@ -513,7 +535,7 @@ if (require.main === module) {
513
535
  })
514
536
  .catch(error => {
515
537
  console.error(`Runner failed: ${error.message}`);
516
- if (process.env.DEBUG) {
538
+ if (process.env['DEBUG']) {
517
539
  console.error(error.stack);
518
540
  }
519
541
  process.exit(1);