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/agents/review.d.ts
CHANGED
|
@@ -43,6 +43,9 @@ export declare function deriveIndividualPassFailFromPerspectives(issues: ReviewI
|
|
|
43
43
|
/**
|
|
44
44
|
* Get source code changes from git diff
|
|
45
45
|
*
|
|
46
|
+
* Compares current branch HEAD against the base branch (main/master) merge-base
|
|
47
|
+
* to detect all source code changes in the feature branch, not just the most recent commit.
|
|
48
|
+
*
|
|
46
49
|
* Returns list of source files that have been modified (excludes tests and story files).
|
|
47
50
|
* Uses spawnSync for security (prevents command injection).
|
|
48
51
|
*
|
|
@@ -53,6 +56,8 @@ export declare function getSourceCodeChanges(workingDir: string): string[];
|
|
|
53
56
|
/**
|
|
54
57
|
* Get configuration file changes from git diff
|
|
55
58
|
*
|
|
59
|
+
* Compares current branch HEAD against the base branch merge-base.
|
|
60
|
+
*
|
|
56
61
|
* Detects changes to configuration files including:
|
|
57
62
|
* - .claude/ directory (Agent SDK skills, CLAUDE.md)
|
|
58
63
|
* - .github/ directory (workflows, actions, issue templates)
|
|
@@ -64,6 +69,21 @@ export declare function getSourceCodeChanges(workingDir: string): string[];
|
|
|
64
69
|
* @returns Array of configuration file paths that have changed, or ['unknown'] if git fails
|
|
65
70
|
*/
|
|
66
71
|
export declare function getConfigurationChanges(workingDir: string): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Get documentation file changes from git diff
|
|
74
|
+
*
|
|
75
|
+
* Compares current branch HEAD against the base branch merge-base.
|
|
76
|
+
*
|
|
77
|
+
* Detects changes to documentation files including:
|
|
78
|
+
* - Markdown files (.md) anywhere in the project (excluding story files)
|
|
79
|
+
* - docs/ directory (any file type)
|
|
80
|
+
*
|
|
81
|
+
* Uses spawnSync for security (prevents command injection).
|
|
82
|
+
*
|
|
83
|
+
* @param workingDir - Working directory to run git diff in
|
|
84
|
+
* @returns Array of documentation file paths that have changed, or ['unknown'] if git fails
|
|
85
|
+
*/
|
|
86
|
+
export declare function getDocumentationChanges(workingDir: string): string[];
|
|
67
87
|
/**
|
|
68
88
|
* Determine the effective content type for validation
|
|
69
89
|
*
|
|
@@ -79,6 +99,8 @@ export declare function determineEffectiveContentType(story: Story): ContentType
|
|
|
79
99
|
/**
|
|
80
100
|
* Check if test files exist in git diff
|
|
81
101
|
*
|
|
102
|
+
* Compares current branch HEAD against the base branch merge-base.
|
|
103
|
+
*
|
|
82
104
|
* Returns true if any test files have been modified/added, false otherwise.
|
|
83
105
|
* Uses spawnSync for security (prevents command injection).
|
|
84
106
|
*
|
|
@@ -154,4 +176,63 @@ export interface CreatePROptions {
|
|
|
154
176
|
* Create a pull request for the completed story
|
|
155
177
|
*/
|
|
156
178
|
export declare function createPullRequest(storyPath: string, sdlcRoot: string, options?: CreatePROptions): Promise<AgentResult>;
|
|
179
|
+
/**
|
|
180
|
+
* Status of a single CI check
|
|
181
|
+
*/
|
|
182
|
+
export interface CheckStatus {
|
|
183
|
+
name: string;
|
|
184
|
+
state: 'PENDING' | 'SUCCESS' | 'FAILURE' | 'ERROR' | 'SKIPPED';
|
|
185
|
+
conclusion: string | null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Result of waiting for CI checks
|
|
189
|
+
*/
|
|
190
|
+
export interface WaitForChecksResult {
|
|
191
|
+
allPassed: boolean;
|
|
192
|
+
checks: CheckStatus[];
|
|
193
|
+
timedOut: boolean;
|
|
194
|
+
error?: string;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Options for waiting for CI checks
|
|
198
|
+
*/
|
|
199
|
+
export interface WaitForChecksOptions {
|
|
200
|
+
timeout?: number;
|
|
201
|
+
pollingInterval?: number;
|
|
202
|
+
requireAllChecksPassing?: boolean;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Wait for CI checks to complete on a pull request
|
|
206
|
+
*
|
|
207
|
+
* @param prUrl - URL or number of the pull request
|
|
208
|
+
* @param workingDir - Working directory for git commands
|
|
209
|
+
* @param options - Timeout and polling options
|
|
210
|
+
* @returns Result indicating whether all checks passed
|
|
211
|
+
*/
|
|
212
|
+
export declare function waitForChecks(prUrl: string, workingDir: string, options?: WaitForChecksOptions): Promise<WaitForChecksResult>;
|
|
213
|
+
/**
|
|
214
|
+
* Result of merging a pull request
|
|
215
|
+
*/
|
|
216
|
+
export interface MergePullRequestResult {
|
|
217
|
+
success: boolean;
|
|
218
|
+
merged: boolean;
|
|
219
|
+
mergeSha?: string;
|
|
220
|
+
error?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Options for merging a pull request
|
|
224
|
+
*/
|
|
225
|
+
export interface MergePullRequestOptions {
|
|
226
|
+
strategy?: 'squash' | 'merge' | 'rebase';
|
|
227
|
+
deleteBranchAfterMerge?: boolean;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Merge a pull request using the GitHub CLI
|
|
231
|
+
*
|
|
232
|
+
* @param prUrl - URL or number of the pull request
|
|
233
|
+
* @param workingDir - Working directory for git commands
|
|
234
|
+
* @param options - Merge strategy and cleanup options
|
|
235
|
+
* @returns Result indicating success/failure and merge SHA
|
|
236
|
+
*/
|
|
237
|
+
export declare function mergePullRequest(prUrl: string, workingDir: string, options?: MergePullRequestOptions): Promise<MergePullRequestResult>;
|
|
157
238
|
//# sourceMappingURL=review.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/agents/review.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAA8E,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/agents/review.ts"],"names":[],"mappings":"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"}
|
package/dist/agents/review.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { ProcessManager } from '../core/process-manager.js';
|
|
6
|
-
import { parseStory, updateStoryStatus,
|
|
6
|
+
import { parseStory, updateStoryStatus, updateStoryField, isAtMaxRetries, appendReviewHistory, snapshotMaxRetries, getEffectiveMaxRetries, getEffectiveMaxImplementationRetries, writeSectionContent } from '../core/story.js';
|
|
7
7
|
import { runAgentQuery } from '../core/client.js';
|
|
8
8
|
import { getLogger } from '../core/logger.js';
|
|
9
9
|
import { loadConfig, DEFAULT_TIMEOUTS } from '../core/config.js';
|
|
@@ -11,6 +11,7 @@ import { extractStructuredResponseSync } from '../core/llm-utils.js';
|
|
|
11
11
|
import { ReviewDecision, ReviewSeverity } from '../types/index.js';
|
|
12
12
|
import { sanitizeInput, truncateText } from '../cli/formatting.js';
|
|
13
13
|
import { detectTestDuplicationPatterns } from './test-pattern-detector.js';
|
|
14
|
+
import { getBaseBranch, getMergeBase } from '../core/git-utils.js';
|
|
14
15
|
/**
|
|
15
16
|
* Security: Validate Git branch name to prevent command injection
|
|
16
17
|
* Only allows alphanumeric characters, hyphens, underscores, and forward slashes
|
|
@@ -591,9 +592,38 @@ function formatIssuesForDisplay(issues) {
|
|
|
591
592
|
}
|
|
592
593
|
return output;
|
|
593
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Get the base commit reference for git diff comparisons
|
|
597
|
+
*
|
|
598
|
+
* Attempts to find the merge-base between the current branch and the base branch (main/master).
|
|
599
|
+
* Falls back to HEAD~1 if merge-base cannot be determined.
|
|
600
|
+
*
|
|
601
|
+
* This allows detecting source code changes across the entire feature branch,
|
|
602
|
+
* not just from the most recent commit.
|
|
603
|
+
*
|
|
604
|
+
* @param workingDir - Working directory to run git commands in
|
|
605
|
+
* @returns Commit reference to use for git diff comparison
|
|
606
|
+
*/
|
|
607
|
+
function getBaseCommitForDiff(workingDir) {
|
|
608
|
+
try {
|
|
609
|
+
const baseBranch = getBaseBranch(workingDir);
|
|
610
|
+
const mergeBase = getMergeBase(workingDir, baseBranch);
|
|
611
|
+
if (mergeBase) {
|
|
612
|
+
return mergeBase;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// If we can't determine base branch or merge-base, fall back to HEAD~1
|
|
617
|
+
}
|
|
618
|
+
// Fallback to HEAD~1 (original behavior)
|
|
619
|
+
return 'HEAD~1';
|
|
620
|
+
}
|
|
594
621
|
/**
|
|
595
622
|
* Get source code changes from git diff
|
|
596
623
|
*
|
|
624
|
+
* Compares current branch HEAD against the base branch (main/master) merge-base
|
|
625
|
+
* to detect all source code changes in the feature branch, not just the most recent commit.
|
|
626
|
+
*
|
|
597
627
|
* Returns list of source files that have been modified (excludes tests and story files).
|
|
598
628
|
* Uses spawnSync for security (prevents command injection).
|
|
599
629
|
*
|
|
@@ -602,8 +632,9 @@ function formatIssuesForDisplay(issues) {
|
|
|
602
632
|
*/
|
|
603
633
|
export function getSourceCodeChanges(workingDir) {
|
|
604
634
|
try {
|
|
635
|
+
const baseCommit = getBaseCommitForDiff(workingDir);
|
|
605
636
|
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
606
|
-
const result = spawnSync('git', ['diff', '--name-only',
|
|
637
|
+
const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
|
|
607
638
|
cwd: workingDir,
|
|
608
639
|
encoding: 'utf-8',
|
|
609
640
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -629,6 +660,8 @@ export function getSourceCodeChanges(workingDir) {
|
|
|
629
660
|
/**
|
|
630
661
|
* Get configuration file changes from git diff
|
|
631
662
|
*
|
|
663
|
+
* Compares current branch HEAD against the base branch merge-base.
|
|
664
|
+
*
|
|
632
665
|
* Detects changes to configuration files including:
|
|
633
666
|
* - .claude/ directory (Agent SDK skills, CLAUDE.md)
|
|
634
667
|
* - .github/ directory (workflows, actions, issue templates)
|
|
@@ -641,8 +674,9 @@ export function getSourceCodeChanges(workingDir) {
|
|
|
641
674
|
*/
|
|
642
675
|
export function getConfigurationChanges(workingDir) {
|
|
643
676
|
try {
|
|
677
|
+
const baseCommit = getBaseCommitForDiff(workingDir);
|
|
644
678
|
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
645
|
-
const result = spawnSync('git', ['diff', '--name-only',
|
|
679
|
+
const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
|
|
646
680
|
cwd: workingDir,
|
|
647
681
|
encoding: 'utf-8',
|
|
648
682
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -691,6 +725,52 @@ export function getConfigurationChanges(workingDir) {
|
|
|
691
725
|
return ['unknown'];
|
|
692
726
|
}
|
|
693
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Get documentation file changes from git diff
|
|
730
|
+
*
|
|
731
|
+
* Compares current branch HEAD against the base branch merge-base.
|
|
732
|
+
*
|
|
733
|
+
* Detects changes to documentation files including:
|
|
734
|
+
* - Markdown files (.md) anywhere in the project (excluding story files)
|
|
735
|
+
* - docs/ directory (any file type)
|
|
736
|
+
*
|
|
737
|
+
* Uses spawnSync for security (prevents command injection).
|
|
738
|
+
*
|
|
739
|
+
* @param workingDir - Working directory to run git diff in
|
|
740
|
+
* @returns Array of documentation file paths that have changed, or ['unknown'] if git fails
|
|
741
|
+
*/
|
|
742
|
+
export function getDocumentationChanges(workingDir) {
|
|
743
|
+
try {
|
|
744
|
+
const baseCommit = getBaseCommitForDiff(workingDir);
|
|
745
|
+
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
746
|
+
const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
|
|
747
|
+
cwd: workingDir,
|
|
748
|
+
encoding: 'utf-8',
|
|
749
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
750
|
+
});
|
|
751
|
+
if (result.status !== 0) {
|
|
752
|
+
// Git command failed - fail open (assume changes exist)
|
|
753
|
+
return ['unknown'];
|
|
754
|
+
}
|
|
755
|
+
const output = result.stdout.toString();
|
|
756
|
+
return output
|
|
757
|
+
.split('\n')
|
|
758
|
+
.filter(f => f.trim())
|
|
759
|
+
.filter(f => {
|
|
760
|
+
// Markdown files (excluding story files in .ai-sdlc/stories/)
|
|
761
|
+
if (f.endsWith('.md') && !f.startsWith('.ai-sdlc/stories/'))
|
|
762
|
+
return true;
|
|
763
|
+
// Files in docs/ directory (any file type - images, diagrams, etc.)
|
|
764
|
+
if (f.startsWith('docs/'))
|
|
765
|
+
return true;
|
|
766
|
+
return false;
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
// If git diff fails, assume there are changes (fail open, not closed)
|
|
771
|
+
return ['unknown'];
|
|
772
|
+
}
|
|
773
|
+
}
|
|
694
774
|
/**
|
|
695
775
|
* Determine the effective content type for validation
|
|
696
776
|
*
|
|
@@ -717,6 +797,8 @@ export function determineEffectiveContentType(story) {
|
|
|
717
797
|
/**
|
|
718
798
|
* Check if test files exist in git diff
|
|
719
799
|
*
|
|
800
|
+
* Compares current branch HEAD against the base branch merge-base.
|
|
801
|
+
*
|
|
720
802
|
* Returns true if any test files have been modified/added, false otherwise.
|
|
721
803
|
* Uses spawnSync for security (prevents command injection).
|
|
722
804
|
*
|
|
@@ -725,8 +807,9 @@ export function determineEffectiveContentType(story) {
|
|
|
725
807
|
*/
|
|
726
808
|
export function hasTestFiles(workingDir) {
|
|
727
809
|
try {
|
|
810
|
+
const baseCommit = getBaseCommitForDiff(workingDir);
|
|
728
811
|
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
729
|
-
const result = spawnSync('git', ['diff', '--name-only',
|
|
812
|
+
const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
|
|
730
813
|
cwd: workingDir,
|
|
731
814
|
encoding: 'utf-8',
|
|
732
815
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -957,11 +1040,24 @@ export async function runReviewAgent(storyPath, sdlcRoot, options) {
|
|
|
957
1040
|
});
|
|
958
1041
|
}
|
|
959
1042
|
}
|
|
960
|
-
//
|
|
961
|
-
if (contentType === 'documentation') {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1043
|
+
// Check documentation changes for 'documentation' type
|
|
1044
|
+
if (!validationFailed && contentType === 'documentation') {
|
|
1045
|
+
const docChanges = getDocumentationChanges(workingDir);
|
|
1046
|
+
if (docChanges.length === 0) {
|
|
1047
|
+
validationFailed = true;
|
|
1048
|
+
validationReason = 'Documentation story requires changes to markdown files (.md) or docs/ directory. No documentation changes detected.';
|
|
1049
|
+
logger.warn('review', 'Documentation validation failed', {
|
|
1050
|
+
storyId: story.frontmatter.id,
|
|
1051
|
+
contentType,
|
|
1052
|
+
docChangesFound: docChanges.length,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
logger.info('review', 'Documentation changes detected', {
|
|
1057
|
+
storyId: story.frontmatter.id,
|
|
1058
|
+
fileCount: docChanges.length,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
965
1061
|
}
|
|
966
1062
|
// Handle validation failure (if any)
|
|
967
1063
|
if (validationFailed) {
|
|
@@ -976,19 +1072,23 @@ export async function runReviewAgent(storyPath, sdlcRoot, options) {
|
|
|
976
1072
|
contentType,
|
|
977
1073
|
});
|
|
978
1074
|
await updateStoryField(story, 'implementation_complete', false);
|
|
979
|
-
// Set restart reason
|
|
1075
|
+
// Set restart reason based on content type
|
|
980
1076
|
const restartReason = contentType === 'configuration'
|
|
981
1077
|
? 'Configuration story requires changes to config files (.claude/, .github/, or root config files). No configuration changes detected.'
|
|
982
1078
|
: contentType === 'mixed'
|
|
983
1079
|
? 'Mixed story requires both source AND configuration changes - no source code was modified.'
|
|
984
|
-
:
|
|
1080
|
+
: contentType === 'documentation'
|
|
1081
|
+
? 'Documentation story requires changes to markdown files (.md) or docs/ directory. No documentation changes detected.'
|
|
1082
|
+
: 'No source code changes detected. Implementation wrote documentation only.';
|
|
985
1083
|
await updateStoryField(story, 'last_restart_reason', restartReason);
|
|
986
1084
|
// Create user-friendly recovery description
|
|
987
1085
|
const recoveryDescription = contentType === 'configuration'
|
|
988
1086
|
? 'No configuration file modifications detected. Re-running implementation phase.'
|
|
989
1087
|
: contentType === 'mixed'
|
|
990
1088
|
? 'No source code modifications detected. Re-running implementation phase.'
|
|
991
|
-
:
|
|
1089
|
+
: contentType === 'documentation'
|
|
1090
|
+
? 'No documentation file modifications detected. Re-running implementation phase.'
|
|
1091
|
+
: 'No source code modifications detected. Re-running implementation phase.';
|
|
992
1092
|
return {
|
|
993
1093
|
success: true,
|
|
994
1094
|
story: parseStory(storyPath),
|
|
@@ -1036,33 +1136,43 @@ export async function runReviewAgent(storyPath, sdlcRoot, options) {
|
|
|
1036
1136
|
storyId: story.frontmatter.id,
|
|
1037
1137
|
contentType,
|
|
1038
1138
|
});
|
|
1039
|
-
// PRE-CHECK GATE: Check if test files exist
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1139
|
+
// PRE-CHECK GATE: Check if test files exist (only for code/mixed types)
|
|
1140
|
+
// Documentation and configuration stories don't require test files
|
|
1141
|
+
const requiresTests = contentType === 'code' || contentType === 'mixed';
|
|
1142
|
+
if (requiresTests) {
|
|
1143
|
+
const testsExist = hasTestFiles(workingDir);
|
|
1144
|
+
if (!testsExist) {
|
|
1145
|
+
logger.warn('review', 'No test files detected in implementation changes', {
|
|
1146
|
+
storyId: story.frontmatter.id,
|
|
1147
|
+
});
|
|
1148
|
+
return {
|
|
1149
|
+
success: true,
|
|
1150
|
+
story: parseStory(storyPath),
|
|
1151
|
+
changesMade: ['No test files found for implementation'],
|
|
1152
|
+
passed: false,
|
|
1153
|
+
decision: ReviewDecision.REJECTED,
|
|
1154
|
+
severity: ReviewSeverity.CRITICAL,
|
|
1155
|
+
reviewType: 'pre-check',
|
|
1156
|
+
issues: [{
|
|
1157
|
+
severity: 'blocker',
|
|
1158
|
+
category: 'testing',
|
|
1159
|
+
description: 'No tests found for this implementation. All implementations must include tests.',
|
|
1160
|
+
suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
|
|
1161
|
+
}],
|
|
1162
|
+
feedback: formatIssuesForDisplay([{
|
|
1163
|
+
severity: 'blocker',
|
|
1164
|
+
category: 'testing',
|
|
1165
|
+
description: 'No tests found for this implementation. All implementations must include tests.',
|
|
1166
|
+
suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
|
|
1167
|
+
}]),
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
else {
|
|
1172
|
+
logger.info('review', 'Test file check skipped for non-code content type', {
|
|
1043
1173
|
storyId: story.frontmatter.id,
|
|
1174
|
+
contentType,
|
|
1044
1175
|
});
|
|
1045
|
-
return {
|
|
1046
|
-
success: true,
|
|
1047
|
-
story: parseStory(storyPath),
|
|
1048
|
-
changesMade: ['No test files found for implementation'],
|
|
1049
|
-
passed: false,
|
|
1050
|
-
decision: ReviewDecision.REJECTED,
|
|
1051
|
-
severity: ReviewSeverity.CRITICAL,
|
|
1052
|
-
reviewType: 'pre-check',
|
|
1053
|
-
issues: [{
|
|
1054
|
-
severity: 'blocker',
|
|
1055
|
-
category: 'testing',
|
|
1056
|
-
description: 'No tests found for this implementation. All implementations must include tests.',
|
|
1057
|
-
suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
|
|
1058
|
-
}],
|
|
1059
|
-
feedback: formatIssuesForDisplay([{
|
|
1060
|
-
severity: 'blocker',
|
|
1061
|
-
category: 'testing',
|
|
1062
|
-
description: 'No tests found for this implementation. All implementations must include tests.',
|
|
1063
|
-
suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
|
|
1064
|
-
}]),
|
|
1065
|
-
};
|
|
1066
1176
|
}
|
|
1067
1177
|
// Run build and tests BEFORE reviews (async with progress)
|
|
1068
1178
|
changesMade.push('Running build and test verification...');
|
|
@@ -1204,8 +1314,15 @@ ${passed ? '✅ **PASSED** - All reviews approved' : '❌ **FAILED** - Issues mu
|
|
|
1204
1314
|
---
|
|
1205
1315
|
*Review completed: ${new Date().toISOString().split('T')[0]}*
|
|
1206
1316
|
`;
|
|
1207
|
-
//
|
|
1208
|
-
|
|
1317
|
+
// Determine if this is a retry (retry_count > 0)
|
|
1318
|
+
const retryCount = story.frontmatter.retry_count || 0;
|
|
1319
|
+
const isRetry = retryCount > 0;
|
|
1320
|
+
// Write review notes to section file
|
|
1321
|
+
await writeSectionContent(storyPath, 'review', reviewNotes, {
|
|
1322
|
+
append: isRetry,
|
|
1323
|
+
iteration: retryCount + 1,
|
|
1324
|
+
isRework: isRetry,
|
|
1325
|
+
});
|
|
1209
1326
|
changesMade.push('Added unified collaborative review notes');
|
|
1210
1327
|
// Determine decision
|
|
1211
1328
|
const decision = passed ? ReviewDecision.APPROVED : ReviewDecision.REJECTED;
|
|
@@ -1576,4 +1693,253 @@ EOF
|
|
|
1576
1693
|
};
|
|
1577
1694
|
}
|
|
1578
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Wait for CI checks to complete on a pull request
|
|
1698
|
+
*
|
|
1699
|
+
* @param prUrl - URL or number of the pull request
|
|
1700
|
+
* @param workingDir - Working directory for git commands
|
|
1701
|
+
* @param options - Timeout and polling options
|
|
1702
|
+
* @returns Result indicating whether all checks passed
|
|
1703
|
+
*/
|
|
1704
|
+
export async function waitForChecks(prUrl, workingDir, options) {
|
|
1705
|
+
const timeout = options?.timeout ?? 600000; // 10 minutes
|
|
1706
|
+
const pollingInterval = options?.pollingInterval ?? 10000; // 10 seconds
|
|
1707
|
+
const requireAllChecksPassing = options?.requireAllChecksPassing ?? true;
|
|
1708
|
+
// Security: Validate working directory
|
|
1709
|
+
validateWorkingDirectory(workingDir);
|
|
1710
|
+
// Extract PR number from URL if needed
|
|
1711
|
+
const prMatch = prUrl.match(/\/pull\/(\d+)/);
|
|
1712
|
+
const prIdentifier = prMatch ? prMatch[1] : prUrl;
|
|
1713
|
+
// Security: Validate PR identifier (should be numeric or a valid URL)
|
|
1714
|
+
if (!/^\d+$/.test(prIdentifier) && !prUrl.startsWith('https://')) {
|
|
1715
|
+
return {
|
|
1716
|
+
allPassed: false,
|
|
1717
|
+
checks: [],
|
|
1718
|
+
timedOut: false,
|
|
1719
|
+
error: `Invalid PR identifier: ${prUrl}`,
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
const startTime = Date.now();
|
|
1723
|
+
while (Date.now() - startTime < timeout) {
|
|
1724
|
+
try {
|
|
1725
|
+
// Use gh pr checks to get check status
|
|
1726
|
+
const result = spawnSync('gh', ['pr', 'checks', prIdentifier, '--json', 'name,state,conclusion'], {
|
|
1727
|
+
cwd: workingDir,
|
|
1728
|
+
encoding: 'utf-8',
|
|
1729
|
+
timeout: 30000, // 30 second timeout for the command
|
|
1730
|
+
});
|
|
1731
|
+
if (result.error) {
|
|
1732
|
+
// gh CLI might not be available or authenticated
|
|
1733
|
+
return {
|
|
1734
|
+
allPassed: false,
|
|
1735
|
+
checks: [],
|
|
1736
|
+
timedOut: false,
|
|
1737
|
+
error: `gh CLI error: ${result.error.message}`,
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
if (result.status !== 0) {
|
|
1741
|
+
const stderr = result.stderr?.trim() || '';
|
|
1742
|
+
// If no checks exist, that's OK
|
|
1743
|
+
if (stderr.includes('no checks') || stderr.includes('No checks')) {
|
|
1744
|
+
return {
|
|
1745
|
+
allPassed: true,
|
|
1746
|
+
checks: [],
|
|
1747
|
+
timedOut: false,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return {
|
|
1751
|
+
allPassed: false,
|
|
1752
|
+
checks: [],
|
|
1753
|
+
timedOut: false,
|
|
1754
|
+
error: `gh pr checks failed: ${stderr}`,
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
const checksOutput = result.stdout?.trim() || '[]';
|
|
1758
|
+
let checks = [];
|
|
1759
|
+
try {
|
|
1760
|
+
checks = JSON.parse(checksOutput);
|
|
1761
|
+
}
|
|
1762
|
+
catch {
|
|
1763
|
+
return {
|
|
1764
|
+
allPassed: false,
|
|
1765
|
+
checks: [],
|
|
1766
|
+
timedOut: false,
|
|
1767
|
+
error: `Failed to parse checks output: ${checksOutput.slice(0, 200)}`,
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
// If no checks, consider it passed
|
|
1771
|
+
if (checks.length === 0) {
|
|
1772
|
+
return {
|
|
1773
|
+
allPassed: true,
|
|
1774
|
+
checks: [],
|
|
1775
|
+
timedOut: false,
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
// Check if all checks are complete
|
|
1779
|
+
const pendingChecks = checks.filter(c => c.state === 'PENDING' || c.state === null);
|
|
1780
|
+
if (pendingChecks.length === 0) {
|
|
1781
|
+
// All checks are complete
|
|
1782
|
+
const failedChecks = checks.filter(c => c.state === 'FAILURE' || c.state === 'ERROR');
|
|
1783
|
+
if (requireAllChecksPassing && failedChecks.length > 0) {
|
|
1784
|
+
return {
|
|
1785
|
+
allPassed: false,
|
|
1786
|
+
checks,
|
|
1787
|
+
timedOut: false,
|
|
1788
|
+
error: `${failedChecks.length} check(s) failed: ${failedChecks.map(c => c.name).join(', ')}`,
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
allPassed: true,
|
|
1793
|
+
checks,
|
|
1794
|
+
timedOut: false,
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
// Still pending, wait and poll again
|
|
1798
|
+
await new Promise(resolve => setTimeout(resolve, pollingInterval));
|
|
1799
|
+
}
|
|
1800
|
+
catch (error) {
|
|
1801
|
+
return {
|
|
1802
|
+
allPassed: false,
|
|
1803
|
+
checks: [],
|
|
1804
|
+
timedOut: false,
|
|
1805
|
+
error: `Error checking PR status: ${error instanceof Error ? error.message : String(error)}`,
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
// Timed out waiting for checks
|
|
1810
|
+
return {
|
|
1811
|
+
allPassed: false,
|
|
1812
|
+
checks: [],
|
|
1813
|
+
timedOut: true,
|
|
1814
|
+
error: `Timed out after ${timeout}ms waiting for CI checks to complete`,
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Merge a pull request using the GitHub CLI
|
|
1819
|
+
*
|
|
1820
|
+
* @param prUrl - URL or number of the pull request
|
|
1821
|
+
* @param workingDir - Working directory for git commands
|
|
1822
|
+
* @param options - Merge strategy and cleanup options
|
|
1823
|
+
* @returns Result indicating success/failure and merge SHA
|
|
1824
|
+
*/
|
|
1825
|
+
export async function mergePullRequest(prUrl, workingDir, options) {
|
|
1826
|
+
const strategy = options?.strategy ?? 'squash';
|
|
1827
|
+
const deleteBranchAfterMerge = options?.deleteBranchAfterMerge ?? true;
|
|
1828
|
+
// Security: Validate working directory
|
|
1829
|
+
validateWorkingDirectory(workingDir);
|
|
1830
|
+
// Extract PR number from URL if needed
|
|
1831
|
+
const prMatch = prUrl.match(/\/pull\/(\d+)/);
|
|
1832
|
+
const prIdentifier = prMatch ? prMatch[1] : prUrl;
|
|
1833
|
+
// Security: Validate PR identifier (should be numeric or a valid URL)
|
|
1834
|
+
if (!/^\d+$/.test(prIdentifier) && !prUrl.startsWith('https://')) {
|
|
1835
|
+
return {
|
|
1836
|
+
success: false,
|
|
1837
|
+
merged: false,
|
|
1838
|
+
error: `Invalid PR identifier: ${prUrl}`,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
// Security: Validate strategy
|
|
1842
|
+
const validStrategies = ['squash', 'merge', 'rebase'];
|
|
1843
|
+
if (!validStrategies.includes(strategy)) {
|
|
1844
|
+
return {
|
|
1845
|
+
success: false,
|
|
1846
|
+
merged: false,
|
|
1847
|
+
error: `Invalid merge strategy: ${strategy}`,
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
try {
|
|
1851
|
+
// Build merge command arguments
|
|
1852
|
+
const args = ['pr', 'merge', prIdentifier, `--${strategy}`];
|
|
1853
|
+
if (deleteBranchAfterMerge) {
|
|
1854
|
+
args.push('--delete-branch');
|
|
1855
|
+
}
|
|
1856
|
+
// Add auto flag to avoid interactive prompts
|
|
1857
|
+
args.push('--auto');
|
|
1858
|
+
const result = spawnSync('gh', args, {
|
|
1859
|
+
cwd: workingDir,
|
|
1860
|
+
encoding: 'utf-8',
|
|
1861
|
+
timeout: 60000, // 60 second timeout
|
|
1862
|
+
});
|
|
1863
|
+
if (result.error) {
|
|
1864
|
+
return {
|
|
1865
|
+
success: false,
|
|
1866
|
+
merged: false,
|
|
1867
|
+
error: `gh CLI error: ${result.error.message}`,
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
if (result.status !== 0) {
|
|
1871
|
+
const stderr = result.stderr?.trim() || '';
|
|
1872
|
+
// Check for common error conditions
|
|
1873
|
+
if (stderr.includes('already merged') || stderr.includes('Pull request #')) {
|
|
1874
|
+
// Already merged is not an error
|
|
1875
|
+
return {
|
|
1876
|
+
success: true,
|
|
1877
|
+
merged: true,
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
if (stderr.includes('conflict')) {
|
|
1881
|
+
return {
|
|
1882
|
+
success: false,
|
|
1883
|
+
merged: false,
|
|
1884
|
+
error: `Merge conflict detected. Manual intervention required.`,
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
if (stderr.includes('review') || stderr.includes('approved')) {
|
|
1888
|
+
return {
|
|
1889
|
+
success: false,
|
|
1890
|
+
merged: false,
|
|
1891
|
+
error: `PR requires review approval before merging.`,
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
if (stderr.includes('check') || stderr.includes('status')) {
|
|
1895
|
+
return {
|
|
1896
|
+
success: false,
|
|
1897
|
+
merged: false,
|
|
1898
|
+
error: `CI checks must pass before merging.`,
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
return {
|
|
1902
|
+
success: false,
|
|
1903
|
+
merged: false,
|
|
1904
|
+
error: `Merge failed: ${stderr}`,
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
// Try to extract merge SHA from output
|
|
1908
|
+
const output = result.stdout?.trim() || '';
|
|
1909
|
+
const shaMatch = output.match(/merged\s+(?:via|to|into)\s+(\w+)/i) || output.match(/([a-f0-9]{40})/);
|
|
1910
|
+
const mergeSha = shaMatch ? shaMatch[1] : undefined;
|
|
1911
|
+
// If merge was successful, try to get the actual merge SHA from the PR
|
|
1912
|
+
let actualMergeSha = mergeSha;
|
|
1913
|
+
if (!actualMergeSha) {
|
|
1914
|
+
try {
|
|
1915
|
+
const prInfoResult = spawnSync('gh', ['pr', 'view', prIdentifier, '--json', 'mergeCommit'], {
|
|
1916
|
+
cwd: workingDir,
|
|
1917
|
+
encoding: 'utf-8',
|
|
1918
|
+
timeout: 10000,
|
|
1919
|
+
});
|
|
1920
|
+
if (prInfoResult.status === 0 && prInfoResult.stdout) {
|
|
1921
|
+
const prInfo = JSON.parse(prInfoResult.stdout);
|
|
1922
|
+
if (prInfo.mergeCommit?.oid) {
|
|
1923
|
+
actualMergeSha = prInfo.mergeCommit.oid;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
catch {
|
|
1928
|
+
// Ignore errors getting merge SHA - merge still succeeded
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return {
|
|
1932
|
+
success: true,
|
|
1933
|
+
merged: true,
|
|
1934
|
+
mergeSha: actualMergeSha,
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
catch (error) {
|
|
1938
|
+
return {
|
|
1939
|
+
success: false,
|
|
1940
|
+
merged: false,
|
|
1941
|
+
error: `Error merging PR: ${error instanceof Error ? error.message : String(error)}`,
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1579
1945
|
//# sourceMappingURL=review.js.map
|