ai-sdlc 0.2.0-alpha.60 → 0.2.0-alpha.61

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Validation error detail
3
+ */
4
+ export interface ValidationError {
5
+ /** Story ID that failed validation */
6
+ storyId: string;
7
+ /** Error message describing the issue */
8
+ message: string;
9
+ }
10
+ /**
11
+ * Result of batch validation
12
+ */
13
+ export interface BatchValidationResult {
14
+ /** Whether all validations passed */
15
+ valid: boolean;
16
+ /** List of valid story IDs that passed validation */
17
+ validStoryIds: string[];
18
+ /** List of validation errors */
19
+ errors: ValidationError[];
20
+ }
21
+ /**
22
+ * Parse comma-separated story ID list
23
+ * Handles whitespace and filters empty strings
24
+ *
25
+ * @param input - Raw comma-separated string (e.g., "S-001, S-002 , S-003")
26
+ * @returns Array of trimmed story IDs
27
+ *
28
+ * @example
29
+ * parseStoryIdList("S-001,S-002,S-003") // => ["S-001", "S-002", "S-003"]
30
+ * parseStoryIdList("S-001, S-002 , S-003") // => ["S-001", "S-002", "S-003"]
31
+ * parseStoryIdList("") // => []
32
+ */
33
+ export declare function parseStoryIdList(input: string): string[];
34
+ /**
35
+ * Deduplicate story IDs while preserving order
36
+ * First occurrence is kept, subsequent duplicates are removed
37
+ *
38
+ * @param storyIds - Array of story IDs (may contain duplicates)
39
+ * @returns Array with duplicates removed
40
+ *
41
+ * @example
42
+ * deduplicateStoryIds(["S-001", "S-002", "S-001"]) // => ["S-001", "S-002"]
43
+ * deduplicateStoryIds(["S-001", "S-001", "S-001"]) // => ["S-001"]
44
+ */
45
+ export declare function deduplicateStoryIds(storyIds: string[]): string[];
46
+ /**
47
+ * Validate story ID format
48
+ * Story IDs must match pattern: S-\d+ (e.g., S-001, S-123)
49
+ *
50
+ * @param storyId - Story ID to validate
51
+ * @returns true if format is valid, false otherwise
52
+ *
53
+ * @example
54
+ * validateStoryIdFormat("S-001") // => true
55
+ * validateStoryIdFormat("S-123") // => true
56
+ * validateStoryIdFormat("s-001") // => true (case-insensitive)
57
+ * validateStoryIdFormat("INVALID") // => false
58
+ * validateStoryIdFormat("S-") // => false
59
+ */
60
+ export declare function validateStoryIdFormat(storyId: string): boolean;
61
+ /**
62
+ * Validate all story IDs in a batch
63
+ * Checks format and existence for each story
64
+ *
65
+ * @param storyIds - Array of story IDs to validate
66
+ * @param sdlcRoot - Root directory of .ai-sdlc
67
+ * @returns BatchValidationResult with valid IDs and errors
68
+ *
69
+ * @example
70
+ * const result = validateStoryIds(["S-001", "S-002", "INVALID"], sdlcRoot);
71
+ * if (result.valid) {
72
+ * // All stories exist and are valid
73
+ * processStories(result.validStoryIds);
74
+ * } else {
75
+ * // Show errors
76
+ * result.errors.forEach(err => console.log(err.message));
77
+ * }
78
+ */
79
+ export declare function validateStoryIds(storyIds: string[], sdlcRoot: string): BatchValidationResult;
80
+ //# sourceMappingURL=batch-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-validator.d.ts","sourceRoot":"","sources":["../../src/cli/batch-validator.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,qDAAqD;IACrD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gCAAgC;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CASxD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAahE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAAE,EAClB,QAAQ,EAAE,MAAM,GACf,qBAAqB,CAwCvB"}
@@ -0,0 +1,121 @@
1
+ import { findStoryById } from '../core/story.js';
2
+ /**
3
+ * Parse comma-separated story ID list
4
+ * Handles whitespace and filters empty strings
5
+ *
6
+ * @param input - Raw comma-separated string (e.g., "S-001, S-002 , S-003")
7
+ * @returns Array of trimmed story IDs
8
+ *
9
+ * @example
10
+ * parseStoryIdList("S-001,S-002,S-003") // => ["S-001", "S-002", "S-003"]
11
+ * parseStoryIdList("S-001, S-002 , S-003") // => ["S-001", "S-002", "S-003"]
12
+ * parseStoryIdList("") // => []
13
+ */
14
+ export function parseStoryIdList(input) {
15
+ if (!input || input.trim() === '') {
16
+ return [];
17
+ }
18
+ return input
19
+ .split(',')
20
+ .map(id => id.trim())
21
+ .filter(id => id.length > 0);
22
+ }
23
+ /**
24
+ * Deduplicate story IDs while preserving order
25
+ * First occurrence is kept, subsequent duplicates are removed
26
+ *
27
+ * @param storyIds - Array of story IDs (may contain duplicates)
28
+ * @returns Array with duplicates removed
29
+ *
30
+ * @example
31
+ * deduplicateStoryIds(["S-001", "S-002", "S-001"]) // => ["S-001", "S-002"]
32
+ * deduplicateStoryIds(["S-001", "S-001", "S-001"]) // => ["S-001"]
33
+ */
34
+ export function deduplicateStoryIds(storyIds) {
35
+ const seen = new Set();
36
+ const result = [];
37
+ for (const id of storyIds) {
38
+ const normalized = id.toUpperCase(); // Case-insensitive deduplication
39
+ if (!seen.has(normalized)) {
40
+ seen.add(normalized);
41
+ result.push(id);
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+ /**
47
+ * Validate story ID format
48
+ * Story IDs must match pattern: S-\d+ (e.g., S-001, S-123)
49
+ *
50
+ * @param storyId - Story ID to validate
51
+ * @returns true if format is valid, false otherwise
52
+ *
53
+ * @example
54
+ * validateStoryIdFormat("S-001") // => true
55
+ * validateStoryIdFormat("S-123") // => true
56
+ * validateStoryIdFormat("s-001") // => true (case-insensitive)
57
+ * validateStoryIdFormat("INVALID") // => false
58
+ * validateStoryIdFormat("S-") // => false
59
+ */
60
+ export function validateStoryIdFormat(storyId) {
61
+ return /^S-\d+$/i.test(storyId);
62
+ }
63
+ /**
64
+ * Validate all story IDs in a batch
65
+ * Checks format and existence for each story
66
+ *
67
+ * @param storyIds - Array of story IDs to validate
68
+ * @param sdlcRoot - Root directory of .ai-sdlc
69
+ * @returns BatchValidationResult with valid IDs and errors
70
+ *
71
+ * @example
72
+ * const result = validateStoryIds(["S-001", "S-002", "INVALID"], sdlcRoot);
73
+ * if (result.valid) {
74
+ * // All stories exist and are valid
75
+ * processStories(result.validStoryIds);
76
+ * } else {
77
+ * // Show errors
78
+ * result.errors.forEach(err => console.log(err.message));
79
+ * }
80
+ */
81
+ export function validateStoryIds(storyIds, sdlcRoot) {
82
+ const result = {
83
+ valid: true,
84
+ validStoryIds: [],
85
+ errors: [],
86
+ };
87
+ for (const storyId of storyIds) {
88
+ // Check format
89
+ if (!validateStoryIdFormat(storyId)) {
90
+ result.valid = false;
91
+ result.errors.push({
92
+ storyId,
93
+ message: `Invalid story ID format: ${storyId} (expected format: S-001, S-123, etc.)`,
94
+ });
95
+ continue;
96
+ }
97
+ // Check existence
98
+ try {
99
+ const story = findStoryById(sdlcRoot, storyId);
100
+ if (!story) {
101
+ result.valid = false;
102
+ result.errors.push({
103
+ storyId,
104
+ message: `Story not found: ${storyId}`,
105
+ });
106
+ }
107
+ else {
108
+ result.validStoryIds.push(storyId);
109
+ }
110
+ }
111
+ catch (error) {
112
+ result.valid = false;
113
+ result.errors.push({
114
+ storyId,
115
+ message: `Error validating story ${storyId}: ${error instanceof Error ? error.message : String(error)}`,
116
+ });
117
+ }
118
+ }
119
+ return result;
120
+ }
121
+ //# sourceMappingURL=batch-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-validator.js","sourceRoot":"","sources":["../../src/cli/batch-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAyBjD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAClC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAkB;IACpD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,UAAU,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,iCAAiC;QACtE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAkB,EAClB,QAAgB;IAEhB,MAAM,MAAM,GAA0B;QACpC,KAAK,EAAE,IAAI;QACX,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,eAAe;QACf,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;gBACjB,OAAO;gBACP,OAAO,EAAE,4BAA4B,OAAO,wCAAwC;aACrF,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,kBAAkB;QAClB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;gBACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;oBACjB,OAAO;oBACP,OAAO,EAAE,oBAAoB,OAAO,EAAE;iBACvC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;gBACjB,OAAO;gBACP,OAAO,EAAE,0BAA0B,OAAO,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;aACxG,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -61,6 +61,7 @@ export declare function run(options: {
61
61
  dryRun?: boolean;
62
62
  continue?: boolean;
63
63
  story?: string;
64
+ batch?: string;
64
65
  step?: string;
65
66
  maxIterations?: string;
66
67
  watch?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -287,6 +287,35 @@ function validateAutoStoryOptions(options) {
287
287
  ' - ai-sdlc run --story <id> --step <phase> (single phase)');
288
288
  }
289
289
  }
290
+ /**
291
+ * Validates flag combinations for --batch conflicts
292
+ * @throws Error if conflicting flags are detected
293
+ */
294
+ function validateBatchOptions(options) {
295
+ if (!options.batch) {
296
+ return; // No batch flag, nothing to validate
297
+ }
298
+ // --batch and --story are mutually exclusive
299
+ if (options.story) {
300
+ throw new Error('Cannot combine --batch with --story flag.\n' +
301
+ 'Use either:\n' +
302
+ ' - ai-sdlc run --batch S-001,S-002,S-003 (batch processing)\n' +
303
+ ' - ai-sdlc run --auto --story <id> (single story)');
304
+ }
305
+ // --batch and --watch are mutually exclusive
306
+ if (options.watch) {
307
+ throw new Error('Cannot combine --batch with --watch flag.\n' +
308
+ 'Use either:\n' +
309
+ ' - ai-sdlc run --batch S-001,S-002,S-003 (batch processing)\n' +
310
+ ' - ai-sdlc run --watch (daemon mode)');
311
+ }
312
+ // --batch and --continue are mutually exclusive
313
+ if (options.continue) {
314
+ throw new Error('Cannot combine --batch with --continue flag.\n' +
315
+ 'Batch mode does not support resuming from checkpoints.\n' +
316
+ 'Use: ai-sdlc run --batch S-001,S-002,S-003');
317
+ }
318
+ }
290
319
  /**
291
320
  * Determines if a specific phase should be executed based on story state
292
321
  * @param story The story to check
@@ -587,6 +616,149 @@ export async function preFlightConflictCheck(targetStory, sdlcRoot, options) {
587
616
  return { proceed: true, warnings: ['Conflict detection failed'] };
588
617
  }
589
618
  }
619
+ /**
620
+ * Process multiple stories sequentially through full SDLC
621
+ * Internal function used by batch mode
622
+ */
623
+ async function processBatchInternal(storyIds, sdlcRoot, options) {
624
+ const startTime = Date.now();
625
+ const config = loadConfig();
626
+ const c = getThemedChalk(config);
627
+ const { formatBatchProgress, formatBatchSummary, logStoryCompletion, promptContinueOnError } = await import('./batch-processor.js');
628
+ const result = {
629
+ total: storyIds.length,
630
+ succeeded: 0,
631
+ failed: 0,
632
+ skipped: 0,
633
+ errors: [],
634
+ duration: 0,
635
+ };
636
+ console.log();
637
+ console.log(c.bold('═══ Starting Batch Processing ═══'));
638
+ console.log(c.dim(` Stories: ${storyIds.join(', ')}`));
639
+ console.log(c.dim(` Dry run: ${options.dryRun ? 'yes' : 'no'}`));
640
+ console.log();
641
+ // Process each story sequentially
642
+ for (let i = 0; i < storyIds.length; i++) {
643
+ const storyId = storyIds[i];
644
+ // Get story and check status
645
+ let story;
646
+ try {
647
+ story = getStory(sdlcRoot, storyId);
648
+ }
649
+ catch (error) {
650
+ result.failed++;
651
+ result.errors.push({
652
+ storyId,
653
+ error: `Story not found: ${error instanceof Error ? error.message : String(error)}`,
654
+ });
655
+ console.log(c.error(`[${i + 1}/${storyIds.length}] ✗ Story not found: ${storyId}`));
656
+ console.log();
657
+ // Ask if user wants to continue (or abort in non-interactive)
658
+ const shouldContinue = await promptContinueOnError(storyId, c);
659
+ if (!shouldContinue) {
660
+ console.log(c.warning('Batch processing aborted.'));
661
+ break;
662
+ }
663
+ continue;
664
+ }
665
+ // Skip if already done
666
+ if (story.frontmatter.status === 'done') {
667
+ result.skipped++;
668
+ console.log(c.dim(`[${i + 1}/${storyIds.length}] ⊘ Skipping ${storyId} (already completed)`));
669
+ console.log();
670
+ continue;
671
+ }
672
+ // Show progress header
673
+ const progress = {
674
+ currentIndex: i,
675
+ total: storyIds.length,
676
+ currentStory: story,
677
+ };
678
+ console.log(c.info(formatBatchProgress(progress)));
679
+ console.log();
680
+ // Dry-run mode: just show what would be done
681
+ if (options.dryRun) {
682
+ console.log(c.dim(' Would process story through full SDLC'));
683
+ console.log(c.dim(` Status: ${story.frontmatter.status}`));
684
+ console.log();
685
+ result.succeeded++;
686
+ continue;
687
+ }
688
+ // Process story through full SDLC by recursively calling run()
689
+ // We set auto: true to ensure full SDLC execution
690
+ try {
691
+ await run({
692
+ auto: true,
693
+ story: storyId,
694
+ dryRun: false,
695
+ worktree: options.worktree,
696
+ force: options.force,
697
+ });
698
+ // Check if story completed successfully (moved to done)
699
+ const finalStory = getStory(sdlcRoot, storyId);
700
+ if (finalStory.frontmatter.status === 'done') {
701
+ result.succeeded++;
702
+ logStoryCompletion(storyId, true, c);
703
+ }
704
+ else {
705
+ // Story didn't reach done state - treat as failure
706
+ result.failed++;
707
+ result.errors.push({
708
+ storyId,
709
+ error: `Story did not complete (status: ${finalStory.frontmatter.status})`,
710
+ });
711
+ logStoryCompletion(storyId, false, c);
712
+ // Ask if user wants to continue (or abort in non-interactive)
713
+ const shouldContinue = await promptContinueOnError(storyId, c);
714
+ if (!shouldContinue) {
715
+ console.log(c.warning('Batch processing aborted.'));
716
+ break;
717
+ }
718
+ }
719
+ }
720
+ catch (error) {
721
+ result.failed++;
722
+ result.errors.push({
723
+ storyId,
724
+ error: error instanceof Error ? error.message : String(error),
725
+ });
726
+ logStoryCompletion(storyId, false, c);
727
+ // Ask if user wants to continue (or abort in non-interactive)
728
+ const shouldContinue = await promptContinueOnError(storyId, c);
729
+ if (!shouldContinue) {
730
+ console.log(c.warning('Batch processing aborted.'));
731
+ break;
732
+ }
733
+ }
734
+ console.log();
735
+ }
736
+ // Display final summary
737
+ result.duration = Date.now() - startTime;
738
+ const summaryLines = formatBatchSummary(result);
739
+ summaryLines.forEach((line) => {
740
+ if (line.includes('✓')) {
741
+ console.log(c.success(line));
742
+ }
743
+ else if (line.includes('✗')) {
744
+ console.log(c.error(line));
745
+ }
746
+ else if (line.includes('⊘')) {
747
+ console.log(c.warning(line));
748
+ }
749
+ else if (line.startsWith(' -')) {
750
+ console.log(c.dim(line));
751
+ }
752
+ else {
753
+ console.log(line);
754
+ }
755
+ });
756
+ // Return non-zero exit code if any failures occurred
757
+ if (result.failed > 0) {
758
+ process.exitCode = 1;
759
+ }
760
+ return result;
761
+ }
590
762
  /**
591
763
  * Run the workflow (process one action or all)
592
764
  */
@@ -626,6 +798,51 @@ export async function run(options) {
626
798
  await startDaemon({ maxIterations: maxIterationsOverride });
627
799
  return; // Daemon runs indefinitely
628
800
  }
801
+ // Handle batch mode
802
+ if (options.batch) {
803
+ // Validate batch options first
804
+ try {
805
+ validateBatchOptions(options);
806
+ }
807
+ catch (error) {
808
+ console.log(c.error(`Error: ${error instanceof Error ? error.message : String(error)}`));
809
+ return;
810
+ }
811
+ // Import batch validation modules
812
+ const { parseStoryIdList, deduplicateStoryIds, validateStoryIds } = await import('./batch-validator.js');
813
+ // Parse and validate story IDs
814
+ const rawStoryIds = parseStoryIdList(options.batch);
815
+ if (rawStoryIds.length === 0) {
816
+ console.log(c.error('Error: Empty batch - no story IDs provided'));
817
+ console.log(c.dim('Usage: ai-sdlc run --batch S-001,S-002,S-003'));
818
+ return;
819
+ }
820
+ // Deduplicate story IDs
821
+ const storyIds = deduplicateStoryIds(rawStoryIds);
822
+ if (storyIds.length < rawStoryIds.length) {
823
+ const duplicateCount = rawStoryIds.length - storyIds.length;
824
+ console.log(c.dim(`Note: Removed ${duplicateCount} duplicate story ID(s)`));
825
+ }
826
+ // Validate all stories exist before processing
827
+ const validation = validateStoryIds(storyIds, sdlcRoot);
828
+ if (!validation.valid) {
829
+ console.log(c.error('Error: Batch validation failed'));
830
+ console.log();
831
+ for (const error of validation.errors) {
832
+ console.log(c.error(` - ${error.message}`));
833
+ }
834
+ console.log();
835
+ console.log(c.dim('Fix the errors above and try again.'));
836
+ return;
837
+ }
838
+ // Process the batch using internal function
839
+ await processBatchInternal(storyIds, sdlcRoot, {
840
+ dryRun: options.dryRun,
841
+ worktree: options.worktree,
842
+ force: options.force,
843
+ });
844
+ return; // Batch processing complete
845
+ }
629
846
  // Valid step names for --step option
630
847
  const validSteps = ['refine', 'research', 'plan', 'implement', 'review'];
631
848
  // Validate --step option early