@hyperdrive.bot/bmad-workflow 1.0.18 → 1.0.19

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 (98) hide show
  1. package/dist/commands/config/show.js +8 -2
  2. package/dist/commands/decompose.js +26 -5
  3. package/dist/commands/epics/create.d.ts +1 -0
  4. package/dist/commands/mcp/add.d.ts +16 -0
  5. package/dist/commands/mcp/add.js +77 -0
  6. package/dist/commands/mcp/credential/get.d.ts +14 -0
  7. package/dist/commands/mcp/credential/get.js +35 -0
  8. package/dist/commands/mcp/credential/list.d.ts +17 -0
  9. package/dist/commands/mcp/credential/list.js +67 -0
  10. package/dist/commands/mcp/credential/remove.d.ts +18 -0
  11. package/dist/commands/mcp/credential/remove.js +84 -0
  12. package/dist/commands/mcp/credential/set.d.ts +16 -0
  13. package/dist/commands/mcp/credential/set.js +41 -0
  14. package/dist/commands/mcp/credential/validate.d.ts +12 -0
  15. package/dist/commands/mcp/credential/validate.js +150 -0
  16. package/dist/commands/mcp/list.d.ts +17 -0
  17. package/dist/commands/mcp/list.js +80 -0
  18. package/dist/commands/mcp/logs.d.ts +15 -0
  19. package/dist/commands/mcp/logs.js +64 -0
  20. package/dist/commands/mcp/preset.d.ts +15 -0
  21. package/dist/commands/mcp/preset.js +84 -0
  22. package/dist/commands/mcp/remove.d.ts +14 -0
  23. package/dist/commands/mcp/remove.js +36 -0
  24. package/dist/commands/mcp/start.d.ts +12 -0
  25. package/dist/commands/mcp/start.js +80 -0
  26. package/dist/commands/mcp/status.d.ts +30 -0
  27. package/dist/commands/mcp/status.js +180 -0
  28. package/dist/commands/mcp/stop.d.ts +12 -0
  29. package/dist/commands/mcp/stop.js +47 -0
  30. package/dist/commands/stories/create.d.ts +1 -0
  31. package/dist/commands/stories/develop.d.ts +1 -0
  32. package/dist/commands/stories/qa.js +5 -2
  33. package/dist/commands/stories/review.d.ts +124 -0
  34. package/dist/commands/stories/review.js +516 -0
  35. package/dist/commands/workflow.d.ts +8 -0
  36. package/dist/commands/workflow.js +110 -2
  37. package/dist/mcp/types.d.ts +99 -0
  38. package/dist/mcp/types.js +7 -0
  39. package/dist/mcp/utils/docker-utils.d.ts +56 -0
  40. package/dist/mcp/utils/docker-utils.js +108 -0
  41. package/dist/mcp/utils/template-loader.d.ts +21 -0
  42. package/dist/mcp/utils/template-loader.js +60 -0
  43. package/dist/models/agent-options.d.ts +10 -1
  44. package/dist/models/workflow-config.d.ts +77 -0
  45. package/dist/models/workflow-result.d.ts +7 -0
  46. package/dist/services/agents/claude-agent-runner.js +19 -3
  47. package/dist/services/file-system/path-resolver.d.ts +10 -0
  48. package/dist/services/file-system/path-resolver.js +12 -0
  49. package/dist/services/mcp/mcp-config-manager.d.ts +54 -0
  50. package/dist/services/mcp/mcp-config-manager.js +146 -0
  51. package/dist/services/mcp/mcp-context-injector.d.ts +92 -0
  52. package/dist/services/mcp/mcp-context-injector.js +168 -0
  53. package/dist/services/mcp/mcp-credential-manager.d.ts +48 -0
  54. package/dist/services/mcp/mcp-credential-manager.js +124 -0
  55. package/dist/services/mcp/mcp-health-checker.d.ts +56 -0
  56. package/dist/services/mcp/mcp-health-checker.js +162 -0
  57. package/dist/services/mcp/types/health-types.d.ts +31 -0
  58. package/dist/services/mcp/types/health-types.js +7 -0
  59. package/dist/services/orchestration/dependency-graph-executor.js +1 -1
  60. package/dist/services/orchestration/task-decomposition-service.d.ts +2 -1
  61. package/dist/services/orchestration/task-decomposition-service.js +90 -36
  62. package/dist/services/orchestration/workflow-orchestrator.d.ts +54 -2
  63. package/dist/services/orchestration/workflow-orchestrator.js +303 -17
  64. package/dist/services/review/ai-review-scanner.d.ts +66 -0
  65. package/dist/services/review/ai-review-scanner.js +142 -0
  66. package/dist/services/review/coderabbit-scanner.d.ts +25 -0
  67. package/dist/services/review/coderabbit-scanner.js +31 -0
  68. package/dist/services/review/index.d.ts +20 -0
  69. package/dist/services/review/index.js +15 -0
  70. package/dist/services/review/lint-scanner.d.ts +46 -0
  71. package/dist/services/review/lint-scanner.js +172 -0
  72. package/dist/services/review/review-config.d.ts +62 -0
  73. package/dist/services/review/review-config.js +91 -0
  74. package/dist/services/review/review-phase-executor.d.ts +69 -0
  75. package/dist/services/review/review-phase-executor.js +152 -0
  76. package/dist/services/review/review-queue.d.ts +98 -0
  77. package/dist/services/review/review-queue.js +174 -0
  78. package/dist/services/review/review-reporter.d.ts +94 -0
  79. package/dist/services/review/review-reporter.js +386 -0
  80. package/dist/services/review/scanner-factory.d.ts +42 -0
  81. package/dist/services/review/scanner-factory.js +60 -0
  82. package/dist/services/review/self-heal-loop.d.ts +58 -0
  83. package/dist/services/review/self-heal-loop.js +132 -0
  84. package/dist/services/review/severity-classifier.d.ts +17 -0
  85. package/dist/services/review/severity-classifier.js +314 -0
  86. package/dist/services/review/tech-debt-tracker.d.ts +52 -0
  87. package/dist/services/review/tech-debt-tracker.js +245 -0
  88. package/dist/services/review/types.d.ts +93 -0
  89. package/dist/services/review/types.js +23 -0
  90. package/dist/services/validation/config-validator.d.ts +84 -0
  91. package/dist/services/validation/config-validator.js +78 -0
  92. package/dist/utils/credential-utils.d.ts +14 -0
  93. package/dist/utils/credential-utils.js +19 -0
  94. package/dist/utils/duration.d.ts +41 -0
  95. package/dist/utils/duration.js +89 -0
  96. package/dist/utils/shared-flags.d.ts +1 -0
  97. package/dist/utils/shared-flags.js +11 -2
  98. package/package.json +4 -2
@@ -0,0 +1,142 @@
1
+ /**
2
+ * AI Review Scanner
3
+ *
4
+ * Spawns a QA agent to perform structured code review on story implementations.
5
+ * Produces RawReviewOutput with source 'ai' for downstream SeverityClassifier parsing.
6
+ *
7
+ * Prompt enforces strict output format: SEVERITY/FILE/LINE/ISSUE/FIX blocks
8
+ * separated by `---`, or `REVIEW_PASS: No issues found` sentinel for clean reviews.
9
+ */
10
+ /** Default AI review timeout: 5 minutes (NFR1) */
11
+ const DEFAULT_AI_REVIEW_TIMEOUT = 300_000;
12
+ /**
13
+ * Simple glob matcher for path rules.
14
+ * Supports `**` (any path segments) and `*` (single segment wildcard).
15
+ */
16
+ function matchGlob(file, pattern) {
17
+ // Escape regex special chars, then convert glob wildcards
18
+ const regexStr = pattern
19
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex chars (not * and ?)
20
+ .replace(/\*\*/g, '{{GLOBSTAR}}') // placeholder for **
21
+ .replace(/\*/g, '[^/]*') // * matches within a segment
22
+ .replace(/\?/g, '[^/]') // ? matches single char
23
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** matches across segments
24
+ return new RegExp(`^${regexStr}$`).test(file);
25
+ }
26
+ /**
27
+ * AIReviewScanner — spawns a QA agent and parses structured review output.
28
+ *
29
+ * Implements ReviewScanner interface. Uses composition over inheritance —
30
+ * composes AIProviderRunner, doesn't extend it.
31
+ */
32
+ export class AIReviewScanner {
33
+ agentRunner;
34
+ config;
35
+ logger;
36
+ timeout;
37
+ constructor(agentRunner, config, logger, timeout) {
38
+ this.agentRunner = agentRunner;
39
+ this.config = config;
40
+ this.logger = logger;
41
+ this.timeout = timeout ?? DEFAULT_AI_REVIEW_TIMEOUT;
42
+ }
43
+ /**
44
+ * Scan the given context by spawning a QA agent for AI-powered code review
45
+ *
46
+ * @param context - Review context describing what to scan
47
+ * @returns Raw output from the AI agent with source 'ai'
48
+ */
49
+ async scan(context) {
50
+ this.logger.info({ changedFiles: context.changedFiles, storyId: context.storyId }, 'Starting AI review scan');
51
+ const prompt = this.buildReviewPrompt(context);
52
+ const references = [context.storyFile, ...context.changedFiles, ...context.referenceFiles];
53
+ try {
54
+ const result = await this.agentRunner.runAgent(prompt, {
55
+ agentType: 'qa',
56
+ references,
57
+ timeout: this.timeout,
58
+ });
59
+ this.logger.info({ duration: result.duration, exitCode: result.exitCode, storyId: context.storyId, success: result.success }, 'AI review scan completed');
60
+ return this.parseAgentOutput(result);
61
+ }
62
+ catch (error) {
63
+ const err = error;
64
+ this.logger.error({ error: err.message, storyId: context.storyId }, 'AI review scan failed');
65
+ return {
66
+ raw: `AI_REVIEW_ERROR: ${err.message}`,
67
+ source: 'ai',
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Build the review prompt with severity definitions, output format spec,
73
+ * changed files, and path-specific rules.
74
+ */
75
+ buildReviewPrompt(context) {
76
+ const sections = [];
77
+ // Preamble
78
+ sections.push('You are a senior code reviewer performing a structured quality review.', 'Review the following changed files for the given story and report any issues found.', '');
79
+ // Severity definitions (Architecture Section 5.1)
80
+ sections.push('## Severity Definitions', '', 'CRITICAL — Security vulnerabilities, data loss risks, authentication/authorization bypasses, injection flaws. Maps to NON-NEGOTIABLE governance tier.', 'HIGH — Logic errors, missing error handling, broken functionality, race conditions, missing input validation. Maps to MUST governance tier.', 'MEDIUM — Code style violations, missing tests, poor naming, missing documentation, unnecessary complexity. Maps to SHOULD governance tier (tech debt).', 'LOW — Minor suggestions, optional improvements, cosmetic issues, nitpicks. Maps to MAY governance tier (noted only).', '');
81
+ // Output format spec
82
+ sections.push('## Output Format', '', 'For each issue found, output a block with these fields:', '', 'SEVERITY: <CRITICAL|HIGH|MEDIUM|LOW>', 'FILE: <relative file path>', 'LINE: <line number>', 'ISSUE: <description of the issue>', 'FIX: <suggested fix>', '', 'Separate multiple issue blocks with a line containing only `---`.', '', 'If no issues are found, output exactly:', 'REVIEW_PASS: No issues found', '', 'Do NOT include any other text, explanations, or markdown formatting outside of these blocks.', '');
83
+ // Changed files
84
+ sections.push('## Changed Files', '');
85
+ for (const file of context.changedFiles) {
86
+ sections.push(`- ${file}`);
87
+ }
88
+ sections.push('');
89
+ // Story context
90
+ sections.push(`## Story`, '', `Story file: ${context.storyFile}`, `Story ID: ${context.storyId}`, '');
91
+ // Path-specific rules
92
+ const matchingRules = this.getMatchingPathRules(context.changedFiles);
93
+ if (matchingRules.length > 0) {
94
+ sections.push('## Path-Specific Review Focus', '');
95
+ for (const rule of matchingRules) {
96
+ sections.push(`Files matching \`${rule.pattern}\`:`);
97
+ for (const focus of rule.focus) {
98
+ sections.push(` - ${focus}`);
99
+ }
100
+ sections.push('');
101
+ }
102
+ }
103
+ return sections.join('\n');
104
+ }
105
+ /**
106
+ * Get path rules that match any of the changed files
107
+ */
108
+ getMatchingPathRules(changedFiles) {
109
+ const pathRules = this.config.pathRules ?? [];
110
+ if (pathRules.length === 0 || changedFiles.length === 0) {
111
+ return [];
112
+ }
113
+ return pathRules.filter((rule) => changedFiles.some((file) => matchGlob(file, rule.pattern)));
114
+ }
115
+ /**
116
+ * Parse agent output into RawReviewOutput
117
+ *
118
+ * Handles three cases:
119
+ * 1. REVIEW_PASS sentinel → clean result
120
+ * 2. Structured output → pass raw text for SeverityClassifier
121
+ * 3. Malformed/empty output → graceful fallback
122
+ */
123
+ parseAgentOutput(result) {
124
+ const output = result.output.trim();
125
+ // Empty or failed output
126
+ if (!output || !result.success) {
127
+ const raw = output || result.errors || 'AI_REVIEW_ERROR: No output from agent';
128
+ this.logger.warn({ exitCode: result.exitCode, hasOutput: !!output }, 'AI review produced no usable output');
129
+ return { raw, source: 'ai' };
130
+ }
131
+ // REVIEW_PASS sentinel
132
+ if (output.includes('REVIEW_PASS:')) {
133
+ this.logger.info('AI review passed with no issues found');
134
+ return {
135
+ raw: 'REVIEW_PASS: No issues found',
136
+ source: 'ai',
137
+ };
138
+ }
139
+ // Structured output — pass through for SeverityClassifier
140
+ return { raw: output, source: 'ai' };
141
+ }
142
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * CodeRabbit Scanner (Stub)
3
+ *
4
+ * Placeholder for future CodeRabbit integration (Epic 2/3).
5
+ * Implements ReviewScanner interface to allow scanner-factory to
6
+ * compile and dynamically import this module.
7
+ */
8
+ import type pino from 'pino';
9
+ import type { RawReviewOutput, ReviewContext, ReviewScanner } from './types.js';
10
+ /**
11
+ * CodeRabbitScanner — runs CodeRabbit CLI for AI-powered code review.
12
+ *
13
+ * Stub implementation — will be fully implemented in a future story.
14
+ */
15
+ export declare class CodeRabbitScanner implements ReviewScanner {
16
+ private readonly logger;
17
+ constructor(logger: pino.Logger);
18
+ /**
19
+ * Scan the given context using CodeRabbit CLI
20
+ *
21
+ * @param _context - Review context describing what to scan
22
+ * @returns Raw output from CodeRabbit
23
+ */
24
+ scan(_context: ReviewContext): Promise<RawReviewOutput>;
25
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CodeRabbit Scanner (Stub)
3
+ *
4
+ * Placeholder for future CodeRabbit integration (Epic 2/3).
5
+ * Implements ReviewScanner interface to allow scanner-factory to
6
+ * compile and dynamically import this module.
7
+ */
8
+ /**
9
+ * CodeRabbitScanner — runs CodeRabbit CLI for AI-powered code review.
10
+ *
11
+ * Stub implementation — will be fully implemented in a future story.
12
+ */
13
+ export class CodeRabbitScanner {
14
+ logger;
15
+ constructor(logger) {
16
+ this.logger = logger;
17
+ }
18
+ /**
19
+ * Scan the given context using CodeRabbit CLI
20
+ *
21
+ * @param _context - Review context describing what to scan
22
+ * @returns Raw output from CodeRabbit
23
+ */
24
+ async scan(_context) {
25
+ this.logger.warn('CodeRabbitScanner is a stub — not yet implemented');
26
+ return {
27
+ raw: 'CODERABBIT_NOT_IMPLEMENTED: This scanner is a placeholder for future implementation',
28
+ source: 'coderabbit',
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Review Services
3
+ *
4
+ * Type definitions, interfaces, and classification for the automated code review system.
5
+ */
6
+ export { AIReviewScanner } from './ai-review-scanner.js';
7
+ export type { PathRule, ReviewConfig } from './ai-review-scanner.js';
8
+ export { CodeRabbitScanner } from './coderabbit-scanner.js';
9
+ export { LintScanner } from './lint-scanner.js';
10
+ export { DefaultReviewPhaseExecutor } from './review-phase-executor.js';
11
+ export type { ReviewPhaseExecutor } from './review-phase-executor.js';
12
+ export { ReviewQueue } from './review-queue.js';
13
+ export { createScanners } from './scanner-factory.js';
14
+ export type { ScannerTimeouts } from './scanner-factory.js';
15
+ export { SelfHealLoop } from './self-heal-loop.js';
16
+ export type { ClassifyFn, SelfHealConfig } from './self-heal-loop.js';
17
+ export { classify } from './severity-classifier.js';
18
+ export { TechDebtTracker } from './tech-debt-tracker.js';
19
+ export { Severity } from './types.js';
20
+ export type { ClassifiedIssue, RawReviewOutput, ReviewContext, ReviewResult, ReviewScanner, ReviewVerdict, } from './types.js';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Review Services
3
+ *
4
+ * Type definitions, interfaces, and classification for the automated code review system.
5
+ */
6
+ export { AIReviewScanner } from './ai-review-scanner.js';
7
+ export { CodeRabbitScanner } from './coderabbit-scanner.js';
8
+ export { LintScanner } from './lint-scanner.js';
9
+ export { DefaultReviewPhaseExecutor } from './review-phase-executor.js';
10
+ export { ReviewQueue } from './review-queue.js';
11
+ export { createScanners } from './scanner-factory.js';
12
+ export { SelfHealLoop } from './self-heal-loop.js';
13
+ export { classify } from './severity-classifier.js';
14
+ export { TechDebtTracker } from './tech-debt-tracker.js';
15
+ export { Severity } from './types.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * LintScanner Service
3
+ *
4
+ * Runs ESLint and tsc on changed files and returns structured raw output.
5
+ * Always available in every review pass — provides deterministic, tool-based
6
+ * code quality findings regardless of AI scanner availability.
7
+ *
8
+ * Returns RawReviewOutput; classification into ClassifiedIssue[] happens
9
+ * separately in SeverityClassifier (Story 1.2).
10
+ */
11
+ import type pino from 'pino';
12
+ import type { RawReviewOutput, ReviewContext, ReviewScanner } from './types.js';
13
+ /**
14
+ * LintScanner — runs ESLint and tsc and returns raw output.
15
+ *
16
+ * Implements ReviewScanner interface. Both tools run in parallel via
17
+ * Promise.all so wall-clock time is max(eslint, tsc), not the sum.
18
+ */
19
+ export declare class LintScanner implements ReviewScanner {
20
+ private readonly execOptions;
21
+ private readonly logger;
22
+ constructor(logger: pino.Logger, timeout?: number);
23
+ /**
24
+ * Scan the given context with ESLint and tsc
25
+ *
26
+ * @param context - Review context describing what to scan
27
+ * @returns Raw output from lint tools
28
+ */
29
+ scan(context: ReviewContext): Promise<RawReviewOutput>;
30
+ /**
31
+ * Run ESLint with --format json on the changed files
32
+ *
33
+ * @returns ESLint JSON output string, or null if skipped/timed out
34
+ */
35
+ private runEslint;
36
+ /**
37
+ * Run tsc with --noEmit --pretty false on the project
38
+ *
39
+ * @returns tsc error output string, or null if skipped/timed out
40
+ */
41
+ private runTsc;
42
+ /**
43
+ * Check if any of the candidate files exist in the given directory
44
+ */
45
+ private hasAnyFile;
46
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * LintScanner Service
3
+ *
4
+ * Runs ESLint and tsc on changed files and returns structured raw output.
5
+ * Always available in every review pass — provides deterministic, tool-based
6
+ * code quality findings regardless of AI scanner availability.
7
+ *
8
+ * Returns RawReviewOutput; classification into ClassifiedIssue[] happens
9
+ * separately in SeverityClassifier (Story 1.2).
10
+ */
11
+ import { exec } from 'node:child_process';
12
+ import { access } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { promisify } from 'node:util';
15
+ const execAsync = promisify(exec);
16
+ /** ESLint config file candidates (v8 legacy + v9 flat config) */
17
+ const ESLINT_CONFIG_FILES = [
18
+ '.eslintrc',
19
+ '.eslintrc.js',
20
+ '.eslintrc.cjs',
21
+ '.eslintrc.json',
22
+ '.eslintrc.yml',
23
+ '.eslintrc.yaml',
24
+ 'eslint.config.js',
25
+ 'eslint.config.mjs',
26
+ 'eslint.config.cjs',
27
+ ];
28
+ /** Default lint timeout: 30 seconds (NFR2) */
29
+ const DEFAULT_LINT_TIMEOUT = 30_000;
30
+ /**
31
+ * LintScanner — runs ESLint and tsc and returns raw output.
32
+ *
33
+ * Implements ReviewScanner interface. Both tools run in parallel via
34
+ * Promise.all so wall-clock time is max(eslint, tsc), not the sum.
35
+ */
36
+ export class LintScanner {
37
+ execOptions;
38
+ logger;
39
+ constructor(logger, timeout) {
40
+ this.logger = logger;
41
+ this.execOptions = {
42
+ maxBuffer: 10 * 1024 * 1024, // 10MB
43
+ timeout: timeout ?? DEFAULT_LINT_TIMEOUT,
44
+ };
45
+ }
46
+ /**
47
+ * Scan the given context with ESLint and tsc
48
+ *
49
+ * @param context - Review context describing what to scan
50
+ * @returns Raw output from lint tools
51
+ */
52
+ async scan(context) {
53
+ const { changedFiles, projectRoot } = context;
54
+ const [eslintOutput, tscOutput] = await Promise.all([
55
+ this.runEslint(changedFiles, projectRoot),
56
+ this.runTsc(projectRoot),
57
+ ]);
58
+ // Both tools unavailable
59
+ if (eslintOutput === null && tscOutput === null) {
60
+ return {
61
+ raw: 'NO_LINT_TOOLS_AVAILABLE: Neither ESLint nor tsc are configured in this project',
62
+ source: 'lint',
63
+ };
64
+ }
65
+ // Only ESLint
66
+ if (eslintOutput !== null && tscOutput === null) {
67
+ return { raw: eslintOutput, source: 'lint-eslint' };
68
+ }
69
+ // Only tsc
70
+ if (eslintOutput === null && tscOutput !== null) {
71
+ return { raw: tscOutput, source: 'lint-tsc' };
72
+ }
73
+ // Both
74
+ return { raw: eslintOutput + '\n' + tscOutput, source: 'lint' };
75
+ }
76
+ /**
77
+ * Run ESLint with --format json on the changed files
78
+ *
79
+ * @returns ESLint JSON output string, or null if skipped/timed out
80
+ */
81
+ async runEslint(changedFiles, projectRoot) {
82
+ if (changedFiles.length === 0) {
83
+ this.logger.info('ESLint: no changed files to lint, skipping');
84
+ return null;
85
+ }
86
+ // Check for any ESLint config file
87
+ const hasConfig = await this.hasAnyFile(projectRoot, ESLINT_CONFIG_FILES);
88
+ if (!hasConfig) {
89
+ this.logger.info('ESLint: no configuration file found in project root, skipping');
90
+ return null;
91
+ }
92
+ const fileArgs = changedFiles.join(' ');
93
+ const command = `npx eslint --format json ${fileArgs}`;
94
+ try {
95
+ const { stdout } = await execAsync(command, {
96
+ ...this.execOptions,
97
+ cwd: projectRoot,
98
+ shell: process.env.SHELL || '/bin/bash',
99
+ });
100
+ return stdout;
101
+ }
102
+ catch (error) {
103
+ const execError = error;
104
+ // Timeout
105
+ if (execError.killed === true && execError.signal === 'SIGTERM') {
106
+ this.logger.warn({ timeout: this.execOptions.timeout }, 'ESLint: timed out, skipping');
107
+ return null;
108
+ }
109
+ // ESLint exits 1 when lint errors are found — stdout still has valid JSON
110
+ if (execError.stdout) {
111
+ return execError.stdout;
112
+ }
113
+ this.logger.warn({ error: error.message }, 'ESLint: execution failed');
114
+ return null;
115
+ }
116
+ }
117
+ /**
118
+ * Run tsc with --noEmit --pretty false on the project
119
+ *
120
+ * @returns tsc error output string, or null if skipped/timed out
121
+ */
122
+ async runTsc(projectRoot) {
123
+ const tsconfigPath = join(projectRoot, 'tsconfig.json');
124
+ try {
125
+ await access(tsconfigPath);
126
+ }
127
+ catch {
128
+ this.logger.info('tsc: no tsconfig.json found in project root, skipping');
129
+ return null;
130
+ }
131
+ const command = 'npx tsc --noEmit --pretty false';
132
+ try {
133
+ const { stdout, stderr } = await execAsync(command, {
134
+ ...this.execOptions,
135
+ cwd: projectRoot,
136
+ shell: process.env.SHELL || '/bin/bash',
137
+ });
138
+ const output = (stdout + '\n' + stderr).trim();
139
+ return output || null;
140
+ }
141
+ catch (error) {
142
+ const execError = error;
143
+ // Timeout
144
+ if (execError.killed === true && execError.signal === 'SIGTERM') {
145
+ this.logger.warn({ timeout: this.execOptions.timeout }, 'tsc: timed out, skipping');
146
+ return null;
147
+ }
148
+ // tsc exits 2 when type errors are found — stdout/stderr still has valid output
149
+ const output = ((execError.stdout || '') + '\n' + (execError.stderr || '')).trim();
150
+ if (output) {
151
+ return output;
152
+ }
153
+ this.logger.warn({ error: error.message }, 'tsc: execution failed');
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Check if any of the candidate files exist in the given directory
159
+ */
160
+ async hasAnyFile(directory, candidates) {
161
+ for (const file of candidates) {
162
+ try {
163
+ await access(join(directory, file));
164
+ return true;
165
+ }
166
+ catch {
167
+ // File doesn't exist, try next
168
+ }
169
+ }
170
+ return false;
171
+ }
172
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Review Configuration Builder
3
+ *
4
+ * Merges review configuration from core-config.yaml with CLI flag overrides.
5
+ * CLI flags always take precedence over config file values. When neither is
6
+ * provided, hardcoded defaults are used.
7
+ */
8
+ import type { ReviewConfig } from '../validation/config-validator.js';
9
+ import { Severity } from './types.js';
10
+ /**
11
+ * CLI flag values that can override config file settings.
12
+ * All fields are optional — only provided flags override config.
13
+ */
14
+ export interface ReviewCliFlags {
15
+ /** Comma-separated severity levels that block the pipeline */
16
+ blockOn?: string;
17
+ /** Maximum self-heal iterations */
18
+ maxFix?: number;
19
+ /** Comma-separated scanner IDs */
20
+ scanners?: string;
21
+ }
22
+ /**
23
+ * Merged review configuration used at runtime
24
+ */
25
+ export interface MergedReviewConfig {
26
+ enabled: boolean;
27
+ pathRules: Array<{
28
+ focus: string;
29
+ pattern: string;
30
+ }>;
31
+ scanners: string[];
32
+ selfHeal: {
33
+ fixAgent: string;
34
+ fixTimeout: number;
35
+ maxIterations: number;
36
+ };
37
+ severity: {
38
+ blockOn: string[];
39
+ documentOn: string[];
40
+ ignoreOn: string[];
41
+ };
42
+ }
43
+ /**
44
+ * Build merged review configuration from config file defaults and CLI flag overrides.
45
+ *
46
+ * Merge precedence: CLI flags > config file > hardcoded defaults.
47
+ *
48
+ * @param coreConfig - Review section from core-config.yaml (may be undefined)
49
+ * @param cliFlags - CLI flag values (may be undefined or partially provided)
50
+ * @returns Fully resolved review configuration
51
+ */
52
+ export declare function buildReviewConfig(coreConfig?: ReviewConfig, cliFlags?: ReviewCliFlags): MergedReviewConfig;
53
+ /**
54
+ * Convert merged severity.blockOn array to a single Severity threshold.
55
+ *
56
+ * Returns the lowest (most permissive) severity from the blockOn list,
57
+ * matching the pattern used by the review phase executor.
58
+ *
59
+ * @param blockOn - Array of severity level strings
60
+ * @returns Single Severity enum value (lowest rank in the list)
61
+ */
62
+ export declare function blockOnToSeverity(blockOn: string[]): Severity;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Review Configuration Builder
3
+ *
4
+ * Merges review configuration from core-config.yaml with CLI flag overrides.
5
+ * CLI flags always take precedence over config file values. When neither is
6
+ * provided, hardcoded defaults are used.
7
+ */
8
+ import { Severity } from './types.js';
9
+ /**
10
+ * Hardcoded defaults used when neither config file nor CLI flags provide a value
11
+ */
12
+ const DEFAULTS = {
13
+ enabled: false,
14
+ pathRules: [],
15
+ scanners: ['ai', 'lint'],
16
+ selfHeal: {
17
+ fixAgent: 'dev',
18
+ fixTimeout: 300_000,
19
+ maxIterations: 3,
20
+ },
21
+ severity: {
22
+ blockOn: ['CRITICAL', 'HIGH'],
23
+ documentOn: ['MEDIUM'],
24
+ ignoreOn: ['LOW'],
25
+ },
26
+ };
27
+ /**
28
+ * Build merged review configuration from config file defaults and CLI flag overrides.
29
+ *
30
+ * Merge precedence: CLI flags > config file > hardcoded defaults.
31
+ *
32
+ * @param coreConfig - Review section from core-config.yaml (may be undefined)
33
+ * @param cliFlags - CLI flag values (may be undefined or partially provided)
34
+ * @returns Fully resolved review configuration
35
+ */
36
+ export function buildReviewConfig(coreConfig, cliFlags) {
37
+ // Start with defaults
38
+ const base = {
39
+ enabled: coreConfig?.enabled ?? DEFAULTS.enabled,
40
+ pathRules: coreConfig?.pathRules ?? DEFAULTS.pathRules,
41
+ scanners: coreConfig?.scanners ?? DEFAULTS.scanners,
42
+ selfHeal: {
43
+ fixAgent: coreConfig?.selfHeal?.fixAgent ?? DEFAULTS.selfHeal.fixAgent,
44
+ fixTimeout: coreConfig?.selfHeal?.fixTimeout ?? DEFAULTS.selfHeal.fixTimeout,
45
+ maxIterations: coreConfig?.selfHeal?.maxIterations ?? DEFAULTS.selfHeal.maxIterations,
46
+ },
47
+ severity: {
48
+ blockOn: coreConfig?.severity?.blockOn ?? DEFAULTS.severity.blockOn,
49
+ documentOn: coreConfig?.severity?.documentOn ?? DEFAULTS.severity.documentOn,
50
+ ignoreOn: coreConfig?.severity?.ignoreOn ?? DEFAULTS.severity.ignoreOn,
51
+ },
52
+ };
53
+ // Apply CLI flag overrides
54
+ if (cliFlags?.scanners) {
55
+ base.scanners = cliFlags.scanners.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
56
+ }
57
+ if (cliFlags?.blockOn) {
58
+ base.severity.blockOn = cliFlags.blockOn.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean);
59
+ }
60
+ if (cliFlags?.maxFix !== undefined) {
61
+ base.selfHeal.maxIterations = cliFlags.maxFix;
62
+ }
63
+ return base;
64
+ }
65
+ /**
66
+ * Convert merged severity.blockOn array to a single Severity threshold.
67
+ *
68
+ * Returns the lowest (most permissive) severity from the blockOn list,
69
+ * matching the pattern used by the review phase executor.
70
+ *
71
+ * @param blockOn - Array of severity level strings
72
+ * @returns Single Severity enum value (lowest rank in the list)
73
+ */
74
+ export function blockOnToSeverity(blockOn) {
75
+ const SEVERITY_RANK = {
76
+ CRITICAL: 4,
77
+ HIGH: 3,
78
+ LOW: 1,
79
+ MEDIUM: 2,
80
+ };
81
+ let lowestRank = Infinity;
82
+ let lowestSeverity = Severity.HIGH;
83
+ for (const sev of blockOn) {
84
+ const rank = SEVERITY_RANK[sev] ?? 0;
85
+ if (rank < lowestRank) {
86
+ lowestRank = rank;
87
+ lowestSeverity = sev;
88
+ }
89
+ }
90
+ return lowestSeverity;
91
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Review Phase Executor
3
+ *
4
+ * Orchestrates automated code review on completed stories. Runs scanners
5
+ * via SelfHealLoop, classifies issues, tracks tech debt, and produces
6
+ * PASS/FAIL verdicts per story.
7
+ *
8
+ * The interface is consumed by WorkflowOrchestrator for both sequential
9
+ * and pipelined review execution paths. The concrete implementation
10
+ * (DefaultReviewPhaseExecutor) composes SelfHealLoop and TechDebtTracker.
11
+ */
12
+ import type pino from 'pino';
13
+ import type { Story } from '../../models/story.js';
14
+ import type { WorkflowConfig } from '../../models/workflow-config.js';
15
+ import type { ReviewResult } from './types.js';
16
+ import type { SelfHealLoop } from './self-heal-loop.js';
17
+ import type { TechDebtTracker } from './tech-debt-tracker.js';
18
+ import type { ReviewReporter } from './review-reporter.js';
19
+ /**
20
+ * Interface for executing automated code review on stories
21
+ */
22
+ export interface ReviewPhaseExecutor {
23
+ /**
24
+ * Review a single story
25
+ *
26
+ * @param story - Story to review
27
+ * @param config - Workflow configuration
28
+ * @returns ReviewResult with verdict and issues
29
+ */
30
+ reviewStory(story: Story, config: WorkflowConfig): Promise<ReviewResult>;
31
+ }
32
+ /**
33
+ * Concrete implementation of ReviewPhaseExecutor.
34
+ *
35
+ * Composes SelfHealLoop (scan + classify + fix) and TechDebtTracker
36
+ * (MEDIUM issues → story file / session backlog). Accepts dependencies
37
+ * via constructor injection for testability.
38
+ */
39
+ export declare class DefaultReviewPhaseExecutor implements ReviewPhaseExecutor {
40
+ private readonly logger;
41
+ private readonly reporter?;
42
+ private readonly selfHealLoop;
43
+ private readonly techDebtTracker;
44
+ constructor(selfHealLoop: SelfHealLoop, techDebtTracker: TechDebtTracker, logger: pino.Logger, reporter?: ReviewReporter);
45
+ /**
46
+ * Review a single story using the self-heal loop.
47
+ *
48
+ * @param story - Story to review
49
+ * @param config - Workflow configuration
50
+ * @returns ReviewResult with verdict and issues
51
+ */
52
+ reviewStory(story: Story, config: WorkflowConfig): Promise<ReviewResult>;
53
+ /**
54
+ * Review all stories and return a map of storyId → ReviewResult.
55
+ *
56
+ * Only stories with verdict PASS are considered passing. FAIL stories
57
+ * are logged with a warning including failure reasons.
58
+ *
59
+ * @param stories - Story file paths to review
60
+ * @param config - Workflow configuration
61
+ * @returns Map of story identifier to ReviewResult
62
+ */
63
+ reviewAll(stories: string[], config: WorkflowConfig): Promise<Map<string, ReviewResult>>;
64
+ /**
65
+ * Extract a story identifier from a file path.
66
+ * e.g. "docs/stories/PROJ-story-1.001.md" → "PROJ-story-1.001"
67
+ */
68
+ private extractStoryId;
69
+ }