@litmers/cursorflow-orchestrator 0.1.9 → 0.1.12
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 +24 -0
- package/README.md +90 -72
- package/commands/cursorflow-clean.md +24 -135
- package/commands/cursorflow-doctor.md +66 -38
- package/commands/cursorflow-init.md +33 -50
- package/commands/cursorflow-models.md +51 -0
- package/commands/cursorflow-monitor.md +52 -72
- package/commands/cursorflow-prepare.md +426 -147
- package/commands/cursorflow-resume.md +51 -159
- package/commands/cursorflow-review.md +38 -202
- package/commands/cursorflow-run.md +197 -84
- package/commands/cursorflow-signal.md +27 -72
- package/dist/cli/clean.js +23 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/doctor.js +14 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +14 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +5 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/models.d.ts +7 -0
- package/dist/cli/models.js +104 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/monitor.js +17 -0
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.d.ts +7 -0
- package/dist/cli/prepare.js +748 -0
- package/dist/cli/prepare.js.map +1 -0
- package/dist/cli/resume.js +56 -0
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +30 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.js +18 -0
- package/dist/cli/signal.js.map +1 -1
- package/dist/utils/cursor-agent.d.ts +4 -0
- package/dist/utils/cursor-agent.js +58 -10
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/doctor.d.ts +10 -0
- package/dist/utils/doctor.js +581 -1
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/types.d.ts +2 -0
- package/examples/README.md +114 -59
- package/examples/demo-project/README.md +61 -79
- package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +17 -6
- package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +17 -6
- package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +66 -25
- package/package.json +1 -1
- package/src/cli/clean.ts +27 -0
- package/src/cli/doctor.ts +18 -2
- package/src/cli/index.ts +15 -3
- package/src/cli/init.ts +6 -4
- package/src/cli/models.ts +83 -0
- package/src/cli/monitor.ts +20 -0
- package/src/cli/prepare.ts +844 -0
- package/src/cli/resume.ts +66 -0
- package/src/cli/run.ts +36 -2
- package/src/cli/signal.ts +22 -0
- package/src/utils/cursor-agent.ts +62 -10
- package/src/utils/doctor.ts +633 -5
- package/src/utils/types.ts +2 -0
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CursorFlow prepare command
|
|
4
|
+
*
|
|
5
|
+
* Prepare task files for a new feature - Terminal-first approach
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const logger = __importStar(require("../utils/logger"));
|
|
43
|
+
const config_1 = require("../utils/config");
|
|
44
|
+
function printHelp() {
|
|
45
|
+
console.log(`
|
|
46
|
+
Usage: cursorflow prepare <feature-name> [options]
|
|
47
|
+
|
|
48
|
+
Prepare task files for a new feature - Terminal-first workflow.
|
|
49
|
+
|
|
50
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
51
|
+
WORKFLOW: Requirements → Lanes → Tasks → Validate → Run
|
|
52
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
53
|
+
|
|
54
|
+
## Step 1: Create Initial Lanes (with Presets)
|
|
55
|
+
|
|
56
|
+
# Complex implementation: plan → implement → test
|
|
57
|
+
cursorflow prepare FeatureName --preset complex --prompt "Implement user auth"
|
|
58
|
+
|
|
59
|
+
# Simple implementation: implement → test
|
|
60
|
+
cursorflow prepare BugFix --preset simple --prompt "Fix login bug"
|
|
61
|
+
|
|
62
|
+
# Merge lane: merge → test (for lanes with dependencies)
|
|
63
|
+
cursorflow prepare Integration --preset merge --depends-on "01-lane-1,02-lane-2"
|
|
64
|
+
|
|
65
|
+
# Multiple sequential lanes (auto-detects merge preset for dependent lanes)
|
|
66
|
+
cursorflow prepare FullStack --lanes 3 --sequential --prompt "Build your layer"
|
|
67
|
+
|
|
68
|
+
## Step 2: Add More Lanes (Incremental)
|
|
69
|
+
|
|
70
|
+
# Add a merge lane to existing task directory
|
|
71
|
+
cursorflow prepare --add-lane _cursorflow/tasks/2412211530_FullStack \\
|
|
72
|
+
--preset merge --depends-on "01-lane-1,02-lane-2"
|
|
73
|
+
|
|
74
|
+
## Step 3: Add More Tasks to a Lane
|
|
75
|
+
|
|
76
|
+
# Append a task to an existing lane
|
|
77
|
+
cursorflow prepare --add-task _cursorflow/tasks/2412211530_FullStack/01-lane-1.json \\
|
|
78
|
+
--task "verify|sonnet-4.5|Double-check all requirements|All criteria met"
|
|
79
|
+
|
|
80
|
+
## Step 4: Validate Configuration
|
|
81
|
+
|
|
82
|
+
cursorflow doctor --tasks-dir _cursorflow/tasks/2412211530_FullStack
|
|
83
|
+
|
|
84
|
+
## Step 5: Run
|
|
85
|
+
|
|
86
|
+
cursorflow run _cursorflow/tasks/2412211530_FullStack
|
|
87
|
+
|
|
88
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
89
|
+
|
|
90
|
+
## Preset Templates
|
|
91
|
+
|
|
92
|
+
--preset complex plan → implement → test (for complex features)
|
|
93
|
+
--preset simple implement → test (for simple changes)
|
|
94
|
+
--preset merge merge → test (auto-applied when --depends-on is set)
|
|
95
|
+
|
|
96
|
+
## Options
|
|
97
|
+
|
|
98
|
+
Core:
|
|
99
|
+
<feature-name> Name of the feature (for new task directories)
|
|
100
|
+
--lanes <num> Number of lanes to create (default: 1)
|
|
101
|
+
--preset <type> Use preset template: complex | simple | merge
|
|
102
|
+
|
|
103
|
+
Task Definition:
|
|
104
|
+
--prompt <text> Task prompt (uses preset or single task)
|
|
105
|
+
--criteria <list> Comma-separated acceptance criteria
|
|
106
|
+
--model <model> Model to use (default: sonnet-4.5)
|
|
107
|
+
--task <spec> Full task spec: "name|model|prompt|criteria" (repeatable)
|
|
108
|
+
|
|
109
|
+
Dependencies:
|
|
110
|
+
--sequential Chain lanes: 1 → 2 → 3
|
|
111
|
+
--deps <spec> Custom dependencies: "2:1;3:1,2"
|
|
112
|
+
--depends-on <lanes> Dependencies for --add-lane: "01-lane-1,02-lane-2"
|
|
113
|
+
|
|
114
|
+
Incremental (add to existing):
|
|
115
|
+
--add-lane <dir> Add a new lane to existing task directory
|
|
116
|
+
--add-task <file> Append task(s) to existing lane JSON file
|
|
117
|
+
|
|
118
|
+
Advanced:
|
|
119
|
+
--template <path> Custom template JSON file
|
|
120
|
+
--force Overwrite existing files
|
|
121
|
+
|
|
122
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
# 1. Complex feature with multiple lanes
|
|
127
|
+
cursorflow prepare AuthSystem --lanes 3 --sequential --preset complex \\
|
|
128
|
+
--prompt "Implement authentication for your layer"
|
|
129
|
+
|
|
130
|
+
# 2. Simple bug fix
|
|
131
|
+
cursorflow prepare FixLoginBug --preset simple \\
|
|
132
|
+
--prompt "Fix the login validation bug in auth.ts"
|
|
133
|
+
|
|
134
|
+
# 3. Add a merge/integration lane
|
|
135
|
+
cursorflow prepare --add-lane _cursorflow/tasks/2412211530_AuthSystem \\
|
|
136
|
+
--preset merge --depends-on "01-lane-1,02-lane-2"
|
|
137
|
+
|
|
138
|
+
# 4. Custom multi-task lane (overrides preset)
|
|
139
|
+
cursorflow prepare ComplexFeature \\
|
|
140
|
+
--task "plan|sonnet-4.5-thinking|Create implementation plan|Plan documented" \\
|
|
141
|
+
--task "implement|sonnet-4.5|Build the feature|Code complete" \\
|
|
142
|
+
--task "test|sonnet-4.5|Write comprehensive tests|Tests pass"
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
function parseArgs(args) {
|
|
146
|
+
const result = {
|
|
147
|
+
featureName: '',
|
|
148
|
+
lanes: 1,
|
|
149
|
+
template: null,
|
|
150
|
+
preset: null,
|
|
151
|
+
sequential: false,
|
|
152
|
+
deps: null,
|
|
153
|
+
prompt: null,
|
|
154
|
+
criteria: [],
|
|
155
|
+
model: null,
|
|
156
|
+
taskSpecs: [],
|
|
157
|
+
addLane: null,
|
|
158
|
+
addTask: null,
|
|
159
|
+
dependsOnLanes: [],
|
|
160
|
+
force: false,
|
|
161
|
+
help: false,
|
|
162
|
+
};
|
|
163
|
+
let i = 0;
|
|
164
|
+
while (i < args.length) {
|
|
165
|
+
const arg = args[i];
|
|
166
|
+
if (arg === '--help' || arg === '-h') {
|
|
167
|
+
result.help = true;
|
|
168
|
+
}
|
|
169
|
+
else if (arg === '--force') {
|
|
170
|
+
result.force = true;
|
|
171
|
+
}
|
|
172
|
+
else if (arg === '--sequential') {
|
|
173
|
+
result.sequential = true;
|
|
174
|
+
}
|
|
175
|
+
else if (arg === '--lanes' && args[i + 1]) {
|
|
176
|
+
result.lanes = parseInt(args[++i]) || 1;
|
|
177
|
+
}
|
|
178
|
+
else if (arg === '--template' && args[i + 1]) {
|
|
179
|
+
result.template = args[++i];
|
|
180
|
+
}
|
|
181
|
+
else if (arg === '--preset' && args[i + 1]) {
|
|
182
|
+
const presetValue = args[++i].toLowerCase();
|
|
183
|
+
if (presetValue === 'complex' || presetValue === 'simple' || presetValue === 'merge') {
|
|
184
|
+
result.preset = presetValue;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
throw new Error(`Invalid preset: "${presetValue}". Must be one of: complex, simple, merge`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (arg === '--deps' && args[i + 1]) {
|
|
191
|
+
result.deps = args[++i];
|
|
192
|
+
}
|
|
193
|
+
else if (arg === '--prompt' && args[i + 1]) {
|
|
194
|
+
result.prompt = args[++i];
|
|
195
|
+
}
|
|
196
|
+
else if (arg === '--criteria' && args[i + 1]) {
|
|
197
|
+
result.criteria = args[++i].split(',').map(c => c.trim()).filter(c => c);
|
|
198
|
+
}
|
|
199
|
+
else if (arg === '--model' && args[i + 1]) {
|
|
200
|
+
result.model = args[++i];
|
|
201
|
+
}
|
|
202
|
+
else if (arg === '--task' && args[i + 1]) {
|
|
203
|
+
result.taskSpecs.push(args[++i]);
|
|
204
|
+
}
|
|
205
|
+
else if (arg === '--add-lane' && args[i + 1]) {
|
|
206
|
+
result.addLane = args[++i];
|
|
207
|
+
}
|
|
208
|
+
else if (arg === '--add-task' && args[i + 1]) {
|
|
209
|
+
result.addTask = args[++i];
|
|
210
|
+
}
|
|
211
|
+
else if (arg === '--depends-on' && args[i + 1]) {
|
|
212
|
+
result.dependsOnLanes = args[++i].split(',').map(d => d.trim()).filter(d => d);
|
|
213
|
+
}
|
|
214
|
+
else if (!arg.startsWith('--') && !result.featureName) {
|
|
215
|
+
result.featureName = arg;
|
|
216
|
+
}
|
|
217
|
+
i++;
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
function parseTaskSpec(spec) {
|
|
222
|
+
// Format: "name|model|prompt|criteria1,criteria2"
|
|
223
|
+
const parts = spec.split('|');
|
|
224
|
+
if (parts.length < 3) {
|
|
225
|
+
throw new Error(`Invalid task spec: "${spec}". Expected format: "name|model|prompt[|criteria1,criteria2]"`);
|
|
226
|
+
}
|
|
227
|
+
const [name, model, prompt, criteriaStr] = parts;
|
|
228
|
+
const acceptanceCriteria = criteriaStr
|
|
229
|
+
? criteriaStr.split(',').map(c => c.trim()).filter(c => c)
|
|
230
|
+
: undefined;
|
|
231
|
+
return {
|
|
232
|
+
name: name.trim(),
|
|
233
|
+
model: model.trim() || 'sonnet-4.5',
|
|
234
|
+
prompt: prompt.trim(),
|
|
235
|
+
...(acceptanceCriteria && acceptanceCriteria.length > 0 ? { acceptanceCriteria } : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Generate tasks based on preset template
|
|
240
|
+
*/
|
|
241
|
+
function buildTasksFromPreset(preset, featureName, laneNumber, basePrompt, criteria, hasDependencies) {
|
|
242
|
+
const tasks = [];
|
|
243
|
+
// Plan document path - stored in the worktree root
|
|
244
|
+
const planDocPath = `_cursorflow/PLAN_lane-${laneNumber}.md`;
|
|
245
|
+
// If lane has dependencies, auto-apply merge preset logic
|
|
246
|
+
const effectivePreset = hasDependencies && preset !== 'merge' ? preset : preset;
|
|
247
|
+
switch (effectivePreset) {
|
|
248
|
+
case 'complex':
|
|
249
|
+
// plan → implement → test
|
|
250
|
+
tasks.push({
|
|
251
|
+
name: 'plan',
|
|
252
|
+
model: 'sonnet-4.5-thinking',
|
|
253
|
+
prompt: `# Planning: ${featureName} (Lane ${laneNumber})
|
|
254
|
+
|
|
255
|
+
## Goal
|
|
256
|
+
Analyze the requirements and create a detailed implementation plan.
|
|
257
|
+
|
|
258
|
+
## Context
|
|
259
|
+
${basePrompt}
|
|
260
|
+
|
|
261
|
+
## Instructions
|
|
262
|
+
1. Understand the scope and requirements.
|
|
263
|
+
2. List all files that need to be created or modified.
|
|
264
|
+
3. Define data structures and interfaces.
|
|
265
|
+
4. Outline step-by-step implementation plan.
|
|
266
|
+
|
|
267
|
+
## Output
|
|
268
|
+
**IMPORTANT: Save the plan document to \`${planDocPath}\`**
|
|
269
|
+
|
|
270
|
+
The plan document should include:
|
|
271
|
+
- Overview of the implementation approach
|
|
272
|
+
- List of files to create/modify
|
|
273
|
+
- Data structures and interfaces
|
|
274
|
+
- Step-by-step implementation tasks
|
|
275
|
+
- Potential risks and edge cases`,
|
|
276
|
+
acceptanceCriteria: [
|
|
277
|
+
`Plan document saved to ${planDocPath}`,
|
|
278
|
+
'All required files are identified',
|
|
279
|
+
'Approach is clearly defined',
|
|
280
|
+
],
|
|
281
|
+
}, {
|
|
282
|
+
name: 'implement',
|
|
283
|
+
model: 'sonnet-4.5',
|
|
284
|
+
prompt: `# Implementation: ${featureName} (Lane ${laneNumber})
|
|
285
|
+
|
|
286
|
+
## Goal
|
|
287
|
+
Implement the planned changes.
|
|
288
|
+
|
|
289
|
+
## Context
|
|
290
|
+
${basePrompt}
|
|
291
|
+
|
|
292
|
+
## Plan Document
|
|
293
|
+
**Read the plan from \`${planDocPath}\` before starting implementation.**
|
|
294
|
+
|
|
295
|
+
## Instructions
|
|
296
|
+
1. Read and understand the plan document at \`${planDocPath}\`.
|
|
297
|
+
2. Follow the plan step by step.
|
|
298
|
+
3. Implement all code changes.
|
|
299
|
+
4. Ensure no build errors.
|
|
300
|
+
5. Write necessary code comments.
|
|
301
|
+
6. Double-check all requirements before finishing.
|
|
302
|
+
|
|
303
|
+
## Important
|
|
304
|
+
- Refer back to the plan document if unsure about any step.
|
|
305
|
+
- Verify all edge cases from the plan are handled.
|
|
306
|
+
- Ensure code follows project conventions.`,
|
|
307
|
+
acceptanceCriteria: criteria.length > 0 ? criteria : [
|
|
308
|
+
'Code implemented according to plan',
|
|
309
|
+
'No build errors',
|
|
310
|
+
'All edge cases handled',
|
|
311
|
+
],
|
|
312
|
+
}, {
|
|
313
|
+
name: 'test',
|
|
314
|
+
model: 'sonnet-4.5',
|
|
315
|
+
prompt: `# Testing: ${featureName} (Lane ${laneNumber})
|
|
316
|
+
|
|
317
|
+
## Goal
|
|
318
|
+
Write comprehensive tests for the implementation.
|
|
319
|
+
|
|
320
|
+
## Plan Document
|
|
321
|
+
**Refer to \`${planDocPath}\` for the list of features and edge cases to test.**
|
|
322
|
+
|
|
323
|
+
## Instructions
|
|
324
|
+
1. Review the plan document for test requirements.
|
|
325
|
+
2. Write unit tests for new functions/classes.
|
|
326
|
+
3. Write integration tests if applicable.
|
|
327
|
+
4. Ensure all tests pass.
|
|
328
|
+
5. Verify edge cases from the plan are covered.
|
|
329
|
+
6. Double-check that nothing is missing.
|
|
330
|
+
|
|
331
|
+
## Important
|
|
332
|
+
- All tests must pass before completing.
|
|
333
|
+
- Cover happy path and error cases from the plan.`,
|
|
334
|
+
acceptanceCriteria: [
|
|
335
|
+
'Unit tests written',
|
|
336
|
+
'All tests pass',
|
|
337
|
+
'Edge cases covered',
|
|
338
|
+
],
|
|
339
|
+
});
|
|
340
|
+
break;
|
|
341
|
+
case 'simple':
|
|
342
|
+
// implement → test
|
|
343
|
+
tasks.push({
|
|
344
|
+
name: 'implement',
|
|
345
|
+
model: 'sonnet-4.5',
|
|
346
|
+
prompt: `# Implementation: ${featureName} (Lane ${laneNumber})
|
|
347
|
+
|
|
348
|
+
## Goal
|
|
349
|
+
${basePrompt}
|
|
350
|
+
|
|
351
|
+
## Instructions
|
|
352
|
+
1. Implement the required changes.
|
|
353
|
+
2. Ensure no build errors.
|
|
354
|
+
3. Handle edge cases appropriately.
|
|
355
|
+
4. Double-check all requirements before finishing.
|
|
356
|
+
|
|
357
|
+
## Important
|
|
358
|
+
- Keep changes focused and minimal.
|
|
359
|
+
- Follow existing code conventions.`,
|
|
360
|
+
acceptanceCriteria: criteria.length > 0 ? criteria : [
|
|
361
|
+
'Implementation complete',
|
|
362
|
+
'No build errors',
|
|
363
|
+
'Code follows conventions',
|
|
364
|
+
],
|
|
365
|
+
}, {
|
|
366
|
+
name: 'test',
|
|
367
|
+
model: 'sonnet-4.5',
|
|
368
|
+
prompt: `# Testing: ${featureName} (Lane ${laneNumber})
|
|
369
|
+
|
|
370
|
+
## Goal
|
|
371
|
+
Test the implementation thoroughly.
|
|
372
|
+
|
|
373
|
+
## Instructions
|
|
374
|
+
1. Write or update tests for the changes.
|
|
375
|
+
2. Run all related tests.
|
|
376
|
+
3. Ensure all tests pass.
|
|
377
|
+
4. Double-check edge cases.
|
|
378
|
+
|
|
379
|
+
## Important
|
|
380
|
+
- All tests must pass before completing.`,
|
|
381
|
+
acceptanceCriteria: [
|
|
382
|
+
'Tests written/updated',
|
|
383
|
+
'All tests pass',
|
|
384
|
+
],
|
|
385
|
+
});
|
|
386
|
+
break;
|
|
387
|
+
case 'merge':
|
|
388
|
+
// merge → test (for dependent lanes)
|
|
389
|
+
tasks.push({
|
|
390
|
+
name: 'merge',
|
|
391
|
+
model: 'sonnet-4.5',
|
|
392
|
+
prompt: `# Merge & Integrate: ${featureName} (Lane ${laneNumber})
|
|
393
|
+
|
|
394
|
+
## Goal
|
|
395
|
+
Merge dependent branches and resolve any conflicts.
|
|
396
|
+
|
|
397
|
+
## Instructions
|
|
398
|
+
1. The dependent branches have been automatically merged.
|
|
399
|
+
2. Check for any merge conflicts and resolve them.
|
|
400
|
+
3. Ensure all imports and dependencies are correct.
|
|
401
|
+
4. Verify the integrated code compiles without errors.
|
|
402
|
+
5. Fix any integration issues.
|
|
403
|
+
|
|
404
|
+
## Important
|
|
405
|
+
- Resolve all conflicts cleanly.
|
|
406
|
+
- Ensure code from all merged branches works together.
|
|
407
|
+
- Check that no functionality was broken by the merge.`,
|
|
408
|
+
acceptanceCriteria: [
|
|
409
|
+
'All conflicts resolved',
|
|
410
|
+
'No build errors',
|
|
411
|
+
'Integration verified',
|
|
412
|
+
],
|
|
413
|
+
}, {
|
|
414
|
+
name: 'test',
|
|
415
|
+
model: 'sonnet-4.5',
|
|
416
|
+
prompt: `# Integration Testing: ${featureName} (Lane ${laneNumber})
|
|
417
|
+
|
|
418
|
+
## Goal
|
|
419
|
+
Run comprehensive tests after the merge.
|
|
420
|
+
|
|
421
|
+
## Instructions
|
|
422
|
+
1. Run all unit tests.
|
|
423
|
+
2. Run integration tests.
|
|
424
|
+
3. Test that features from merged branches work together.
|
|
425
|
+
4. Verify no regressions were introduced.
|
|
426
|
+
5. Fix any failing tests.
|
|
427
|
+
|
|
428
|
+
## Important
|
|
429
|
+
- All tests must pass.
|
|
430
|
+
- Test the interaction between merged features.`,
|
|
431
|
+
acceptanceCriteria: criteria.length > 0 ? criteria : [
|
|
432
|
+
'All unit tests pass',
|
|
433
|
+
'Integration tests pass',
|
|
434
|
+
'No regressions',
|
|
435
|
+
],
|
|
436
|
+
});
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
return tasks;
|
|
440
|
+
}
|
|
441
|
+
function buildTasksFromOptions(options, laneNumber, featureName, hasDependencies = false) {
|
|
442
|
+
// Priority: --task > --preset/dependencies > --prompt alone > default
|
|
443
|
+
// 1. Explicit --task specifications (highest priority)
|
|
444
|
+
if (options.taskSpecs.length > 0) {
|
|
445
|
+
const tasks = [];
|
|
446
|
+
for (const spec of options.taskSpecs) {
|
|
447
|
+
tasks.push(parseTaskSpec(spec));
|
|
448
|
+
}
|
|
449
|
+
return tasks;
|
|
450
|
+
}
|
|
451
|
+
// 2. Preset template (use when --preset specified OR lane has dependencies)
|
|
452
|
+
// --prompt serves as context when used with preset
|
|
453
|
+
if (options.preset || hasDependencies) {
|
|
454
|
+
// Auto-apply merge preset if lane has dependencies and no explicit preset
|
|
455
|
+
const preset = options.preset || (hasDependencies ? 'merge' : 'complex');
|
|
456
|
+
return buildTasksFromPreset(preset, featureName, laneNumber, options.prompt || `Implement ${featureName}`, options.criteria, hasDependencies);
|
|
457
|
+
}
|
|
458
|
+
// 3. Single task from --prompt (only when no preset specified)
|
|
459
|
+
if (options.prompt) {
|
|
460
|
+
const task = {
|
|
461
|
+
name: 'implement',
|
|
462
|
+
model: options.model || 'sonnet-4.5',
|
|
463
|
+
prompt: options.prompt,
|
|
464
|
+
};
|
|
465
|
+
if (options.criteria.length > 0) {
|
|
466
|
+
task.acceptanceCriteria = options.criteria;
|
|
467
|
+
}
|
|
468
|
+
return [task];
|
|
469
|
+
}
|
|
470
|
+
// 4. Default: complex preset
|
|
471
|
+
return buildTasksFromPreset('complex', featureName, laneNumber, `Implement ${featureName}`, options.criteria, hasDependencies);
|
|
472
|
+
}
|
|
473
|
+
function getDefaultConfig(laneNumber, featureName, tasks) {
|
|
474
|
+
return {
|
|
475
|
+
// Git Configuration
|
|
476
|
+
baseBranch: 'main',
|
|
477
|
+
branchPrefix: `${featureName.toLowerCase()}/lane-${laneNumber}-`,
|
|
478
|
+
// Execution Settings
|
|
479
|
+
timeout: 300000,
|
|
480
|
+
enableIntervention: false,
|
|
481
|
+
// Dependency Policy
|
|
482
|
+
dependencyPolicy: {
|
|
483
|
+
allowDependencyChange: false,
|
|
484
|
+
lockfileReadOnly: true,
|
|
485
|
+
},
|
|
486
|
+
// Review Settings
|
|
487
|
+
enableReview: true,
|
|
488
|
+
reviewModel: 'sonnet-4.5-thinking',
|
|
489
|
+
maxReviewIterations: 3,
|
|
490
|
+
// Lane Metadata
|
|
491
|
+
laneNumber: laneNumber,
|
|
492
|
+
devPort: 3000 + laneNumber,
|
|
493
|
+
// Tasks
|
|
494
|
+
tasks: tasks,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function parseDeps(depsStr) {
|
|
498
|
+
const map = new Map();
|
|
499
|
+
// Format: "2:1;3:1,2"
|
|
500
|
+
const lanes = depsStr.split(';');
|
|
501
|
+
for (const lane of lanes) {
|
|
502
|
+
const [targetStr, depsPart] = lane.split(':');
|
|
503
|
+
if (!targetStr || !depsPart)
|
|
504
|
+
continue;
|
|
505
|
+
const target = parseInt(targetStr);
|
|
506
|
+
const deps = depsPart.split(',').map(d => parseInt(d)).filter(d => !isNaN(d));
|
|
507
|
+
if (!isNaN(target) && deps.length > 0) {
|
|
508
|
+
map.set(target, deps);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return map;
|
|
512
|
+
}
|
|
513
|
+
function replacePlaceholders(obj, context) {
|
|
514
|
+
if (typeof obj === 'string') {
|
|
515
|
+
return obj
|
|
516
|
+
.replace(/\{\{featureName\}\}/g, context.featureName)
|
|
517
|
+
.replace(/\{\{laneNumber\}\}/g, String(context.laneNumber))
|
|
518
|
+
.replace(/\{\{devPort\}\}/g, String(context.devPort));
|
|
519
|
+
}
|
|
520
|
+
if (Array.isArray(obj)) {
|
|
521
|
+
return obj.map(item => replacePlaceholders(item, context));
|
|
522
|
+
}
|
|
523
|
+
if (obj !== null && typeof obj === 'object') {
|
|
524
|
+
const result = {};
|
|
525
|
+
for (const key in obj) {
|
|
526
|
+
result[key] = replacePlaceholders(obj[key], context);
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
return obj;
|
|
531
|
+
}
|
|
532
|
+
function getNextLaneNumber(taskDir) {
|
|
533
|
+
const files = fs.readdirSync(taskDir).filter(f => f.endsWith('.json'));
|
|
534
|
+
let maxNum = 0;
|
|
535
|
+
for (const file of files) {
|
|
536
|
+
const match = file.match(/^(\d+)-/);
|
|
537
|
+
if (match) {
|
|
538
|
+
const num = parseInt(match[1]);
|
|
539
|
+
if (num > maxNum)
|
|
540
|
+
maxNum = num;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return maxNum + 1;
|
|
544
|
+
}
|
|
545
|
+
function getFeatureNameFromDir(taskDir) {
|
|
546
|
+
const dirName = path.basename(taskDir);
|
|
547
|
+
// Format: YYMMDDHHMM_FeatureName
|
|
548
|
+
const match = dirName.match(/^\d+_(.+)$/);
|
|
549
|
+
return match ? match[1] : dirName;
|
|
550
|
+
}
|
|
551
|
+
async function addLaneToDir(options) {
|
|
552
|
+
const taskDir = path.resolve(process.cwd(), options.addLane);
|
|
553
|
+
if (!fs.existsSync(taskDir)) {
|
|
554
|
+
throw new Error(`Task directory not found: ${taskDir}`);
|
|
555
|
+
}
|
|
556
|
+
const featureName = getFeatureNameFromDir(taskDir);
|
|
557
|
+
const laneNumber = getNextLaneNumber(taskDir);
|
|
558
|
+
const laneName = `lane-${laneNumber}`;
|
|
559
|
+
const fileName = `${laneNumber.toString().padStart(2, '0')}-${laneName}.json`;
|
|
560
|
+
const filePath = path.join(taskDir, fileName);
|
|
561
|
+
if (fs.existsSync(filePath) && !options.force) {
|
|
562
|
+
throw new Error(`Lane file already exists: ${filePath}. Use --force to overwrite.`);
|
|
563
|
+
}
|
|
564
|
+
const hasDependencies = options.dependsOnLanes.length > 0;
|
|
565
|
+
// Build tasks from options (auto-detects merge preset if has dependencies)
|
|
566
|
+
const tasks = buildTasksFromOptions(options, laneNumber, featureName, hasDependencies);
|
|
567
|
+
const config = getDefaultConfig(laneNumber, featureName, tasks);
|
|
568
|
+
// Add dependencies if specified
|
|
569
|
+
const finalConfig = {
|
|
570
|
+
...config,
|
|
571
|
+
...(hasDependencies ? { dependsOn: options.dependsOnLanes } : {}),
|
|
572
|
+
};
|
|
573
|
+
fs.writeFileSync(filePath, JSON.stringify(finalConfig, null, 2) + '\n', 'utf8');
|
|
574
|
+
const taskSummary = tasks.map(t => t.name).join(' → ');
|
|
575
|
+
const depsInfo = hasDependencies ? ` (depends: ${options.dependsOnLanes.join(', ')})` : '';
|
|
576
|
+
const presetInfo = options.preset ? ` [${options.preset}]` : (hasDependencies ? ' [merge]' : '');
|
|
577
|
+
logger.success(`Added lane: ${fileName} [${taskSummary}]${presetInfo}${depsInfo}`);
|
|
578
|
+
logger.info(`Directory: ${taskDir}`);
|
|
579
|
+
console.log(`\nNext steps:`);
|
|
580
|
+
console.log(` 1. Validate: cursorflow doctor --tasks-dir ${taskDir}`);
|
|
581
|
+
console.log(` 2. Run: cursorflow run ${taskDir}`);
|
|
582
|
+
}
|
|
583
|
+
async function addTaskToLane(options) {
|
|
584
|
+
const laneFile = path.resolve(process.cwd(), options.addTask);
|
|
585
|
+
if (!fs.existsSync(laneFile)) {
|
|
586
|
+
throw new Error(`Lane file not found: ${laneFile}`);
|
|
587
|
+
}
|
|
588
|
+
if (options.taskSpecs.length === 0) {
|
|
589
|
+
throw new Error('No task specified. Use --task "name|model|prompt|criteria" to define a task.');
|
|
590
|
+
}
|
|
591
|
+
// Read existing config
|
|
592
|
+
const existingConfig = JSON.parse(fs.readFileSync(laneFile, 'utf8'));
|
|
593
|
+
if (!existingConfig.tasks || !Array.isArray(existingConfig.tasks)) {
|
|
594
|
+
existingConfig.tasks = [];
|
|
595
|
+
}
|
|
596
|
+
// Add new tasks
|
|
597
|
+
const newTasks = [];
|
|
598
|
+
for (const spec of options.taskSpecs) {
|
|
599
|
+
const task = parseTaskSpec(spec);
|
|
600
|
+
existingConfig.tasks.push(task);
|
|
601
|
+
newTasks.push(task);
|
|
602
|
+
}
|
|
603
|
+
// Write back
|
|
604
|
+
fs.writeFileSync(laneFile, JSON.stringify(existingConfig, null, 2) + '\n', 'utf8');
|
|
605
|
+
const taskNames = newTasks.map(t => t.name).join(', ');
|
|
606
|
+
logger.success(`Added task(s): ${taskNames}`);
|
|
607
|
+
logger.info(`Updated: ${laneFile}`);
|
|
608
|
+
const taskSummary = existingConfig.tasks.map((t) => t.name).join(' → ');
|
|
609
|
+
logger.info(`Lane now has: ${taskSummary}`);
|
|
610
|
+
}
|
|
611
|
+
async function createNewFeature(options) {
|
|
612
|
+
const config = (0, config_1.loadConfig)();
|
|
613
|
+
const tasksBaseDir = (0, config_1.getTasksDir)(config);
|
|
614
|
+
// Timestamp-based folder name (YYMMDDHHMM)
|
|
615
|
+
const now = new Date();
|
|
616
|
+
const timestamp = now.toISOString().replace(/[-T:]/g, '').substring(2, 12);
|
|
617
|
+
const taskDirName = `${timestamp}_${options.featureName}`;
|
|
618
|
+
const taskDir = path.join(tasksBaseDir, taskDirName);
|
|
619
|
+
if (fs.existsSync(taskDir) && !options.force) {
|
|
620
|
+
throw new Error(`Task directory already exists: ${taskDir}. Use --force to overwrite.`);
|
|
621
|
+
}
|
|
622
|
+
if (!fs.existsSync(taskDir)) {
|
|
623
|
+
fs.mkdirSync(taskDir, { recursive: true });
|
|
624
|
+
}
|
|
625
|
+
logger.info(`Creating tasks in: ${path.relative(config.projectRoot, taskDir)}`);
|
|
626
|
+
// Load template if provided (overrides --prompt/--task/--preset)
|
|
627
|
+
let template = null;
|
|
628
|
+
if (options.template) {
|
|
629
|
+
const templatePath = path.resolve(process.cwd(), options.template);
|
|
630
|
+
if (!fs.existsSync(templatePath)) {
|
|
631
|
+
throw new Error(`Template file not found: ${templatePath}`);
|
|
632
|
+
}
|
|
633
|
+
template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
|
634
|
+
logger.info(`Using template: ${options.template}`);
|
|
635
|
+
}
|
|
636
|
+
// Calculate dependencies
|
|
637
|
+
const dependencyMap = options.sequential
|
|
638
|
+
? new Map(Array.from({ length: options.lanes - 1 }, (_, i) => [i + 2, [i + 1]]))
|
|
639
|
+
: (options.deps ? parseDeps(options.deps) : new Map());
|
|
640
|
+
const laneInfoList = [];
|
|
641
|
+
for (let i = 1; i <= options.lanes; i++) {
|
|
642
|
+
const laneName = `lane-${i}`;
|
|
643
|
+
const fileName = `${i.toString().padStart(2, '0')}-${laneName}.json`;
|
|
644
|
+
const filePath = path.join(taskDir, fileName);
|
|
645
|
+
const depNums = dependencyMap.get(i) || [];
|
|
646
|
+
const dependsOn = depNums.map(n => {
|
|
647
|
+
const depLaneName = `lane-${n}`;
|
|
648
|
+
return `${n.toString().padStart(2, '0')}-${depLaneName}`;
|
|
649
|
+
});
|
|
650
|
+
const hasDependencies = dependsOn.length > 0;
|
|
651
|
+
const devPort = 3000 + i;
|
|
652
|
+
let taskConfig;
|
|
653
|
+
let effectivePreset = options.preset || (hasDependencies ? 'merge' : 'complex');
|
|
654
|
+
if (template) {
|
|
655
|
+
// Use template
|
|
656
|
+
taskConfig = { ...template, laneNumber: i, devPort };
|
|
657
|
+
effectivePreset = 'custom';
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
// Build from CLI options
|
|
661
|
+
const tasks = buildTasksFromOptions(options, i, options.featureName, hasDependencies);
|
|
662
|
+
taskConfig = getDefaultConfig(i, options.featureName, tasks);
|
|
663
|
+
}
|
|
664
|
+
// Replace placeholders
|
|
665
|
+
const processedConfig = replacePlaceholders(taskConfig, {
|
|
666
|
+
featureName: options.featureName,
|
|
667
|
+
laneNumber: i,
|
|
668
|
+
devPort: devPort,
|
|
669
|
+
});
|
|
670
|
+
// Add dependencies if any
|
|
671
|
+
const finalConfig = {
|
|
672
|
+
...processedConfig,
|
|
673
|
+
...(dependsOn.length > 0 ? { dependsOn } : {}),
|
|
674
|
+
};
|
|
675
|
+
fs.writeFileSync(filePath, JSON.stringify(finalConfig, null, 2) + '\n', 'utf8');
|
|
676
|
+
const taskSummary = finalConfig.tasks?.map((t) => t.name).join(' → ') || 'default';
|
|
677
|
+
const presetLabel = effectivePreset !== 'custom' ? ` [${effectivePreset}]` : '';
|
|
678
|
+
logger.success(`Created: ${fileName} [${taskSummary}]${presetLabel}${dependsOn.length > 0 ? ` (depends: ${dependsOn.join(', ')})` : ''}`);
|
|
679
|
+
laneInfoList.push({ name: laneName, fileName, dependsOn, preset: effectivePreset });
|
|
680
|
+
}
|
|
681
|
+
// Create README
|
|
682
|
+
const readmePath = path.join(taskDir, 'README.md');
|
|
683
|
+
const readme = `# Task: ${options.featureName}
|
|
684
|
+
|
|
685
|
+
Prepared at: ${now.toISOString()}
|
|
686
|
+
Lanes: ${options.lanes}
|
|
687
|
+
|
|
688
|
+
## How to Run
|
|
689
|
+
|
|
690
|
+
\`\`\`bash
|
|
691
|
+
# 1. Validate configuration
|
|
692
|
+
cursorflow doctor --tasks-dir ${path.relative(config.projectRoot, taskDir)}
|
|
693
|
+
|
|
694
|
+
# 2. Run
|
|
695
|
+
cursorflow run ${path.relative(config.projectRoot, taskDir)}
|
|
696
|
+
\`\`\`
|
|
697
|
+
|
|
698
|
+
## Lanes
|
|
699
|
+
|
|
700
|
+
${laneInfoList.map(l => `- **${l.fileName.replace('.json', '')}** [${l.preset}]${l.dependsOn.length > 0 ? ` (depends: ${l.dependsOn.join(', ')})` : ''}`).join('\n')}
|
|
701
|
+
|
|
702
|
+
## Modifying Tasks
|
|
703
|
+
|
|
704
|
+
\`\`\`bash
|
|
705
|
+
# Add a new lane (with merge preset for dependent lanes)
|
|
706
|
+
cursorflow prepare --add-lane ${path.relative(config.projectRoot, taskDir)} \\
|
|
707
|
+
--preset merge --depends-on "01-lane-1"
|
|
708
|
+
|
|
709
|
+
# Add task to existing lane
|
|
710
|
+
cursorflow prepare --add-task ${path.relative(config.projectRoot, taskDir)}/01-lane-1.json \\
|
|
711
|
+
--task "verify|sonnet-4.5|Verify requirements|All met"
|
|
712
|
+
\`\`\`
|
|
713
|
+
`;
|
|
714
|
+
fs.writeFileSync(readmePath, readme, 'utf8');
|
|
715
|
+
logger.success('Created README.md');
|
|
716
|
+
logger.section('✅ Preparation complete!');
|
|
717
|
+
console.log(`\nNext steps:`);
|
|
718
|
+
console.log(` 1. (Optional) Add more lanes/tasks`);
|
|
719
|
+
console.log(` 2. Validate: cursorflow doctor --tasks-dir ${path.relative(config.projectRoot, taskDir)}`);
|
|
720
|
+
console.log(` 3. Run: cursorflow run ${path.relative(config.projectRoot, taskDir)}`);
|
|
721
|
+
console.log('');
|
|
722
|
+
}
|
|
723
|
+
async function prepare(args) {
|
|
724
|
+
const options = parseArgs(args);
|
|
725
|
+
if (options.help) {
|
|
726
|
+
printHelp();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
// Mode 1: Add task to existing lane
|
|
730
|
+
if (options.addTask) {
|
|
731
|
+
await addTaskToLane(options);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
// Mode 2: Add lane to existing directory
|
|
735
|
+
if (options.addLane) {
|
|
736
|
+
await addLaneToDir(options);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
// Mode 3: Create new feature (requires featureName)
|
|
740
|
+
if (!options.featureName) {
|
|
741
|
+
printHelp();
|
|
742
|
+
process.exit(1);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
await createNewFeature(options);
|
|
746
|
+
}
|
|
747
|
+
module.exports = prepare;
|
|
748
|
+
//# sourceMappingURL=prepare.js.map
|