ai-sdlc 0.2.1-alpha.1 → 0.3.0-alpha.10

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 (88) hide show
  1. package/README.md +78 -6
  2. package/dist/agents/implementation.d.ts +30 -1
  3. package/dist/agents/implementation.d.ts.map +1 -1
  4. package/dist/agents/implementation.js +110 -23
  5. package/dist/agents/implementation.js.map +1 -1
  6. package/dist/agents/review.d.ts +49 -1
  7. package/dist/agents/review.d.ts.map +1 -1
  8. package/dist/agents/review.js +318 -46
  9. package/dist/agents/review.js.map +1 -1
  10. package/dist/agents/rework.d.ts.map +1 -1
  11. package/dist/agents/rework.js +3 -1
  12. package/dist/agents/rework.js.map +1 -1
  13. package/dist/agents/verification.d.ts.map +1 -1
  14. package/dist/agents/verification.js +26 -12
  15. package/dist/agents/verification.js.map +1 -1
  16. package/dist/cli/batch-processor.d.ts +64 -0
  17. package/dist/cli/batch-processor.d.ts.map +1 -0
  18. package/dist/cli/batch-processor.js +85 -0
  19. package/dist/cli/batch-processor.js.map +1 -0
  20. package/dist/cli/batch-validator.d.ts +80 -0
  21. package/dist/cli/batch-validator.d.ts.map +1 -0
  22. package/dist/cli/batch-validator.js +121 -0
  23. package/dist/cli/batch-validator.js.map +1 -0
  24. package/dist/cli/commands.d.ts +11 -0
  25. package/dist/cli/commands.d.ts.map +1 -1
  26. package/dist/cli/commands.js +772 -46
  27. package/dist/cli/commands.js.map +1 -1
  28. package/dist/cli/daemon.d.ts.map +1 -1
  29. package/dist/cli/daemon.js +5 -0
  30. package/dist/cli/daemon.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 +361 -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/runner.d.ts.map +1 -1
  48. package/dist/cli/runner.js +17 -2
  49. package/dist/cli/runner.js.map +1 -1
  50. package/dist/cli/table-renderer.d.ts.map +1 -1
  51. package/dist/cli/table-renderer.js +5 -1
  52. package/dist/cli/table-renderer.js.map +1 -1
  53. package/dist/core/client.d.ts +19 -1
  54. package/dist/core/client.d.ts.map +1 -1
  55. package/dist/core/client.js +191 -5
  56. package/dist/core/client.js.map +1 -1
  57. package/dist/core/config.d.ts +13 -1
  58. package/dist/core/config.d.ts.map +1 -1
  59. package/dist/core/config.js +117 -0
  60. package/dist/core/config.js.map +1 -1
  61. package/dist/core/git-utils.d.ts +19 -0
  62. package/dist/core/git-utils.d.ts.map +1 -1
  63. package/dist/core/git-utils.js +58 -0
  64. package/dist/core/git-utils.js.map +1 -1
  65. package/dist/core/kanban.d.ts +125 -1
  66. package/dist/core/kanban.d.ts.map +1 -1
  67. package/dist/core/kanban.js +363 -4
  68. package/dist/core/kanban.js.map +1 -1
  69. package/dist/core/process-manager.d.ts +15 -0
  70. package/dist/core/process-manager.d.ts.map +1 -0
  71. package/dist/core/process-manager.js +132 -0
  72. package/dist/core/process-manager.js.map +1 -0
  73. package/dist/core/story.d.ts +24 -0
  74. package/dist/core/story.d.ts.map +1 -1
  75. package/dist/core/story.js +78 -0
  76. package/dist/core/story.js.map +1 -1
  77. package/dist/core/worktree.d.ts +111 -2
  78. package/dist/core/worktree.d.ts.map +1 -1
  79. package/dist/core/worktree.js +310 -2
  80. package/dist/core/worktree.js.map +1 -1
  81. package/dist/index.js +38 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/types/index.d.ts +184 -0
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/index.js +23 -0
  86. package/dist/types/index.js.map +1 -1
  87. package/package.json +2 -2
  88. package/templates/story.md +5 -0
@@ -1 +1 @@
1
- {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/agents/review.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAA8E,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAqH5K;;;;;;;;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;AAuZrJ;;;;;;;;;;;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;;;;;;;;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,CA+YvB;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":"AAUA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAA8E,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAsHzL;;;;;;;;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;AAkGD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA6BjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAuDpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAkCpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,CAavE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CA6BxD;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,CA4fvB;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"}
@@ -2,6 +2,7 @@ import { execSync, spawn, spawnSync } from 'child_process';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
4
  import { z } from 'zod';
5
+ import { ProcessManager } from '../core/process-manager.js';
5
6
  import { parseStory, updateStoryStatus, appendToSection, updateStoryField, isAtMaxRetries, appendReviewHistory, snapshotMaxRetries, getEffectiveMaxRetries, getEffectiveMaxImplementationRetries } from '../core/story.js';
6
7
  import { runAgentQuery } from '../core/client.js';
7
8
  import { getLogger } from '../core/logger.js';
@@ -10,6 +11,7 @@ import { extractStructuredResponseSync } from '../core/llm-utils.js';
10
11
  import { ReviewDecision, ReviewSeverity } from '../types/index.js';
11
12
  import { sanitizeInput, truncateText } from '../cli/formatting.js';
12
13
  import { detectTestDuplicationPatterns } from './test-pattern-detector.js';
14
+ import { getBaseBranch, getMergeBase } from '../core/git-utils.js';
13
15
  /**
14
16
  * Security: Validate Git branch name to prevent command injection
15
17
  * Only allows alphanumeric characters, hyphens, underscores, and forward slashes
@@ -173,6 +175,7 @@ async function runCommandAsync(command, workingDir, timeout, onProgress) {
173
175
  cwd: workingDir,
174
176
  stdio: ['pipe', 'pipe', 'pipe'],
175
177
  });
178
+ ProcessManager.getInstance().registerChild(child);
176
179
  const timeoutId = setTimeout(() => {
177
180
  killed = true;
178
181
  child.kill('SIGTERM');
@@ -589,9 +592,38 @@ function formatIssuesForDisplay(issues) {
589
592
  }
590
593
  return output;
591
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
+ }
592
621
  /**
593
622
  * Get source code changes from git diff
594
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
+ *
595
627
  * Returns list of source files that have been modified (excludes tests and story files).
596
628
  * Uses spawnSync for security (prevents command injection).
597
629
  *
@@ -600,8 +632,9 @@ function formatIssuesForDisplay(issues) {
600
632
  */
601
633
  export function getSourceCodeChanges(workingDir) {
602
634
  try {
635
+ const baseCommit = getBaseCommitForDiff(workingDir);
603
636
  // Security: Use spawnSync with explicit args (not shell) to prevent injection
604
- const result = spawnSync('git', ['diff', '--name-only', 'HEAD~1'], {
637
+ const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
605
638
  cwd: workingDir,
606
639
  encoding: 'utf-8',
607
640
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -624,9 +657,148 @@ export function getSourceCodeChanges(workingDir) {
624
657
  return ['unknown'];
625
658
  }
626
659
  }
660
+ /**
661
+ * Get configuration file changes from git diff
662
+ *
663
+ * Compares current branch HEAD against the base branch merge-base.
664
+ *
665
+ * Detects changes to configuration files including:
666
+ * - .claude/ directory (Agent SDK skills, CLAUDE.md)
667
+ * - .github/ directory (workflows, actions, issue templates)
668
+ * - Root config files (tsconfig.json, package.json, .gitignore, vitest.config.ts, etc.)
669
+ *
670
+ * Uses spawnSync for security (prevents command injection).
671
+ *
672
+ * @param workingDir - Working directory to run git diff in
673
+ * @returns Array of configuration file paths that have changed, or ['unknown'] if git fails
674
+ */
675
+ export function getConfigurationChanges(workingDir) {
676
+ try {
677
+ const baseCommit = getBaseCommitForDiff(workingDir);
678
+ // Security: Use spawnSync with explicit args (not shell) to prevent injection
679
+ const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
680
+ cwd: workingDir,
681
+ encoding: 'utf-8',
682
+ stdio: ['ignore', 'pipe', 'pipe'],
683
+ });
684
+ if (result.status !== 0) {
685
+ // Git command failed - fail open (assume changes exist)
686
+ return ['unknown'];
687
+ }
688
+ const output = result.stdout.toString();
689
+ return output
690
+ .split('\n')
691
+ .filter(f => f.trim())
692
+ .filter(f => {
693
+ // Configuration directories
694
+ if (f.startsWith('.claude/'))
695
+ return true;
696
+ if (f.startsWith('.github/'))
697
+ return true;
698
+ // Root configuration files (common patterns)
699
+ const rootConfigs = [
700
+ 'tsconfig.json',
701
+ 'package.json',
702
+ 'package-lock.json',
703
+ '.gitignore',
704
+ '.gitattributes',
705
+ 'vitest.config.ts',
706
+ 'vitest.config.js',
707
+ 'jest.config.js',
708
+ 'jest.config.ts',
709
+ '.eslintrc',
710
+ '.eslintrc.js',
711
+ '.eslintrc.json',
712
+ '.prettierrc',
713
+ '.prettierrc.js',
714
+ '.prettierrc.json',
715
+ 'Makefile',
716
+ 'Dockerfile',
717
+ 'docker-compose.yml',
718
+ '.env.example',
719
+ ];
720
+ return rootConfigs.includes(f);
721
+ });
722
+ }
723
+ catch {
724
+ // If git diff fails, assume there are changes (fail open, not closed)
725
+ return ['unknown'];
726
+ }
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
+ }
774
+ /**
775
+ * Determine the effective content type for validation
776
+ *
777
+ * Resolves the final content type based on story frontmatter fields:
778
+ * 1. If requires_source_changes === false, treat as 'configuration'
779
+ * 2. If requires_source_changes === true, treat as 'code'
780
+ * 3. Otherwise, use content_type field (default: 'code' for backward compatibility)
781
+ *
782
+ * @param story - Story with frontmatter to analyze
783
+ * @returns The effective content type to use for validation
784
+ */
785
+ export function determineEffectiveContentType(story) {
786
+ const frontmatter = story.frontmatter;
787
+ // Manual override takes precedence
788
+ if (frontmatter.requires_source_changes === false) {
789
+ return 'configuration';
790
+ }
791
+ if (frontmatter.requires_source_changes === true) {
792
+ return 'code';
793
+ }
794
+ // Use explicit content_type or default to 'code'
795
+ return frontmatter.content_type || 'code';
796
+ }
627
797
  /**
628
798
  * Check if test files exist in git diff
629
799
  *
800
+ * Compares current branch HEAD against the base branch merge-base.
801
+ *
630
802
  * Returns true if any test files have been modified/added, false otherwise.
631
803
  * Uses spawnSync for security (prevents command injection).
632
804
  *
@@ -635,8 +807,9 @@ export function getSourceCodeChanges(workingDir) {
635
807
  */
636
808
  export function hasTestFiles(workingDir) {
637
809
  try {
810
+ const baseCommit = getBaseCommitForDiff(workingDir);
638
811
  // Security: Use spawnSync with explicit args (not shell) to prevent injection
639
- const result = spawnSync('git', ['diff', '--name-only', 'HEAD~1'], {
812
+ const result = spawnSync('git', ['diff', '--name-only', baseCommit], {
640
813
  cwd: workingDir,
641
814
  encoding: 'utf-8',
642
815
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -813,94 +986,193 @@ export async function runReviewAgent(storyPath, sdlcRoot, options) {
813
986
  feedback: errorMsg,
814
987
  };
815
988
  }
816
- // PRE-CHECK GATE: Detect documentation-only implementations before running expensive LLM reviews
817
- const sourceChanges = getSourceCodeChanges(workingDir);
818
- if (sourceChanges.length === 0) {
819
- // No source code changes detected - check if we can recover
989
+ // PRE-CHECK GATE: Content type-aware validation before running expensive LLM reviews
990
+ const contentType = determineEffectiveContentType(story);
991
+ logger.info('review', 'Running content-type-specific validation', {
992
+ storyId: story.frontmatter.id,
993
+ contentType,
994
+ explicitContentType: story.frontmatter.content_type,
995
+ requiresSourceChanges: story.frontmatter.requires_source_changes,
996
+ });
997
+ // Validation flags
998
+ let validationFailed = false;
999
+ let validationReason = '';
1000
+ let validationCategory = 'implementation';
1001
+ // Check source code changes for 'code' and 'mixed' types
1002
+ if (contentType === 'code' || contentType === 'mixed') {
1003
+ const sourceChanges = getSourceCodeChanges(workingDir);
1004
+ if (sourceChanges.length === 0) {
1005
+ validationFailed = true;
1006
+ validationReason = contentType === 'mixed'
1007
+ ? 'Mixed story requires both source AND configuration changes - no source code was modified.'
1008
+ : 'Implementation wrote documentation/planning only - no source code was modified.';
1009
+ logger.warn('review', 'Source code validation failed', {
1010
+ storyId: story.frontmatter.id,
1011
+ contentType,
1012
+ sourceChangesFound: sourceChanges.length,
1013
+ });
1014
+ }
1015
+ else {
1016
+ logger.info('review', 'Source code changes detected', {
1017
+ storyId: story.frontmatter.id,
1018
+ fileCount: sourceChanges.length,
1019
+ });
1020
+ }
1021
+ }
1022
+ // Check configuration changes for 'configuration' and 'mixed' types
1023
+ if (!validationFailed && (contentType === 'configuration' || contentType === 'mixed')) {
1024
+ const configChanges = getConfigurationChanges(workingDir);
1025
+ if (configChanges.length === 0) {
1026
+ validationFailed = true;
1027
+ validationReason = contentType === 'mixed'
1028
+ ? 'Mixed story requires both source AND configuration changes. No configuration file changes detected.'
1029
+ : 'Configuration story requires changes to config files (.claude/, .github/, or root config files). No configuration changes detected.';
1030
+ logger.warn('review', 'Configuration validation failed', {
1031
+ storyId: story.frontmatter.id,
1032
+ contentType,
1033
+ configChangesFound: configChanges.length,
1034
+ });
1035
+ }
1036
+ else {
1037
+ logger.info('review', 'Configuration changes detected', {
1038
+ storyId: story.frontmatter.id,
1039
+ fileCount: configChanges.length,
1040
+ });
1041
+ }
1042
+ }
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
+ }
1061
+ }
1062
+ // Handle validation failure (if any)
1063
+ if (validationFailed) {
820
1064
  const retryCount = story.frontmatter.implementation_retry_count || 0;
821
1065
  const maxRetries = getEffectiveMaxImplementationRetries(story, config);
822
1066
  if (retryCount < maxRetries) {
823
1067
  // RECOVERABLE: Trigger implementation recovery
824
- logger.warn('review', 'No source code changes detected - triggering implementation recovery', {
1068
+ logger.warn('review', 'Validation failed - triggering implementation recovery', {
825
1069
  storyId: story.frontmatter.id,
826
1070
  retryCount,
827
1071
  maxRetries,
1072
+ contentType,
828
1073
  });
829
1074
  await updateStoryField(story, 'implementation_complete', false);
830
- await updateStoryField(story, 'last_restart_reason', 'No source code changes detected. Implementation wrote documentation only.');
1075
+ // Set restart reason based on content type
1076
+ const restartReason = contentType === 'configuration'
1077
+ ? 'Configuration story requires changes to config files (.claude/, .github/, or root config files). No configuration changes detected.'
1078
+ : contentType === 'mixed'
1079
+ ? 'Mixed story requires both source AND configuration changes - no source code was modified.'
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.';
1083
+ await updateStoryField(story, 'last_restart_reason', restartReason);
1084
+ // Create user-friendly recovery description
1085
+ const recoveryDescription = contentType === 'configuration'
1086
+ ? 'No configuration file modifications detected. Re-running implementation phase.'
1087
+ : contentType === 'mixed'
1088
+ ? '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.';
831
1092
  return {
832
1093
  success: true,
833
1094
  story: parseStory(storyPath),
834
- changesMade: ['Detected documentation-only implementation', 'Triggered implementation recovery'],
1095
+ changesMade: ['Detected incomplete implementation', 'Triggered implementation recovery'],
835
1096
  passed: false,
836
1097
  decision: ReviewDecision.RECOVERY,
837
1098
  reviewType: 'pre-check',
838
1099
  issues: [{
839
1100
  severity: 'critical',
840
- category: 'implementation',
841
- description: 'No source code modifications detected. Re-running implementation phase.',
1101
+ category: validationCategory,
1102
+ description: recoveryDescription,
842
1103
  }],
843
- feedback: 'Implementation recovery triggered - no source changes found.',
1104
+ feedback: `Implementation recovery triggered - ${validationReason}`,
844
1105
  };
845
1106
  }
846
1107
  else {
847
1108
  // NON-RECOVERABLE: Max retries reached
848
1109
  const maxRetriesDisplay = Number.isFinite(maxRetries) ? maxRetries : '∞';
849
- logger.error('review', 'No source code changes detected and max implementation retries reached', {
1110
+ logger.error('review', 'Validation failed and max implementation retries reached', {
850
1111
  storyId: story.frontmatter.id,
851
1112
  retryCount,
852
1113
  maxRetries,
1114
+ contentType,
853
1115
  });
854
1116
  return {
855
1117
  success: true,
856
1118
  story: parseStory(storyPath),
857
- changesMade: ['Detected documentation-only implementation', 'Max retries reached'],
1119
+ changesMade: ['Detected incomplete implementation', 'Max retries reached'],
858
1120
  passed: false,
859
1121
  decision: ReviewDecision.FAILED,
860
1122
  severity: ReviewSeverity.CRITICAL,
861
1123
  reviewType: 'pre-check',
862
1124
  issues: [{
863
1125
  severity: 'blocker',
864
- category: 'implementation',
865
- description: `Implementation phase wrote documentation/planning only - no source code was modified. This has occurred ${retryCount} time(s) (max: ${maxRetriesDisplay}). Manual intervention required.`,
866
- suggestedFix: 'Review the story requirements and implementation plan. The agent may be confused about what needs to be built. Consider simplifying the story or providing more explicit guidance.',
1126
+ category: validationCategory,
1127
+ description: `${validationReason} This has occurred ${retryCount} time(s) (max: ${maxRetriesDisplay}). Manual intervention required.`,
1128
+ suggestedFix: 'Review the story requirements and implementation plan. Verify the content_type field matches the expected implementation. Consider simplifying the story or providing more explicit guidance.',
867
1129
  }],
868
- feedback: 'Implementation failed to produce code changes after multiple attempts.',
1130
+ feedback: 'Implementation failed validation after multiple attempts.',
869
1131
  };
870
1132
  }
871
1133
  }
872
- // Source changes exist - proceed with normal review flow
873
- logger.info('review', 'Source code changes detected - proceeding with verification', {
1134
+ // Validation passed - proceed with normal review flow
1135
+ logger.info('review', 'Content validation passed - proceeding with verification', {
874
1136
  storyId: story.frontmatter.id,
875
- fileCount: sourceChanges.length,
1137
+ contentType,
876
1138
  });
877
- // PRE-CHECK GATE: Check if test files exist
878
- const testsExist = hasTestFiles(workingDir);
879
- if (!testsExist) {
880
- 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', {
881
1173
  storyId: story.frontmatter.id,
1174
+ contentType,
882
1175
  });
883
- return {
884
- success: true,
885
- story: parseStory(storyPath),
886
- changesMade: ['No test files found for implementation'],
887
- passed: false,
888
- decision: ReviewDecision.REJECTED,
889
- severity: ReviewSeverity.CRITICAL,
890
- reviewType: 'pre-check',
891
- issues: [{
892
- severity: 'blocker',
893
- category: 'testing',
894
- description: 'No tests found for this implementation. All implementations must include tests.',
895
- suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
896
- }],
897
- feedback: formatIssuesForDisplay([{
898
- severity: 'blocker',
899
- category: 'testing',
900
- description: 'No tests found for this implementation. All implementations must include tests.',
901
- suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
902
- }]),
903
- };
904
1176
  }
905
1177
  // Run build and tests BEFORE reviews (async with progress)
906
1178
  changesMade.push('Running build and test verification...');