@litmers/cursorflow-orchestrator 0.1.8 → 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.
Files changed (66) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +113 -319
  3. package/commands/cursorflow-clean.md +24 -135
  4. package/commands/cursorflow-doctor.md +74 -18
  5. package/commands/cursorflow-init.md +33 -50
  6. package/commands/cursorflow-models.md +51 -0
  7. package/commands/cursorflow-monitor.md +56 -118
  8. package/commands/cursorflow-prepare.md +410 -108
  9. package/commands/cursorflow-resume.md +51 -148
  10. package/commands/cursorflow-review.md +38 -202
  11. package/commands/cursorflow-run.md +208 -86
  12. package/commands/cursorflow-signal.md +38 -12
  13. package/dist/cli/clean.d.ts +3 -1
  14. package/dist/cli/clean.js +145 -8
  15. package/dist/cli/clean.js.map +1 -1
  16. package/dist/cli/doctor.js +14 -1
  17. package/dist/cli/doctor.js.map +1 -1
  18. package/dist/cli/index.js +32 -21
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/init.js +5 -4
  21. package/dist/cli/init.js.map +1 -1
  22. package/dist/cli/models.d.ts +7 -0
  23. package/dist/cli/models.js +104 -0
  24. package/dist/cli/models.js.map +1 -0
  25. package/dist/cli/monitor.js +56 -1
  26. package/dist/cli/monitor.js.map +1 -1
  27. package/dist/cli/prepare.d.ts +7 -0
  28. package/dist/cli/prepare.js +748 -0
  29. package/dist/cli/prepare.js.map +1 -0
  30. package/dist/cli/resume.js +56 -0
  31. package/dist/cli/resume.js.map +1 -1
  32. package/dist/cli/run.js +30 -1
  33. package/dist/cli/run.js.map +1 -1
  34. package/dist/cli/signal.js +18 -0
  35. package/dist/cli/signal.js.map +1 -1
  36. package/dist/core/runner.d.ts +9 -1
  37. package/dist/core/runner.js +139 -23
  38. package/dist/core/runner.js.map +1 -1
  39. package/dist/utils/cursor-agent.d.ts +4 -0
  40. package/dist/utils/cursor-agent.js +58 -10
  41. package/dist/utils/cursor-agent.js.map +1 -1
  42. package/dist/utils/doctor.d.ts +10 -0
  43. package/dist/utils/doctor.js +581 -1
  44. package/dist/utils/doctor.js.map +1 -1
  45. package/dist/utils/types.d.ts +11 -0
  46. package/examples/README.md +114 -59
  47. package/examples/demo-project/README.md +61 -79
  48. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +17 -6
  49. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +17 -6
  50. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +66 -25
  51. package/package.json +1 -1
  52. package/scripts/patches/test-cursor-agent.js +203 -0
  53. package/src/cli/clean.ts +156 -9
  54. package/src/cli/doctor.ts +18 -2
  55. package/src/cli/index.ts +33 -21
  56. package/src/cli/init.ts +6 -4
  57. package/src/cli/models.ts +83 -0
  58. package/src/cli/monitor.ts +60 -1
  59. package/src/cli/prepare.ts +844 -0
  60. package/src/cli/resume.ts +66 -0
  61. package/src/cli/run.ts +36 -2
  62. package/src/cli/signal.ts +22 -0
  63. package/src/core/runner.ts +164 -23
  64. package/src/utils/cursor-agent.ts +62 -10
  65. package/src/utils/doctor.ts +633 -5
  66. package/src/utils/types.ts +11 -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