ai-sdlc 0.3.0 → 0.3.1-alpha.0-alpha.1
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/README.md +78 -6
- package/dist/agents/implementation.d.ts +5 -0
- package/dist/agents/implementation.d.ts.map +1 -1
- package/dist/agents/implementation.js +78 -9
- package/dist/agents/implementation.js.map +1 -1
- package/dist/agents/planning.d.ts.map +1 -1
- package/dist/agents/planning.js +10 -3
- package/dist/agents/planning.js.map +1 -1
- package/dist/agents/research.d.ts.map +1 -1
- package/dist/agents/research.js +14 -6
- package/dist/agents/research.js.map +1 -1
- package/dist/agents/review.d.ts +81 -0
- package/dist/agents/review.d.ts.map +1 -1
- package/dist/agents/review.js +405 -39
- package/dist/agents/review.js.map +1 -1
- package/dist/agents/single-task.d.ts +1 -1
- package/dist/agents/single-task.d.ts.map +1 -1
- package/dist/agents/single-task.js +1 -1
- package/dist/cli/batch-processor.d.ts +64 -0
- package/dist/cli/batch-processor.d.ts.map +1 -0
- package/dist/cli/batch-processor.js +85 -0
- package/dist/cli/batch-processor.js.map +1 -0
- package/dist/cli/batch-validator.d.ts +80 -0
- package/dist/cli/batch-validator.d.ts.map +1 -0
- package/dist/cli/batch-validator.js +121 -0
- package/dist/cli/batch-validator.js.map +1 -0
- package/dist/cli/commands.d.ts +7 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +285 -1
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/dependency-resolver.d.ts +49 -0
- package/dist/cli/dependency-resolver.d.ts.map +1 -0
- package/dist/cli/dependency-resolver.js +133 -0
- package/dist/cli/dependency-resolver.js.map +1 -0
- package/dist/cli/epic-processor.d.ts +16 -0
- package/dist/cli/epic-processor.d.ts.map +1 -0
- package/dist/cli/epic-processor.js +489 -0
- package/dist/cli/epic-processor.js.map +1 -0
- package/dist/cli/formatting.d.ts +15 -0
- package/dist/cli/formatting.d.ts.map +1 -1
- package/dist/cli/formatting.js +19 -0
- package/dist/cli/formatting.js.map +1 -1
- package/dist/cli/progress-dashboard.d.ts +58 -0
- package/dist/cli/progress-dashboard.d.ts.map +1 -0
- package/dist/cli/progress-dashboard.js +216 -0
- package/dist/cli/progress-dashboard.js.map +1 -0
- package/dist/cli/table-renderer.d.ts.map +1 -1
- package/dist/cli/table-renderer.js +5 -1
- package/dist/cli/table-renderer.js.map +1 -1
- package/dist/core/agent-executor.d.ts +13 -0
- package/dist/core/agent-executor.d.ts.map +1 -0
- package/dist/core/agent-executor.js +153 -0
- package/dist/core/agent-executor.js.map +1 -0
- package/dist/core/config.d.ts +16 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +113 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/git-utils.d.ts +19 -0
- package/dist/core/git-utils.d.ts.map +1 -1
- package/dist/core/git-utils.js +58 -0
- package/dist/core/git-utils.js.map +1 -1
- package/dist/core/kanban.d.ts +125 -1
- package/dist/core/kanban.d.ts.map +1 -1
- package/dist/core/kanban.js +371 -4
- package/dist/core/kanban.js.map +1 -1
- package/dist/core/orchestrator.d.ts +63 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +320 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/story.d.ts +84 -0
- package/dist/core/story.d.ts.map +1 -1
- package/dist/core/story.js +159 -14
- package/dist/core/story.js.map +1 -1
- package/dist/core/worktree.d.ts +7 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +44 -0
- package/dist/core/worktree.js.map +1 -1
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +252 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -61,12 +61,19 @@ export declare function run(options: {
|
|
|
61
61
|
dryRun?: boolean;
|
|
62
62
|
continue?: boolean;
|
|
63
63
|
story?: string;
|
|
64
|
+
batch?: string;
|
|
65
|
+
epic?: string;
|
|
66
|
+
maxConcurrent?: string;
|
|
67
|
+
concurrent?: string;
|
|
64
68
|
step?: string;
|
|
65
69
|
maxIterations?: string;
|
|
66
70
|
watch?: boolean;
|
|
67
71
|
force?: boolean;
|
|
68
72
|
worktree?: boolean;
|
|
69
73
|
clean?: boolean;
|
|
74
|
+
keepWorktrees?: boolean;
|
|
75
|
+
merge?: boolean;
|
|
76
|
+
mergeStrategy?: string;
|
|
70
77
|
}): Promise<void>;
|
|
71
78
|
/**
|
|
72
79
|
* Phase information for RPIV display
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,EAAU,UAAU,EAA0H,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA4BvM;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAyB1C;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmF1E;AA2DD;;GAEG;AACH,wBAAsB,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyGpF;
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/commands.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as readline from 'readline';
|
|
|
5
5
|
import { spawnSync } from 'child_process';
|
|
6
6
|
import { getSdlcRoot, loadConfig, initConfig, validateWorktreeBasePath, DEFAULT_WORKTREE_CONFIG } from '../core/config.js';
|
|
7
7
|
import { initializeKanban, kanbanExists, assessState, getBoardStats, findStoryBySlug, findStoriesByStatus } from '../core/kanban.js';
|
|
8
|
-
import { createStory, parseStory, resetRPIVCycle, isAtMaxRetries, unblockStory, getStory, findStoryById, updateStoryField, writeStory, sanitizeStoryId, autoCompleteStoryAfterReview, incrementImplementationRetryCount, getEffectiveMaxImplementationRetries, isAtMaxImplementationRetries, updateStoryStatus } from '../core/story.js';
|
|
8
|
+
import { createStory, parseStory, resetRPIVCycle, isAtMaxRetries, unblockStory, getStory, findStoryById, updateStoryField, writeStory, sanitizeStoryId, autoCompleteStoryAfterReview, incrementImplementationRetryCount, resetImplementationRetryCount, getEffectiveMaxImplementationRetries, isAtMaxImplementationRetries, updateStoryStatus } from '../core/story.js';
|
|
9
9
|
import { GitWorktreeService, getLastCompletedPhase, getNextPhase } from '../core/worktree.js';
|
|
10
10
|
import { ReviewDecision } from '../types/index.js';
|
|
11
11
|
import { getThemedChalk } from '../core/theme.js';
|
|
@@ -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,115 @@ export async function run(options) {
|
|
|
626
798
|
await startDaemon({ maxIterations: maxIterationsOverride });
|
|
627
799
|
return; // Daemon runs indefinitely
|
|
628
800
|
}
|
|
801
|
+
// Handle concurrent mode
|
|
802
|
+
if (options.concurrent) {
|
|
803
|
+
const concurrency = parseInt(options.concurrent, 10);
|
|
804
|
+
// Validate concurrency value
|
|
805
|
+
if (isNaN(concurrency) || concurrency <= 0) {
|
|
806
|
+
console.log(c.warning(`Warning: Invalid --concurrent value "${options.concurrent}". Defaulting to 1 (single-story mode).`));
|
|
807
|
+
// Fall through to normal single-story mode
|
|
808
|
+
}
|
|
809
|
+
else if (concurrency > 1) {
|
|
810
|
+
// Import orchestrator and run concurrent mode
|
|
811
|
+
const { Orchestrator } = await import('../core/orchestrator.js');
|
|
812
|
+
const { findStoriesByStatus } = await import('../core/kanban.js');
|
|
813
|
+
// Query database for ready stories, sorted by priority
|
|
814
|
+
const readyStories = findStoriesByStatus(sdlcRoot, 'ready');
|
|
815
|
+
if (readyStories.length === 0) {
|
|
816
|
+
console.log(c.info('No ready stories found. Add stories to the ready column in the kanban board.'));
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
// Limit to available stories
|
|
820
|
+
const storiesToRun = readyStories.slice(0, Math.min(concurrency, readyStories.length));
|
|
821
|
+
console.log(c.info(`🚀 Running ${storiesToRun.length} stories concurrently (concurrency: ${concurrency})`));
|
|
822
|
+
// Create orchestrator and execute
|
|
823
|
+
const orchestrator = new Orchestrator({
|
|
824
|
+
concurrency,
|
|
825
|
+
shutdownTimeout: 10000,
|
|
826
|
+
keepWorktrees: options.keepWorktrees,
|
|
827
|
+
});
|
|
828
|
+
const results = await orchestrator.execute(storiesToRun);
|
|
829
|
+
// Report results
|
|
830
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
831
|
+
const failed = results.filter((r) => !r.success).length;
|
|
832
|
+
console.log('');
|
|
833
|
+
console.log(c.info('═══════════════════════════════════════════'));
|
|
834
|
+
console.log(c.info('Concurrent Execution Summary'));
|
|
835
|
+
console.log(c.info('═══════════════════════════════════════════'));
|
|
836
|
+
console.log(c.success(`✅ Succeeded: ${succeeded}`));
|
|
837
|
+
if (failed > 0) {
|
|
838
|
+
console.log(c.error(`❌ Failed: ${failed}`));
|
|
839
|
+
}
|
|
840
|
+
console.log('');
|
|
841
|
+
// Exit with error code if any failed
|
|
842
|
+
if (failed > 0) {
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
// Handle epic mode
|
|
849
|
+
if (options.epic) {
|
|
850
|
+
const { processEpic } = await import('./epic-processor.js');
|
|
851
|
+
const maxConcurrent = options.maxConcurrent ? parseInt(options.maxConcurrent, 10) : undefined;
|
|
852
|
+
// Parse merge strategy if provided
|
|
853
|
+
const mergeStrategy = options.mergeStrategy;
|
|
854
|
+
const exitCode = await processEpic({
|
|
855
|
+
epicId: options.epic,
|
|
856
|
+
maxConcurrent,
|
|
857
|
+
dryRun: options.dryRun,
|
|
858
|
+
force: options.force,
|
|
859
|
+
keepWorktrees: options.keepWorktrees,
|
|
860
|
+
merge: options.merge,
|
|
861
|
+
mergeStrategy,
|
|
862
|
+
});
|
|
863
|
+
process.exit(exitCode);
|
|
864
|
+
}
|
|
865
|
+
// Handle batch mode
|
|
866
|
+
if (options.batch) {
|
|
867
|
+
// Validate batch options first
|
|
868
|
+
try {
|
|
869
|
+
validateBatchOptions(options);
|
|
870
|
+
}
|
|
871
|
+
catch (error) {
|
|
872
|
+
console.log(c.error(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
// Import batch validation modules
|
|
876
|
+
const { parseStoryIdList, deduplicateStoryIds, validateStoryIds } = await import('./batch-validator.js');
|
|
877
|
+
// Parse and validate story IDs
|
|
878
|
+
const rawStoryIds = parseStoryIdList(options.batch);
|
|
879
|
+
if (rawStoryIds.length === 0) {
|
|
880
|
+
console.log(c.error('Error: Empty batch - no story IDs provided'));
|
|
881
|
+
console.log(c.dim('Usage: ai-sdlc run --batch S-001,S-002,S-003'));
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
// Deduplicate story IDs
|
|
885
|
+
const storyIds = deduplicateStoryIds(rawStoryIds);
|
|
886
|
+
if (storyIds.length < rawStoryIds.length) {
|
|
887
|
+
const duplicateCount = rawStoryIds.length - storyIds.length;
|
|
888
|
+
console.log(c.dim(`Note: Removed ${duplicateCount} duplicate story ID(s)`));
|
|
889
|
+
}
|
|
890
|
+
// Validate all stories exist before processing
|
|
891
|
+
const validation = validateStoryIds(storyIds, sdlcRoot);
|
|
892
|
+
if (!validation.valid) {
|
|
893
|
+
console.log(c.error('Error: Batch validation failed'));
|
|
894
|
+
console.log();
|
|
895
|
+
for (const error of validation.errors) {
|
|
896
|
+
console.log(c.error(` - ${error.message}`));
|
|
897
|
+
}
|
|
898
|
+
console.log();
|
|
899
|
+
console.log(c.dim('Fix the errors above and try again.'));
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
// Process the batch using internal function
|
|
903
|
+
await processBatchInternal(storyIds, sdlcRoot, {
|
|
904
|
+
dryRun: options.dryRun,
|
|
905
|
+
worktree: options.worktree,
|
|
906
|
+
force: options.force,
|
|
907
|
+
});
|
|
908
|
+
return; // Batch processing complete
|
|
909
|
+
}
|
|
629
910
|
// Valid step names for --step option
|
|
630
911
|
const validSteps = ['refine', 'research', 'plan', 'implement', 'review'];
|
|
631
912
|
// Validate --step option early
|
|
@@ -1693,6 +1974,9 @@ async function executeAction(action, sdlcRoot) {
|
|
|
1693
1974
|
story = await autoCompleteStoryAfterReview(story, config, reviewResult);
|
|
1694
1975
|
// Log auto-completion if it occurred
|
|
1695
1976
|
if (reviewResult.decision === ReviewDecision.APPROVED && config.reviewConfig.autoCompleteOnApproval) {
|
|
1977
|
+
// Reset implementation retry count on successful review approval
|
|
1978
|
+
await resetImplementationRetryCount(story);
|
|
1979
|
+
storyLogger?.log('INFO', 'Implementation retry count reset after review approval');
|
|
1696
1980
|
spinner.text = c.success('Review approved - auto-completing story');
|
|
1697
1981
|
storyLogger?.log('INFO', `Story auto-completed after review approval: "${story.frontmatter.title}"`);
|
|
1698
1982
|
// Auto-create PR in automated mode
|