@litmers/cursorflow-orchestrator 0.2.2 → 0.2.3

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.
@@ -1,690 +0,0 @@
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
- const path_1 = require("../utils/path");
45
- const template_1 = require("../utils/template");
46
- function printHelp() {
47
- console.log(`
48
- cursorflow prepare - 태스크 파일 생성
49
-
50
- ═══════════════════════════════════════════════════════════════════════════════
51
- 시나리오: "쇼핑몰" 프로젝트에서 백엔드 API와 프론트엔드 동시 개발
52
- ═══════════════════════════════════════════════════════════════════════════════
53
-
54
- [Case 1] 가장 간단하게 - 버그 하나 고치기
55
- ─────────────────────────────────────────
56
- cursorflow prepare FixCartBug --prompt "장바구니 수량 버그 수정"
57
-
58
- 결과: _cursorflow/tasks/2412251030_FixCartBug/
59
- └── 01-FixCartBug.json (implement 태스크 1개)
60
-
61
-
62
- [Case 2] 프리셋 사용 - 계획부터 테스트까지
63
- ─────────────────────────────────────────
64
- cursorflow prepare PaymentAPI --preset complex --prompt "Stripe 결제 연동"
65
-
66
- 결과: 01-PaymentAPI.json에 plan → implement → test 태스크 생성
67
-
68
- 프리셋:
69
- --preset complex plan → implement → test
70
- --preset simple implement → test
71
- (없으면) implement만
72
-
73
-
74
- [Case 3] 병렬 레인 - 백엔드/프론트 동시 개발
75
- ─────────────────────────────────────────
76
- cursorflow prepare ShopFeature --lanes 2 --preset complex \\
77
- --prompt "상품 검색 기능"
78
-
79
- 결과: 01-lane-1.json (백엔드) ─┬─ 동시 실행
80
- 02-lane-2.json (프론트) ─┘
81
-
82
-
83
- [Case 4] 의존성 - 프론트가 백엔드 완료 후 시작
84
- ─────────────────────────────────────────
85
- cursorflow prepare --add-task ./02-lane-2.json \\
86
- --task "integrate|sonnet-4.5|API 연동|완료|01-lane-1:implement"
87
- └─ 이 태스크 완료 후 시작
88
-
89
- 실행 흐름:
90
- 01-lane-1: [plan] → [implement] → [test]
91
- ↓ 완료되면
92
- 02-lane-2: [plan] ───────┴─────→ [integrate]
93
-
94
-
95
- [Case 5] 커스텀 태스크 - 원하는 대로 구성
96
- ─────────────────────────────────────────
97
- cursorflow prepare CustomFlow \\
98
- --task "setup|sonnet-4.5|DB 스키마 생성|완료" \\
99
- --task "api|sonnet-4.5|REST API 구현|동작" \\
100
- --task "test|sonnet-4.5|테스트 작성|통과"
101
-
102
-
103
- [Case 6] 나중에 추가 - 레인이나 태스크 덧붙이기
104
- ─────────────────────────────────────────
105
- # 새 레인 추가
106
- cursorflow prepare --add-lane ./tasks/ShopFeature --preset simple
107
-
108
- # 기존 레인에 태스크 추가
109
- cursorflow prepare --add-task ./01-lane-1.json \\
110
- --task "docs|sonnet-4.5|API 문서화|완성"
111
-
112
- ═══════════════════════════════════════════════════════════════════════════════
113
-
114
- --task 형식: "이름|모델|프롬프트|완료조건|의존성|타임아웃"
115
-
116
- 예시:
117
- "build|sonnet-4.5|빌드하기|완료" 기본
118
- "deploy|sonnet-4.5|배포|성공|01-lane:build" 의존성
119
- "heavy|sonnet-4.5|대용량|완료||1200000" 타임아웃 20분
120
-
121
- ═══════════════════════════════════════════════════════════════════════════════
122
-
123
- 옵션 요약:
124
- --prompt <text> 작업 설명
125
- --preset <type> complex | simple | merge
126
- --lanes <num> 병렬 레인 수 (기본: 1)
127
- --task <spec> 커스텀 태스크 (반복 가능)
128
- --add-lane <dir> 기존 디렉토리에 레인 추가
129
- --add-task <file> 기존 레인에 태스크 추가
130
- --model <model> AI 모델 (기본: sonnet-4.5)
131
- --template <path> 외부 템플릿 파일
132
- --force 덮어쓰기
133
- `);
134
- }
135
- function parseArgs(args) {
136
- const result = {
137
- featureName: '',
138
- lanes: 1,
139
- template: null,
140
- preset: null,
141
- prompt: null,
142
- criteria: [],
143
- model: null,
144
- taskSpecs: [],
145
- addLane: null,
146
- addTask: null,
147
- force: false,
148
- help: false,
149
- };
150
- let i = 0;
151
- while (i < args.length) {
152
- const arg = args[i];
153
- if (arg === '--help' || arg === '-h') {
154
- result.help = true;
155
- }
156
- else if (arg === '--force') {
157
- result.force = true;
158
- }
159
- else if (arg === '--lanes' && args[i + 1]) {
160
- result.lanes = parseInt(args[++i]) || 1;
161
- }
162
- else if (arg === '--template' && args[i + 1]) {
163
- result.template = args[++i];
164
- }
165
- else if (arg === '--preset' && args[i + 1]) {
166
- const presetValue = args[++i].toLowerCase();
167
- if (presetValue === 'complex' || presetValue === 'simple' || presetValue === 'merge') {
168
- result.preset = presetValue;
169
- }
170
- else {
171
- throw new Error(`Invalid preset: "${presetValue}". Must be one of: complex, simple, merge`);
172
- }
173
- }
174
- else if (arg === '--prompt' && args[i + 1]) {
175
- result.prompt = args[++i];
176
- }
177
- else if (arg === '--criteria' && args[i + 1]) {
178
- result.criteria = args[++i].split(',').map(c => c.trim()).filter(c => c);
179
- }
180
- else if (arg === '--model' && args[i + 1]) {
181
- result.model = args[++i];
182
- }
183
- else if (arg === '--task' && args[i + 1]) {
184
- result.taskSpecs.push(args[++i]);
185
- }
186
- else if (arg === '--add-lane' && args[i + 1]) {
187
- result.addLane = args[++i];
188
- }
189
- else if (arg === '--add-task' && args[i + 1]) {
190
- result.addTask = args[++i];
191
- }
192
- else if (!arg.startsWith('--') && !result.featureName) {
193
- result.featureName = arg;
194
- }
195
- i++;
196
- }
197
- return result;
198
- }
199
- function parseTaskSpec(spec) {
200
- // Format: "name|model|prompt|criteria1,criteria2|lane:task1,lane:task2|timeoutMs"
201
- const parts = spec.split('|');
202
- if (parts.length < 3) {
203
- throw new Error(`Invalid task spec: "${spec}". Expected format: "name|model|prompt[|criteria[|dependsOn[|timeout]]]"`);
204
- }
205
- const [name, model, prompt, _criteriaStr, depsStr, timeoutStr] = parts;
206
- const dependsOn = depsStr
207
- ? depsStr.split(',').map(d => d.trim()).filter(d => d)
208
- : undefined;
209
- const timeout = timeoutStr ? parseInt(timeoutStr) : undefined;
210
- return {
211
- name: name.trim(),
212
- model: model.trim() || 'sonnet-4.5',
213
- prompt: prompt.trim(),
214
- ...(dependsOn && dependsOn.length > 0 ? { dependsOn } : {}),
215
- ...(timeout ? { timeout } : {}),
216
- };
217
- }
218
- /**
219
- * Generate tasks based on preset template
220
- */
221
- function buildTasksFromPreset(preset, featureName, laneNumber, basePrompt, criteria, hasDependencies) {
222
- const tasks = [];
223
- // Plan document path - stored in the worktree root
224
- const planDocPath = `_cursorflow/PLAN_lane-${laneNumber}.md`;
225
- // If lane has dependencies, auto-apply merge preset logic
226
- const effectivePreset = hasDependencies && preset !== 'merge' ? preset : preset;
227
- switch (effectivePreset) {
228
- case 'complex':
229
- // plan → implement → test
230
- tasks.push({
231
- name: 'plan',
232
- model: 'sonnet-4.5-thinking',
233
- prompt: `# Planning: ${featureName} (Lane ${laneNumber})
234
-
235
- ## Goal
236
- Analyze the requirements and create a detailed implementation plan.
237
-
238
- ## Context
239
- ${basePrompt}
240
-
241
- ## Instructions
242
- 1. Understand the scope and requirements.
243
- 2. List all files that need to be created or modified.
244
- 3. Define data structures and interfaces.
245
- 4. Outline step-by-step implementation plan.
246
-
247
- ## Output
248
- **IMPORTANT: Save the plan document to \`${planDocPath}\`**
249
-
250
- The plan document should include:
251
- - Overview of the implementation approach
252
- - List of files to create/modify
253
- - Data structures and interfaces
254
- - Step-by-step implementation tasks
255
- - Potential risks and edge cases`,
256
- }, {
257
- name: 'implement',
258
- model: 'sonnet-4.5',
259
- prompt: `# Implementation: ${featureName} (Lane ${laneNumber})
260
-
261
- ## Goal
262
- Implement the planned changes.
263
-
264
- ## Context
265
- ${basePrompt}
266
-
267
- ## Plan Document
268
- **Read the plan from \`${planDocPath}\` before starting implementation.**
269
-
270
- ## Instructions
271
- 1. Read and understand the plan document at \`${planDocPath}\`.
272
- 2. Follow the plan step by step.
273
- 3. Implement all code changes.
274
- 4. Ensure no build errors.
275
- 5. Write necessary code comments.
276
- 6. Double-check all requirements before finishing.
277
-
278
- ## Important
279
- - Refer back to the plan document if unsure about any step.
280
- - Verify all edge cases from the plan are handled.
281
- - Ensure code follows project conventions.`,
282
- }, {
283
- name: 'test',
284
- model: 'sonnet-4.5',
285
- prompt: `# Testing: ${featureName} (Lane ${laneNumber})
286
-
287
- ## Goal
288
- Write comprehensive tests for the implementation.
289
-
290
- ## Plan Document
291
- **Refer to \`${planDocPath}\` for the list of features and edge cases to test.**
292
-
293
- ## Instructions
294
- 1. Review the plan document for test requirements.
295
- 2. Write unit tests for new functions/classes.
296
- 3. Write integration tests if applicable.
297
- 4. Ensure all tests pass.
298
- 5. Verify edge cases from the plan are covered.
299
- 6. Double-check that nothing is missing.
300
-
301
- ## Important
302
- - All tests must pass before completing.
303
- - Cover happy path and error cases from the plan.`,
304
- });
305
- break;
306
- case 'simple':
307
- // implement → test
308
- tasks.push({
309
- name: 'implement',
310
- model: 'sonnet-4.5',
311
- prompt: `# Implementation: ${featureName} (Lane ${laneNumber})
312
-
313
- ## Goal
314
- ${basePrompt}
315
-
316
- ## Instructions
317
- 1. Implement the required changes.
318
- 2. Ensure no build errors.
319
- 3. Handle edge cases appropriately.
320
- 4. Double-check all requirements before finishing.
321
-
322
- ## Important
323
- - Keep changes focused and minimal.
324
- - Follow existing code conventions.`,
325
- }, {
326
- name: 'test',
327
- model: 'sonnet-4.5',
328
- prompt: `# Testing: ${featureName} (Lane ${laneNumber})
329
-
330
- ## Goal
331
- Test the implementation thoroughly.
332
-
333
- ## Instructions
334
- 1. Write or update tests for the changes.
335
- 2. Run all related tests.
336
- 3. Ensure all tests pass.
337
- 4. Double-check edge cases.
338
-
339
- ## Important
340
- - All tests must pass before completing.`,
341
- });
342
- break;
343
- case 'merge':
344
- // merge → test (for dependent lanes)
345
- tasks.push({
346
- name: 'merge',
347
- model: 'sonnet-4.5',
348
- prompt: `# Merge & Integrate: ${featureName} (Lane ${laneNumber})
349
-
350
- ## Goal
351
- Merge dependent branches and resolve any conflicts.
352
-
353
- ## Instructions
354
- 1. The dependent branches have been automatically merged.
355
- 2. Check for any merge conflicts and resolve them.
356
- 3. Ensure all imports and dependencies are correct.
357
- 4. Verify the integrated code compiles without errors.
358
- 5. Fix any integration issues.
359
-
360
- ## Important
361
- - Resolve all conflicts cleanly.
362
- - Ensure code from all merged branches works together.
363
- - Check that no functionality was broken by the merge.`,
364
- }, {
365
- name: 'test',
366
- model: 'sonnet-4.5',
367
- prompt: `# Integration Testing: ${featureName} (Lane ${laneNumber})
368
-
369
- ## Goal
370
- Run comprehensive tests after the merge.
371
-
372
- ## Instructions
373
- 1. Run all unit tests.
374
- 2. Run integration tests.
375
- 3. Test that features from merged branches work together.
376
- 4. Verify no regressions were introduced.
377
- 5. Fix any failing tests.
378
-
379
- ## Important
380
- - All tests must pass.
381
- - Test the interaction between merged features.`,
382
- });
383
- break;
384
- }
385
- return tasks;
386
- }
387
- function buildTasksFromOptions(options, laneNumber, featureName, _hasDependencies = false) {
388
- // Priority: --task > --preset > --prompt alone > default
389
- // 1. Explicit --task specifications (highest priority)
390
- if (options.taskSpecs.length > 0) {
391
- const tasks = [];
392
- for (const spec of options.taskSpecs) {
393
- tasks.push(parseTaskSpec(spec));
394
- }
395
- return tasks;
396
- }
397
- // 2. Preset template (use when --preset specified)
398
- // --prompt serves as context when used with preset
399
- if (options.preset) {
400
- return buildTasksFromPreset(options.preset, featureName, laneNumber, options.prompt || `Implement ${featureName}`, options.criteria, false);
401
- }
402
- // 3. Single task from --prompt (only when no preset specified)
403
- if (options.prompt) {
404
- const task = {
405
- name: 'implement',
406
- model: options.model || 'sonnet-4.5',
407
- prompt: options.prompt,
408
- };
409
- return [task];
410
- }
411
- // 4. Default: complex preset
412
- return buildTasksFromPreset('complex', featureName, laneNumber, `Implement ${featureName}`, options.criteria, false);
413
- }
414
- function getDefaultConfig(laneNumber, featureName, tasks) {
415
- return {
416
- // Git Configuration
417
- // baseBranch is auto-detected from current branch at runtime
418
- branchPrefix: `${featureName.toLowerCase()}/lane-${laneNumber}-`,
419
- // Execution Settings
420
- timeout: 600000,
421
- // Dependency Policy
422
- dependencyPolicy: {
423
- allowDependencyChange: false,
424
- lockfileReadOnly: true,
425
- },
426
- // Lane Metadata
427
- laneNumber: laneNumber,
428
- devPort: 3000 + laneNumber,
429
- // Tasks
430
- tasks: tasks,
431
- };
432
- }
433
- function replacePlaceholders(obj, context) {
434
- if (typeof obj === 'string') {
435
- return obj
436
- .replace(/\{\{featureName\}\}/g, context.featureName)
437
- .replace(/\{\{laneNumber\}\}/g, String(context.laneNumber))
438
- .replace(/\{\{devPort\}\}/g, String(context.devPort));
439
- }
440
- if (Array.isArray(obj)) {
441
- return obj.map(item => replacePlaceholders(item, context));
442
- }
443
- if (obj !== null && typeof obj === 'object') {
444
- const result = {};
445
- for (const key in obj) {
446
- result[key] = replacePlaceholders(obj[key], context);
447
- }
448
- return result;
449
- }
450
- return obj;
451
- }
452
- function getNextLaneNumber(taskDir) {
453
- const files = fs.readdirSync(taskDir).filter(f => f.endsWith('.json'));
454
- let maxNum = 0;
455
- for (const file of files) {
456
- const match = file.match(/^(\d+)-/);
457
- if (match) {
458
- const num = parseInt(match[1]);
459
- if (num > maxNum)
460
- maxNum = num;
461
- }
462
- }
463
- return maxNum + 1;
464
- }
465
- function getFeatureNameFromDir(taskDir) {
466
- const dirName = path.basename(taskDir);
467
- // Format: YYMMDDHHMM_FeatureName
468
- const match = dirName.match(/^\d+_(.+)$/);
469
- return match ? match[1] : dirName;
470
- }
471
- async function addLaneToDir(options) {
472
- const taskDir = path.resolve(process.cwd(), options.addLane); // nosemgrep
473
- if (!fs.existsSync(taskDir)) {
474
- throw new Error(`Task directory not found: ${taskDir}`);
475
- }
476
- const featureName = getFeatureNameFromDir(taskDir);
477
- const laneNumber = getNextLaneNumber(taskDir);
478
- const laneName = `lane-${laneNumber}`;
479
- const fileName = `${laneNumber.toString().padStart(2, '0')}-${laneName}.json`;
480
- const filePath = (0, path_1.safeJoin)(taskDir, fileName);
481
- // Load template if provided
482
- let template = null;
483
- if (options.template) {
484
- template = await (0, template_1.resolveTemplate)(options.template);
485
- }
486
- let taskConfig;
487
- if (template) {
488
- taskConfig = { ...template, laneNumber, devPort: 3000 + laneNumber };
489
- }
490
- else {
491
- // Build tasks from options
492
- const tasks = buildTasksFromOptions(options, laneNumber, featureName, false);
493
- taskConfig = getDefaultConfig(laneNumber, featureName, tasks);
494
- }
495
- // Replace placeholders
496
- const finalConfig = replacePlaceholders(taskConfig, {
497
- featureName,
498
- laneNumber,
499
- devPort: 3000 + laneNumber,
500
- });
501
- // Use atomic write with wx flag to avoid TOCTOU race condition (unless force is set)
502
- // SECURITY NOTE: Writing user-defined task configuration to the file system.
503
- // The input is from CLI arguments and templates, used to generate CursorFlow lane files.
504
- try {
505
- const writeFlag = options.force ? 'w' : 'wx';
506
- fs.writeFileSync(filePath, JSON.stringify(finalConfig, null, 2) + '\n', { encoding: 'utf8', flag: writeFlag });
507
- }
508
- catch (err) {
509
- if (err.code === 'EEXIST') {
510
- throw new Error(`Lane file already exists: ${filePath}. Use --force to overwrite.`);
511
- }
512
- throw err;
513
- }
514
- const tasksList = finalConfig.tasks || [];
515
- const taskSummary = tasksList.map((t) => t.name).join(' → ');
516
- const presetInfo = options.preset ? ` [${options.preset}]` : (template ? ' [template]' : '');
517
- logger.success(`Added lane: ${fileName} [${taskSummary}]${presetInfo}`);
518
- logger.info(`Directory: ${taskDir}`);
519
- console.log(`\nNext steps:`);
520
- console.log(` 1. Validate: cursorflow doctor --tasks-dir ${taskDir}`);
521
- console.log(` 2. Run: cursorflow run ${taskDir}`);
522
- }
523
- async function addTaskToLane(options) {
524
- const laneFile = path.resolve(process.cwd(), options.addTask); // nosemgrep
525
- if (options.taskSpecs.length === 0) {
526
- throw new Error('No task specified. Use --task "name|model|prompt|criteria" to define a task.');
527
- }
528
- // Read existing config - let the error propagate if file doesn't exist (avoids TOCTOU)
529
- let existingConfig;
530
- try {
531
- existingConfig = JSON.parse(fs.readFileSync(laneFile, 'utf8'));
532
- }
533
- catch (err) {
534
- if (err.code === 'ENOENT') {
535
- throw new Error(`Lane file not found: ${laneFile}`);
536
- }
537
- throw err;
538
- }
539
- if (!existingConfig.tasks || !Array.isArray(existingConfig.tasks)) {
540
- existingConfig.tasks = [];
541
- }
542
- // Add new tasks
543
- const newTasks = [];
544
- for (const spec of options.taskSpecs) {
545
- const task = parseTaskSpec(spec);
546
- existingConfig.tasks.push(task);
547
- newTasks.push(task);
548
- }
549
- // Write back
550
- fs.writeFileSync(laneFile, JSON.stringify(existingConfig, null, 2) + '\n', 'utf8');
551
- const taskNames = newTasks.map(t => t.name).join(', ');
552
- logger.success(`Added task(s): ${taskNames}`);
553
- logger.info(`Updated: ${laneFile}`);
554
- const taskSummary = existingConfig.tasks.map((t) => t.name).join(' → ');
555
- logger.info(`Lane now has: ${taskSummary}`);
556
- }
557
- async function createNewFeature(options) {
558
- const config = (0, config_1.loadConfig)();
559
- const tasksBaseDir = (0, config_1.getTasksDir)(config);
560
- // Timestamp-based folder name (YYMMDDHHMM)
561
- const now = new Date();
562
- const timestamp = now.toISOString().replace(/[-T:]/g, '').substring(2, 12);
563
- const taskDirName = `${timestamp}_${options.featureName}`;
564
- const taskDir = (0, path_1.safeJoin)(tasksBaseDir, taskDirName);
565
- if (fs.existsSync(taskDir) && !options.force) {
566
- throw new Error(`Task directory already exists: ${taskDir}. Use --force to overwrite.`);
567
- }
568
- if (!fs.existsSync(taskDir)) {
569
- fs.mkdirSync(taskDir, { recursive: true });
570
- }
571
- logger.info(`Creating tasks in: ${path.relative(config.projectRoot, taskDir)}`);
572
- // Load template if provided (overrides --prompt/--task/--preset)
573
- let template = null;
574
- if (options.template) {
575
- template = await (0, template_1.resolveTemplate)(options.template);
576
- }
577
- const laneInfoList = [];
578
- for (let i = 1; i <= options.lanes; i++) {
579
- const laneName = `lane-${i}`;
580
- const fileName = `${i.toString().padStart(2, '0')}-${laneName}.json`;
581
- const filePath = (0, path_1.safeJoin)(taskDir, fileName);
582
- const devPort = 3000 + i;
583
- let taskConfig;
584
- let effectivePreset = options.preset || 'complex';
585
- if (template) {
586
- // Use template
587
- taskConfig = { ...template, laneNumber: i, devPort };
588
- effectivePreset = 'custom';
589
- }
590
- else {
591
- // Build from CLI options
592
- const tasks = buildTasksFromOptions(options, i, options.featureName, false);
593
- taskConfig = getDefaultConfig(i, options.featureName, tasks);
594
- }
595
- // Replace placeholders
596
- const finalConfig = replacePlaceholders(taskConfig, {
597
- featureName: options.featureName,
598
- laneNumber: i,
599
- devPort: devPort,
600
- });
601
- // SECURITY NOTE: Writing generated lane configuration (containing user prompts) to file system.
602
- fs.writeFileSync(filePath, JSON.stringify(finalConfig, null, 2) + '\n', 'utf8');
603
- const taskSummary = finalConfig.tasks?.map((t) => t.name).join(' → ') || 'default';
604
- const presetLabel = effectivePreset !== 'custom' ? ` [${effectivePreset}]` : '';
605
- logger.success(`Created: ${fileName} [${taskSummary}]${presetLabel}`);
606
- laneInfoList.push({ name: laneName, fileName, preset: effectivePreset });
607
- }
608
- // Create README
609
- const readmePath = (0, path_1.safeJoin)(taskDir, 'README.md');
610
- const readme = `# Task: ${options.featureName}
611
-
612
- Prepared at: ${now.toISOString()}
613
- Lanes: ${options.lanes}
614
-
615
- ## How to Run
616
-
617
- \`\`\`bash
618
- # 1. Validate configuration
619
- cursorflow doctor --tasks-dir ${path.relative(config.projectRoot, taskDir)}
620
-
621
- # 2. Run
622
- cursorflow run ${path.relative(config.projectRoot, taskDir)}
623
- \`\`\`
624
-
625
- ## Lanes
626
-
627
- ${laneInfoList.map(l => `- **${l.fileName.replace('.json', '')}** [${l.preset}]`).join('\n')}
628
-
629
- ## Task-Level Dependencies
630
-
631
- To make a task wait for another task to complete before starting, use the \`dependsOn\` field:
632
-
633
- \`\`\`json
634
- {
635
- "tasks": [
636
- {
637
- "name": "my-task",
638
- "prompt": "...",
639
- "dependsOn": ["other-lane:other-task"]
640
- }
641
- ]
642
- }
643
- \`\`\`
644
-
645
- ## Modifying Tasks
646
-
647
- \`\`\`bash
648
- # Add a new lane
649
- cursorflow prepare --add-lane ${path.relative(config.projectRoot, taskDir)} --preset complex
650
-
651
- # Add task to existing lane
652
- cursorflow prepare --add-task ${path.relative(config.projectRoot, taskDir)}/01-lane-1.json \\
653
- --task "verify|sonnet-4.5|Verify requirements|All met"
654
- \`\`\`
655
- `;
656
- fs.writeFileSync(readmePath, readme, 'utf8');
657
- logger.success('Created README.md');
658
- logger.section('✅ Preparation complete!');
659
- console.log(`\nNext steps:`);
660
- console.log(` 1. (Optional) Add more lanes/tasks`);
661
- console.log(` 2. Validate: cursorflow doctor --tasks-dir ${path.relative(config.projectRoot, taskDir)}`);
662
- console.log(` 3. Run: cursorflow run ${path.relative(config.projectRoot, taskDir)}`);
663
- console.log('');
664
- }
665
- async function prepare(args) {
666
- const options = parseArgs(args);
667
- if (options.help) {
668
- printHelp();
669
- return;
670
- }
671
- // Mode 1: Add task to existing lane
672
- if (options.addTask) {
673
- await addTaskToLane(options);
674
- return;
675
- }
676
- // Mode 2: Add lane to existing directory
677
- if (options.addLane) {
678
- await addLaneToDir(options);
679
- return;
680
- }
681
- // Mode 3: Create new feature (requires featureName)
682
- if (!options.featureName) {
683
- printHelp();
684
- process.exit(1);
685
- return;
686
- }
687
- await createNewFeature(options);
688
- }
689
- module.exports = prepare;
690
- //# sourceMappingURL=prepare.js.map