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.
Files changed (84) hide show
  1. package/README.md +78 -6
  2. package/dist/agents/implementation.d.ts +5 -0
  3. package/dist/agents/implementation.d.ts.map +1 -1
  4. package/dist/agents/implementation.js +78 -9
  5. package/dist/agents/implementation.js.map +1 -1
  6. package/dist/agents/planning.d.ts.map +1 -1
  7. package/dist/agents/planning.js +10 -3
  8. package/dist/agents/planning.js.map +1 -1
  9. package/dist/agents/research.d.ts.map +1 -1
  10. package/dist/agents/research.js +14 -6
  11. package/dist/agents/research.js.map +1 -1
  12. package/dist/agents/review.d.ts +81 -0
  13. package/dist/agents/review.d.ts.map +1 -1
  14. package/dist/agents/review.js +405 -39
  15. package/dist/agents/review.js.map +1 -1
  16. package/dist/agents/single-task.d.ts +1 -1
  17. package/dist/agents/single-task.d.ts.map +1 -1
  18. package/dist/agents/single-task.js +1 -1
  19. package/dist/cli/batch-processor.d.ts +64 -0
  20. package/dist/cli/batch-processor.d.ts.map +1 -0
  21. package/dist/cli/batch-processor.js +85 -0
  22. package/dist/cli/batch-processor.js.map +1 -0
  23. package/dist/cli/batch-validator.d.ts +80 -0
  24. package/dist/cli/batch-validator.d.ts.map +1 -0
  25. package/dist/cli/batch-validator.js +121 -0
  26. package/dist/cli/batch-validator.js.map +1 -0
  27. package/dist/cli/commands.d.ts +7 -0
  28. package/dist/cli/commands.d.ts.map +1 -1
  29. package/dist/cli/commands.js +285 -1
  30. package/dist/cli/commands.js.map +1 -1
  31. package/dist/cli/dependency-resolver.d.ts +49 -0
  32. package/dist/cli/dependency-resolver.d.ts.map +1 -0
  33. package/dist/cli/dependency-resolver.js +133 -0
  34. package/dist/cli/dependency-resolver.js.map +1 -0
  35. package/dist/cli/epic-processor.d.ts +16 -0
  36. package/dist/cli/epic-processor.d.ts.map +1 -0
  37. package/dist/cli/epic-processor.js +489 -0
  38. package/dist/cli/epic-processor.js.map +1 -0
  39. package/dist/cli/formatting.d.ts +15 -0
  40. package/dist/cli/formatting.d.ts.map +1 -1
  41. package/dist/cli/formatting.js +19 -0
  42. package/dist/cli/formatting.js.map +1 -1
  43. package/dist/cli/progress-dashboard.d.ts +58 -0
  44. package/dist/cli/progress-dashboard.d.ts.map +1 -0
  45. package/dist/cli/progress-dashboard.js +216 -0
  46. package/dist/cli/progress-dashboard.js.map +1 -0
  47. package/dist/cli/table-renderer.d.ts.map +1 -1
  48. package/dist/cli/table-renderer.js +5 -1
  49. package/dist/cli/table-renderer.js.map +1 -1
  50. package/dist/core/agent-executor.d.ts +13 -0
  51. package/dist/core/agent-executor.d.ts.map +1 -0
  52. package/dist/core/agent-executor.js +153 -0
  53. package/dist/core/agent-executor.js.map +1 -0
  54. package/dist/core/config.d.ts +16 -1
  55. package/dist/core/config.d.ts.map +1 -1
  56. package/dist/core/config.js +113 -0
  57. package/dist/core/config.js.map +1 -1
  58. package/dist/core/git-utils.d.ts +19 -0
  59. package/dist/core/git-utils.d.ts.map +1 -1
  60. package/dist/core/git-utils.js +58 -0
  61. package/dist/core/git-utils.js.map +1 -1
  62. package/dist/core/kanban.d.ts +125 -1
  63. package/dist/core/kanban.d.ts.map +1 -1
  64. package/dist/core/kanban.js +371 -4
  65. package/dist/core/kanban.js.map +1 -1
  66. package/dist/core/orchestrator.d.ts +63 -0
  67. package/dist/core/orchestrator.d.ts.map +1 -0
  68. package/dist/core/orchestrator.js +320 -0
  69. package/dist/core/orchestrator.js.map +1 -0
  70. package/dist/core/story.d.ts +84 -0
  71. package/dist/core/story.d.ts.map +1 -1
  72. package/dist/core/story.js +159 -14
  73. package/dist/core/story.js.map +1 -1
  74. package/dist/core/worktree.d.ts +7 -0
  75. package/dist/core/worktree.d.ts.map +1 -1
  76. package/dist/core/worktree.js +44 -0
  77. package/dist/core/worktree.js.map +1 -1
  78. package/dist/index.js +53 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/types/index.d.ts +252 -0
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/types/index.js +23 -0
  83. package/dist/types/index.js.map +1 -1
  84. package/package.json +1 -1
@@ -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;AAqHzL;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CA0BlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAOrE;AAiBD;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAyZrJ;;;;;;;;;;;GAWG;AACH,wBAAgB,wCAAwC,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG;IAC/E,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;CACzB,CAsBA;AAsED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAqDpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,CAavE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CA2BxD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CA+F1F;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,sBAAsB,CAAC,EAAE,4BAA4B,CAAC;CACvD;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAievB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB/F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAelE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAiB7F;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAsC9E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAc,GAAG,MAAM,CAqC9E;AAgCD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,WAAW,CAAC,CAqKtB"}
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/agents/review.ts"],"names":[],"mappings":"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"}
@@ -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, appendToSection, updateStoryField, isAtMaxRetries, appendReviewHistory, snapshotMaxRetries, getEffectiveMaxRetries, getEffectiveMaxImplementationRetries } from '../core/story.js';
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', 'HEAD~1'], {
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', 'HEAD~1'], {
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', 'HEAD~1'], {
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
- // For 'documentation' type, skip all file change validation
961
- if (contentType === 'documentation') {
962
- logger.info('review', 'Documentation story - skipping file change validation', {
963
- storyId: story.frontmatter.id,
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 (backward compatible message for default code stories)
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
- : 'No source code changes detected. Implementation wrote documentation only.';
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
- : 'No source code modifications detected. Re-running implementation phase.';
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
- const testsExist = hasTestFiles(workingDir);
1041
- if (!testsExist) {
1042
- logger.warn('review', 'No test files detected in implementation changes', {
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
- // Append reviews to story
1208
- await appendToSection(story, 'Review Notes', reviewNotes);
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