ai-sdlc 0.2.0 → 0.3.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 +19 -6
- package/dist/agents/implementation.d.ts +30 -1
- package/dist/agents/implementation.d.ts.map +1 -1
- package/dist/agents/implementation.js +172 -17
- package/dist/agents/implementation.js.map +1 -1
- package/dist/agents/index.d.ts +2 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/orchestrator.d.ts +61 -0
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/agents/orchestrator.js +443 -0
- package/dist/agents/orchestrator.js.map +1 -0
- package/dist/agents/planning.d.ts +1 -1
- package/dist/agents/planning.d.ts.map +1 -1
- package/dist/agents/planning.js +33 -1
- package/dist/agents/planning.js.map +1 -1
- package/dist/agents/review.d.ts +50 -1
- package/dist/agents/review.d.ts.map +1 -1
- package/dist/agents/review.js +389 -44
- package/dist/agents/review.js.map +1 -1
- package/dist/agents/rework.d.ts.map +1 -1
- package/dist/agents/rework.js +3 -1
- package/dist/agents/rework.js.map +1 -1
- package/dist/agents/single-task.d.ts +41 -0
- package/dist/agents/single-task.d.ts.map +1 -0
- package/dist/agents/single-task.js +357 -0
- package/dist/agents/single-task.js.map +1 -0
- package/dist/agents/verification.d.ts.map +1 -1
- package/dist/agents/verification.js +26 -12
- package/dist/agents/verification.js.map +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 +8 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +777 -48
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +5 -0
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/runner.d.ts.map +1 -1
- package/dist/cli/runner.js +20 -9
- package/dist/cli/runner.js.map +1 -1
- package/dist/core/client.d.ts +19 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +191 -5
- package/dist/core/client.js.map +1 -1
- package/dist/core/config.d.ts +9 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +51 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/llm-utils.d.ts +103 -0
- package/dist/core/llm-utils.d.ts.map +1 -0
- package/dist/core/llm-utils.js +368 -0
- package/dist/core/llm-utils.js.map +1 -0
- package/dist/core/process-manager.d.ts +15 -0
- package/dist/core/process-manager.d.ts.map +1 -0
- package/dist/core/process-manager.js +132 -0
- package/dist/core/process-manager.js.map +1 -0
- package/dist/core/story.d.ts +35 -1
- package/dist/core/story.d.ts.map +1 -1
- package/dist/core/story.js +107 -1
- package/dist/core/story.js.map +1 -1
- package/dist/core/task-parser.d.ts +59 -0
- package/dist/core/task-parser.d.ts.map +1 -0
- package/dist/core/task-parser.js +235 -0
- package/dist/core/task-parser.js.map +1 -0
- package/dist/core/task-progress.d.ts +92 -0
- package/dist/core/task-progress.d.ts.map +1 -0
- package/dist/core/task-progress.js +280 -0
- package/dist/core/task-progress.js.map +1 -0
- package/dist/core/worktree.d.ts +111 -2
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +310 -2
- package/dist/core/worktree.js.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/services/error-classifier.d.ts +119 -0
- package/dist/services/error-classifier.d.ts.map +1 -0
- package/dist/services/error-classifier.js +182 -0
- package/dist/services/error-classifier.js.map +1 -0
- package/dist/types/index.d.ts +230 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -2
- package/templates/story.md +5 -0
package/dist/agents/review.js
CHANGED
|
@@ -2,10 +2,12 @@ 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';
|
|
8
9
|
import { loadConfig, DEFAULT_TIMEOUTS } from '../core/config.js';
|
|
10
|
+
import { extractStructuredResponseSync } from '../core/llm-utils.js';
|
|
9
11
|
import { ReviewDecision, ReviewSeverity } from '../types/index.js';
|
|
10
12
|
import { sanitizeInput, truncateText } from '../cli/formatting.js';
|
|
11
13
|
import { detectTestDuplicationPatterns } from './test-pattern-detector.js';
|
|
@@ -172,6 +174,7 @@ async function runCommandAsync(command, workingDir, timeout, onProgress) {
|
|
|
172
174
|
cwd: workingDir,
|
|
173
175
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
174
176
|
});
|
|
177
|
+
ProcessManager.getInstance().registerChild(child);
|
|
175
178
|
const timeoutId = setTimeout(() => {
|
|
176
179
|
killed = true;
|
|
177
180
|
child.kill('SIGTERM');
|
|
@@ -252,7 +255,7 @@ Output your review as a JSON object with this structure:
|
|
|
252
255
|
"issues": [
|
|
253
256
|
{
|
|
254
257
|
"severity": "blocker" | "critical" | "major" | "minor",
|
|
255
|
-
"category": "code_quality" | "security" | "requirements" | "testing" | etc,
|
|
258
|
+
"category": "code_quality" | "security" | "requirements" | "testing" | "test_alignment" | etc,
|
|
256
259
|
"description": "Detailed description of the issue",
|
|
257
260
|
"file": "path/to/file.ts" (if applicable),
|
|
258
261
|
"line": 42 (if applicable),
|
|
@@ -263,7 +266,7 @@ Output your review as a JSON object with this structure:
|
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
Severity guidelines:
|
|
266
|
-
- blocker: Must be fixed before merging (security holes, broken functionality)
|
|
269
|
+
- blocker: Must be fixed before merging (security holes, broken functionality, test misalignment)
|
|
267
270
|
- critical: Should be fixed before merging (major bugs, poor practices)
|
|
268
271
|
- major: Should be addressed soon (code quality, maintainability)
|
|
269
272
|
- minor: Nice to have improvements (style, optimizations)
|
|
@@ -304,6 +307,56 @@ Evaluate:
|
|
|
304
307
|
- Is documentation adequate for users and maintainers?
|
|
305
308
|
- Does the implementation align with the story goals?
|
|
306
309
|
|
|
310
|
+
## Test-Implementation Alignment (BLOCKER category)
|
|
311
|
+
|
|
312
|
+
**CRITICAL PRE-REVIEW REQUIREMENT**: Tests have already been executed and passed. However, passing tests don't guarantee correctness if they verify outdated behavior.
|
|
313
|
+
|
|
314
|
+
During code review, you MUST verify test alignment:
|
|
315
|
+
|
|
316
|
+
1. **For each changed production file, identify its test file**
|
|
317
|
+
- Check if tests exist for modified functions/modules
|
|
318
|
+
- Read the test assertions carefully
|
|
319
|
+
|
|
320
|
+
2. **Verify tests match NEW behavior, not OLD**
|
|
321
|
+
- Do test assertions expect the current implementation behavior?
|
|
322
|
+
- If production code changed from sync to async, do tests use await?
|
|
323
|
+
- If function signature changed, do tests call it correctly?
|
|
324
|
+
- If return values changed, do tests expect the new values?
|
|
325
|
+
|
|
326
|
+
3. **Flag misalignment as BLOCKER**
|
|
327
|
+
- If tests reference changed code but still expect old behavior:
|
|
328
|
+
- This is a **BLOCKER** severity issue
|
|
329
|
+
- Category MUST be: \`"test_alignment"\`
|
|
330
|
+
- Specify which test files need updating and why
|
|
331
|
+
- Provide example of correct assertion for new behavior
|
|
332
|
+
|
|
333
|
+
**Example of misaligned test (BLOCKER):**
|
|
334
|
+
\`\`\`typescript
|
|
335
|
+
// Production code changed from sync to async
|
|
336
|
+
async function loadConfig(): Promise<Config> {
|
|
337
|
+
return await fetchConfig();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Test still expects sync behavior - MISSING await (BLOCKER)
|
|
341
|
+
test('loads config', () => {
|
|
342
|
+
const config = loadConfig(); // ❌ Missing await! Returns Promise<Config>, not Config
|
|
343
|
+
expect(config.port).toBe(3000); // ❌ Checking Promise.port, not config.port
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Correct aligned test:
|
|
347
|
+
test('loads config', async () => {
|
|
348
|
+
const config = await loadConfig(); // ✅ Awaits async function
|
|
349
|
+
expect(config.port).toBe(3000); // ✅ Checks actual config
|
|
350
|
+
});
|
|
351
|
+
\`\`\`
|
|
352
|
+
|
|
353
|
+
**When to flag test_alignment issues:**
|
|
354
|
+
- Tests verify old function signatures that no longer exist
|
|
355
|
+
- Tests expect old return value formats that changed
|
|
356
|
+
- Tests miss new error conditions introduced
|
|
357
|
+
- Tests pass but don't exercise the new code paths
|
|
358
|
+
- Mock expectations don't match the new implementation calls
|
|
359
|
+
|
|
307
360
|
## CRITICAL DEDUPLICATION INSTRUCTIONS:
|
|
308
361
|
|
|
309
362
|
1. **DO NOT repeat the same underlying issue from different perspectives**
|
|
@@ -363,26 +416,25 @@ const PO_REVIEW_PROMPT = `You are a product owner validating the implementation.
|
|
|
363
416
|
${REVIEW_OUTPUT_FORMAT}`;
|
|
364
417
|
/**
|
|
365
418
|
* Parse review response and extract structured issues
|
|
419
|
+
* Uses extractStructuredResponseSync for robust parsing with multiple strategies:
|
|
420
|
+
* 1. Direct JSON parse
|
|
421
|
+
* 2. JSON within markdown code blocks
|
|
422
|
+
* 3. JSON with leading/trailing text stripped
|
|
423
|
+
* 4. YAML format fallback
|
|
424
|
+
*
|
|
366
425
|
* Security: Uses zod schema validation to prevent malicious JSON
|
|
367
426
|
*/
|
|
368
427
|
function parseReviewResponse(response, reviewType) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (!validationResult.success) {
|
|
380
|
-
// Log validation errors for debugging
|
|
381
|
-
console.warn('Review response failed schema validation:', validationResult.error);
|
|
382
|
-
// Fallback to text analysis
|
|
383
|
-
return parseTextReview(response, reviewType);
|
|
384
|
-
}
|
|
385
|
-
const validated = validationResult.data;
|
|
428
|
+
const logger = getLogger();
|
|
429
|
+
// Use the robust extraction utility with all strategies
|
|
430
|
+
const extractionResult = extractStructuredResponseSync(response, ReviewResponseSchema, false);
|
|
431
|
+
if (extractionResult.success && extractionResult.data) {
|
|
432
|
+
const validated = extractionResult.data;
|
|
433
|
+
logger.debug('review', `Successfully parsed review response using strategy: ${extractionResult.strategy}`, {
|
|
434
|
+
reviewType,
|
|
435
|
+
strategy: extractionResult.strategy,
|
|
436
|
+
issueCount: validated.issues.length,
|
|
437
|
+
});
|
|
386
438
|
// Map validated data to ReviewIssue format (additional sanitization)
|
|
387
439
|
const issues = validated.issues.map((issue) => ({
|
|
388
440
|
severity: issue.severity,
|
|
@@ -398,11 +450,13 @@ function parseReviewResponse(response, reviewType) {
|
|
|
398
450
|
issues,
|
|
399
451
|
};
|
|
400
452
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
453
|
+
// All extraction strategies failed - log raw response for debugging and use text fallback
|
|
454
|
+
logger.warn('review', 'All extraction strategies failed for review response', {
|
|
455
|
+
reviewType,
|
|
456
|
+
error: extractionResult.error,
|
|
457
|
+
responsePreview: response.substring(0, 200),
|
|
458
|
+
});
|
|
459
|
+
return parseTextReview(response, reviewType);
|
|
406
460
|
}
|
|
407
461
|
/**
|
|
408
462
|
* Fallback: Parse text-based review response (for when LLM doesn't return JSON)
|
|
@@ -572,6 +626,170 @@ export function getSourceCodeChanges(workingDir) {
|
|
|
572
626
|
return ['unknown'];
|
|
573
627
|
}
|
|
574
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Get configuration file changes from git diff
|
|
631
|
+
*
|
|
632
|
+
* Detects changes to configuration files including:
|
|
633
|
+
* - .claude/ directory (Agent SDK skills, CLAUDE.md)
|
|
634
|
+
* - .github/ directory (workflows, actions, issue templates)
|
|
635
|
+
* - Root config files (tsconfig.json, package.json, .gitignore, vitest.config.ts, etc.)
|
|
636
|
+
*
|
|
637
|
+
* Uses spawnSync for security (prevents command injection).
|
|
638
|
+
*
|
|
639
|
+
* @param workingDir - Working directory to run git diff in
|
|
640
|
+
* @returns Array of configuration file paths that have changed, or ['unknown'] if git fails
|
|
641
|
+
*/
|
|
642
|
+
export function getConfigurationChanges(workingDir) {
|
|
643
|
+
try {
|
|
644
|
+
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
645
|
+
const result = spawnSync('git', ['diff', '--name-only', 'HEAD~1'], {
|
|
646
|
+
cwd: workingDir,
|
|
647
|
+
encoding: 'utf-8',
|
|
648
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
649
|
+
});
|
|
650
|
+
if (result.status !== 0) {
|
|
651
|
+
// Git command failed - fail open (assume changes exist)
|
|
652
|
+
return ['unknown'];
|
|
653
|
+
}
|
|
654
|
+
const output = result.stdout.toString();
|
|
655
|
+
return output
|
|
656
|
+
.split('\n')
|
|
657
|
+
.filter(f => f.trim())
|
|
658
|
+
.filter(f => {
|
|
659
|
+
// Configuration directories
|
|
660
|
+
if (f.startsWith('.claude/'))
|
|
661
|
+
return true;
|
|
662
|
+
if (f.startsWith('.github/'))
|
|
663
|
+
return true;
|
|
664
|
+
// Root configuration files (common patterns)
|
|
665
|
+
const rootConfigs = [
|
|
666
|
+
'tsconfig.json',
|
|
667
|
+
'package.json',
|
|
668
|
+
'package-lock.json',
|
|
669
|
+
'.gitignore',
|
|
670
|
+
'.gitattributes',
|
|
671
|
+
'vitest.config.ts',
|
|
672
|
+
'vitest.config.js',
|
|
673
|
+
'jest.config.js',
|
|
674
|
+
'jest.config.ts',
|
|
675
|
+
'.eslintrc',
|
|
676
|
+
'.eslintrc.js',
|
|
677
|
+
'.eslintrc.json',
|
|
678
|
+
'.prettierrc',
|
|
679
|
+
'.prettierrc.js',
|
|
680
|
+
'.prettierrc.json',
|
|
681
|
+
'Makefile',
|
|
682
|
+
'Dockerfile',
|
|
683
|
+
'docker-compose.yml',
|
|
684
|
+
'.env.example',
|
|
685
|
+
];
|
|
686
|
+
return rootConfigs.includes(f);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
// If git diff fails, assume there are changes (fail open, not closed)
|
|
691
|
+
return ['unknown'];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Get documentation file changes from git diff
|
|
696
|
+
*
|
|
697
|
+
* Detects changes to documentation files including:
|
|
698
|
+
* - Markdown files (.md) anywhere in the project (excluding story files)
|
|
699
|
+
* - docs/ directory (any file type)
|
|
700
|
+
*
|
|
701
|
+
* Uses spawnSync for security (prevents command injection).
|
|
702
|
+
*
|
|
703
|
+
* @param workingDir - Working directory to run git diff in
|
|
704
|
+
* @returns Array of documentation file paths that have changed, or ['unknown'] if git fails
|
|
705
|
+
*/
|
|
706
|
+
export function getDocumentationChanges(workingDir) {
|
|
707
|
+
try {
|
|
708
|
+
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
709
|
+
const result = spawnSync('git', ['diff', '--name-only', 'HEAD~1'], {
|
|
710
|
+
cwd: workingDir,
|
|
711
|
+
encoding: 'utf-8',
|
|
712
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
713
|
+
});
|
|
714
|
+
if (result.status !== 0) {
|
|
715
|
+
// Git command failed - fail open (assume changes exist)
|
|
716
|
+
return ['unknown'];
|
|
717
|
+
}
|
|
718
|
+
const output = result.stdout.toString();
|
|
719
|
+
return output
|
|
720
|
+
.split('\n')
|
|
721
|
+
.filter(f => f.trim())
|
|
722
|
+
.filter(f => {
|
|
723
|
+
// Markdown files (excluding story files in .ai-sdlc/stories/)
|
|
724
|
+
if (f.endsWith('.md') && !f.startsWith('.ai-sdlc/stories/'))
|
|
725
|
+
return true;
|
|
726
|
+
// Files in docs/ directory (any file type - images, diagrams, etc.)
|
|
727
|
+
if (f.startsWith('docs/'))
|
|
728
|
+
return true;
|
|
729
|
+
return false;
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
// If git diff fails, assume there are changes (fail open, not closed)
|
|
734
|
+
return ['unknown'];
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Determine the effective content type for validation
|
|
739
|
+
*
|
|
740
|
+
* Resolves the final content type based on story frontmatter fields:
|
|
741
|
+
* 1. If requires_source_changes === false, treat as 'configuration'
|
|
742
|
+
* 2. If requires_source_changes === true, treat as 'code'
|
|
743
|
+
* 3. Otherwise, use content_type field (default: 'code' for backward compatibility)
|
|
744
|
+
*
|
|
745
|
+
* @param story - Story with frontmatter to analyze
|
|
746
|
+
* @returns The effective content type to use for validation
|
|
747
|
+
*/
|
|
748
|
+
export function determineEffectiveContentType(story) {
|
|
749
|
+
const frontmatter = story.frontmatter;
|
|
750
|
+
// Manual override takes precedence
|
|
751
|
+
if (frontmatter.requires_source_changes === false) {
|
|
752
|
+
return 'configuration';
|
|
753
|
+
}
|
|
754
|
+
if (frontmatter.requires_source_changes === true) {
|
|
755
|
+
return 'code';
|
|
756
|
+
}
|
|
757
|
+
// Use explicit content_type or default to 'code'
|
|
758
|
+
return frontmatter.content_type || 'code';
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Check if test files exist in git diff
|
|
762
|
+
*
|
|
763
|
+
* Returns true if any test files have been modified/added, false otherwise.
|
|
764
|
+
* Uses spawnSync for security (prevents command injection).
|
|
765
|
+
*
|
|
766
|
+
* @param workingDir - Working directory to run git diff in
|
|
767
|
+
* @returns True if test files exist in changes, false otherwise
|
|
768
|
+
*/
|
|
769
|
+
export function hasTestFiles(workingDir) {
|
|
770
|
+
try {
|
|
771
|
+
// Security: Use spawnSync with explicit args (not shell) to prevent injection
|
|
772
|
+
const result = spawnSync('git', ['diff', '--name-only', 'HEAD~1'], {
|
|
773
|
+
cwd: workingDir,
|
|
774
|
+
encoding: 'utf-8',
|
|
775
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
776
|
+
});
|
|
777
|
+
if (result.status !== 0) {
|
|
778
|
+
// Git command failed - fail open (assume tests exist to avoid false blocks)
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
const output = result.stdout.toString();
|
|
782
|
+
const files = output.split('\n').filter(f => f.trim());
|
|
783
|
+
// Check if any files match test patterns
|
|
784
|
+
return files.some(f => f.includes('.test.') ||
|
|
785
|
+
f.includes('.spec.') ||
|
|
786
|
+
f.includes('__tests__/'));
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
// If git diff fails, assume tests exist (fail open, not closed)
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
575
793
|
/**
|
|
576
794
|
* Generate executive summary from review issues (1-3 sentences)
|
|
577
795
|
*
|
|
@@ -728,67 +946,194 @@ export async function runReviewAgent(storyPath, sdlcRoot, options) {
|
|
|
728
946
|
feedback: errorMsg,
|
|
729
947
|
};
|
|
730
948
|
}
|
|
731
|
-
// PRE-CHECK GATE:
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
949
|
+
// PRE-CHECK GATE: Content type-aware validation before running expensive LLM reviews
|
|
950
|
+
const contentType = determineEffectiveContentType(story);
|
|
951
|
+
logger.info('review', 'Running content-type-specific validation', {
|
|
952
|
+
storyId: story.frontmatter.id,
|
|
953
|
+
contentType,
|
|
954
|
+
explicitContentType: story.frontmatter.content_type,
|
|
955
|
+
requiresSourceChanges: story.frontmatter.requires_source_changes,
|
|
956
|
+
});
|
|
957
|
+
// Validation flags
|
|
958
|
+
let validationFailed = false;
|
|
959
|
+
let validationReason = '';
|
|
960
|
+
let validationCategory = 'implementation';
|
|
961
|
+
// Check source code changes for 'code' and 'mixed' types
|
|
962
|
+
if (contentType === 'code' || contentType === 'mixed') {
|
|
963
|
+
const sourceChanges = getSourceCodeChanges(workingDir);
|
|
964
|
+
if (sourceChanges.length === 0) {
|
|
965
|
+
validationFailed = true;
|
|
966
|
+
validationReason = contentType === 'mixed'
|
|
967
|
+
? 'Mixed story requires both source AND configuration changes - no source code was modified.'
|
|
968
|
+
: 'Implementation wrote documentation/planning only - no source code was modified.';
|
|
969
|
+
logger.warn('review', 'Source code validation failed', {
|
|
970
|
+
storyId: story.frontmatter.id,
|
|
971
|
+
contentType,
|
|
972
|
+
sourceChangesFound: sourceChanges.length,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
logger.info('review', 'Source code changes detected', {
|
|
977
|
+
storyId: story.frontmatter.id,
|
|
978
|
+
fileCount: sourceChanges.length,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
// Check configuration changes for 'configuration' and 'mixed' types
|
|
983
|
+
if (!validationFailed && (contentType === 'configuration' || contentType === 'mixed')) {
|
|
984
|
+
const configChanges = getConfigurationChanges(workingDir);
|
|
985
|
+
if (configChanges.length === 0) {
|
|
986
|
+
validationFailed = true;
|
|
987
|
+
validationReason = contentType === 'mixed'
|
|
988
|
+
? 'Mixed story requires both source AND configuration changes. No configuration file changes detected.'
|
|
989
|
+
: 'Configuration story requires changes to config files (.claude/, .github/, or root config files). No configuration changes detected.';
|
|
990
|
+
logger.warn('review', 'Configuration validation failed', {
|
|
991
|
+
storyId: story.frontmatter.id,
|
|
992
|
+
contentType,
|
|
993
|
+
configChangesFound: configChanges.length,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
logger.info('review', 'Configuration changes detected', {
|
|
998
|
+
storyId: story.frontmatter.id,
|
|
999
|
+
fileCount: configChanges.length,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
// Check documentation changes for 'documentation' type
|
|
1004
|
+
if (!validationFailed && contentType === 'documentation') {
|
|
1005
|
+
const docChanges = getDocumentationChanges(workingDir);
|
|
1006
|
+
if (docChanges.length === 0) {
|
|
1007
|
+
validationFailed = true;
|
|
1008
|
+
validationReason = 'Documentation story requires changes to markdown files (.md) or docs/ directory. No documentation changes detected.';
|
|
1009
|
+
logger.warn('review', 'Documentation validation failed', {
|
|
1010
|
+
storyId: story.frontmatter.id,
|
|
1011
|
+
contentType,
|
|
1012
|
+
docChangesFound: docChanges.length,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
logger.info('review', 'Documentation changes detected', {
|
|
1017
|
+
storyId: story.frontmatter.id,
|
|
1018
|
+
fileCount: docChanges.length,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
// Handle validation failure (if any)
|
|
1023
|
+
if (validationFailed) {
|
|
735
1024
|
const retryCount = story.frontmatter.implementation_retry_count || 0;
|
|
736
1025
|
const maxRetries = getEffectiveMaxImplementationRetries(story, config);
|
|
737
1026
|
if (retryCount < maxRetries) {
|
|
738
1027
|
// RECOVERABLE: Trigger implementation recovery
|
|
739
|
-
logger.warn('review', '
|
|
1028
|
+
logger.warn('review', 'Validation failed - triggering implementation recovery', {
|
|
740
1029
|
storyId: story.frontmatter.id,
|
|
741
1030
|
retryCount,
|
|
742
1031
|
maxRetries,
|
|
1032
|
+
contentType,
|
|
743
1033
|
});
|
|
744
1034
|
await updateStoryField(story, 'implementation_complete', false);
|
|
745
|
-
|
|
1035
|
+
// Set restart reason based on content type
|
|
1036
|
+
const restartReason = contentType === 'configuration'
|
|
1037
|
+
? 'Configuration story requires changes to config files (.claude/, .github/, or root config files). No configuration changes detected.'
|
|
1038
|
+
: contentType === 'mixed'
|
|
1039
|
+
? 'Mixed story requires both source AND configuration changes - no source code was modified.'
|
|
1040
|
+
: contentType === 'documentation'
|
|
1041
|
+
? 'Documentation story requires changes to markdown files (.md) or docs/ directory. No documentation changes detected.'
|
|
1042
|
+
: 'No source code changes detected. Implementation wrote documentation only.';
|
|
1043
|
+
await updateStoryField(story, 'last_restart_reason', restartReason);
|
|
1044
|
+
// Create user-friendly recovery description
|
|
1045
|
+
const recoveryDescription = contentType === 'configuration'
|
|
1046
|
+
? 'No configuration file modifications detected. Re-running implementation phase.'
|
|
1047
|
+
: contentType === 'mixed'
|
|
1048
|
+
? 'No source code modifications detected. Re-running implementation phase.'
|
|
1049
|
+
: contentType === 'documentation'
|
|
1050
|
+
? 'No documentation file modifications detected. Re-running implementation phase.'
|
|
1051
|
+
: 'No source code modifications detected. Re-running implementation phase.';
|
|
746
1052
|
return {
|
|
747
1053
|
success: true,
|
|
748
1054
|
story: parseStory(storyPath),
|
|
749
|
-
changesMade: ['Detected
|
|
1055
|
+
changesMade: ['Detected incomplete implementation', 'Triggered implementation recovery'],
|
|
750
1056
|
passed: false,
|
|
751
1057
|
decision: ReviewDecision.RECOVERY,
|
|
752
1058
|
reviewType: 'pre-check',
|
|
753
1059
|
issues: [{
|
|
754
1060
|
severity: 'critical',
|
|
755
|
-
category:
|
|
756
|
-
description:
|
|
1061
|
+
category: validationCategory,
|
|
1062
|
+
description: recoveryDescription,
|
|
757
1063
|
}],
|
|
758
|
-
feedback:
|
|
1064
|
+
feedback: `Implementation recovery triggered - ${validationReason}`,
|
|
759
1065
|
};
|
|
760
1066
|
}
|
|
761
1067
|
else {
|
|
762
1068
|
// NON-RECOVERABLE: Max retries reached
|
|
763
1069
|
const maxRetriesDisplay = Number.isFinite(maxRetries) ? maxRetries : '∞';
|
|
764
|
-
logger.error('review', '
|
|
1070
|
+
logger.error('review', 'Validation failed and max implementation retries reached', {
|
|
765
1071
|
storyId: story.frontmatter.id,
|
|
766
1072
|
retryCount,
|
|
767
1073
|
maxRetries,
|
|
1074
|
+
contentType,
|
|
768
1075
|
});
|
|
769
1076
|
return {
|
|
770
1077
|
success: true,
|
|
771
1078
|
story: parseStory(storyPath),
|
|
772
|
-
changesMade: ['Detected
|
|
1079
|
+
changesMade: ['Detected incomplete implementation', 'Max retries reached'],
|
|
773
1080
|
passed: false,
|
|
774
1081
|
decision: ReviewDecision.FAILED,
|
|
775
1082
|
severity: ReviewSeverity.CRITICAL,
|
|
776
1083
|
reviewType: 'pre-check',
|
|
777
1084
|
issues: [{
|
|
778
1085
|
severity: 'blocker',
|
|
779
|
-
category:
|
|
780
|
-
description:
|
|
781
|
-
suggestedFix: 'Review the story requirements and implementation plan.
|
|
1086
|
+
category: validationCategory,
|
|
1087
|
+
description: `${validationReason} This has occurred ${retryCount} time(s) (max: ${maxRetriesDisplay}). Manual intervention required.`,
|
|
1088
|
+
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.',
|
|
782
1089
|
}],
|
|
783
|
-
feedback: 'Implementation failed
|
|
1090
|
+
feedback: 'Implementation failed validation after multiple attempts.',
|
|
784
1091
|
};
|
|
785
1092
|
}
|
|
786
1093
|
}
|
|
787
|
-
//
|
|
788
|
-
logger.info('review', '
|
|
1094
|
+
// Validation passed - proceed with normal review flow
|
|
1095
|
+
logger.info('review', 'Content validation passed - proceeding with verification', {
|
|
789
1096
|
storyId: story.frontmatter.id,
|
|
790
|
-
|
|
1097
|
+
contentType,
|
|
791
1098
|
});
|
|
1099
|
+
// PRE-CHECK GATE: Check if test files exist (only for code/mixed types)
|
|
1100
|
+
// Documentation and configuration stories don't require test files
|
|
1101
|
+
const requiresTests = contentType === 'code' || contentType === 'mixed';
|
|
1102
|
+
if (requiresTests) {
|
|
1103
|
+
const testsExist = hasTestFiles(workingDir);
|
|
1104
|
+
if (!testsExist) {
|
|
1105
|
+
logger.warn('review', 'No test files detected in implementation changes', {
|
|
1106
|
+
storyId: story.frontmatter.id,
|
|
1107
|
+
});
|
|
1108
|
+
return {
|
|
1109
|
+
success: true,
|
|
1110
|
+
story: parseStory(storyPath),
|
|
1111
|
+
changesMade: ['No test files found for implementation'],
|
|
1112
|
+
passed: false,
|
|
1113
|
+
decision: ReviewDecision.REJECTED,
|
|
1114
|
+
severity: ReviewSeverity.CRITICAL,
|
|
1115
|
+
reviewType: 'pre-check',
|
|
1116
|
+
issues: [{
|
|
1117
|
+
severity: 'blocker',
|
|
1118
|
+
category: 'testing',
|
|
1119
|
+
description: 'No tests found for this implementation. All implementations must include tests.',
|
|
1120
|
+
suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
|
|
1121
|
+
}],
|
|
1122
|
+
feedback: formatIssuesForDisplay([{
|
|
1123
|
+
severity: 'blocker',
|
|
1124
|
+
category: 'testing',
|
|
1125
|
+
description: 'No tests found for this implementation. All implementations must include tests.',
|
|
1126
|
+
suggestedFix: 'Add test files (*.test.ts, *.spec.ts, or files in __tests__/ directory) that verify the implementation.',
|
|
1127
|
+
}]),
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
logger.info('review', 'Test file check skipped for non-code content type', {
|
|
1133
|
+
storyId: story.frontmatter.id,
|
|
1134
|
+
contentType,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
792
1137
|
// Run build and tests BEFORE reviews (async with progress)
|
|
793
1138
|
changesMade.push('Running build and test verification...');
|
|
794
1139
|
const verification = await runVerificationAsync(workingDir, config, options?.onVerificationProgress);
|
|
@@ -835,7 +1180,7 @@ export async function runReviewAgent(storyPath, sdlcRoot, options) {
|
|
|
835
1180
|
severity: 'blocker',
|
|
836
1181
|
category: 'testing',
|
|
837
1182
|
description: `Tests must pass before code review can proceed.\n\nCommand: ${config.testCommand}\n\nTest output:\n\`\`\`\n${testOutput}${truncationNote}\n\`\`\``,
|
|
838
|
-
suggestedFix: 'Fix failing tests before review can proceed.',
|
|
1183
|
+
suggestedFix: 'Fix failing tests before review can proceed. If tests are failing after implementation changes, verify that tests were updated to match the new behavior (not just the old behavior).',
|
|
839
1184
|
});
|
|
840
1185
|
verificationContext += `\n## Test Results ❌\nTest command \`${config.testCommand}\` FAILED:\n\`\`\`\n${testOutput}${truncationNote}\n\`\`\`\n`;
|
|
841
1186
|
}
|