@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
@@ -1,6 +1,9 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { basename } from 'node:path';
3
+ import { parseDuration } from '../utils/duration.js';
3
4
  import { createAgentRunner, isProviderSupported } from '../services/agents/agent-runner-factory.js';
5
+ import { getRegisteredScannerNames } from '../services/review/scanner-factory.js';
6
+ import { Severity } from '../services/review/types.js';
4
7
  import { FileManager } from '../services/file-system/file-manager.js';
5
8
  import { PathResolver } from '../services/file-system/path-resolver.js';
6
9
  import { BatchProcessor } from '../services/orchestration/batch-processor.js';
@@ -90,6 +93,37 @@ export default class Workflow extends Command {
90
93
  description: 'AI provider to use (claude, gemini, or opencode)',
91
94
  options: ['claude', 'gemini', 'opencode'],
92
95
  }),
96
+ mcp: Flags.boolean({
97
+ default: false,
98
+ description: 'Enable MCP tool injection for pipeline phases via Docker gateway',
99
+ helpGroup: 'MCP',
100
+ }),
101
+ 'mcp-phases': Flags.string({
102
+ description: 'Comma-separated list of phases to inject MCP tools into (valid: epic,story,dev,review,qa)',
103
+ helpGroup: 'MCP',
104
+ }),
105
+ 'mcp-preset': Flags.string({
106
+ description: 'MCP preset name to use for tool configuration (overrides config file)',
107
+ helpGroup: 'MCP',
108
+ }),
109
+ review: Flags.boolean({
110
+ default: false,
111
+ description: 'Enable automated code review phase between dev and QA',
112
+ helpGroup: 'Review Workflow',
113
+ }),
114
+ 'review-block-on': Flags.string({
115
+ description: 'Minimum severity to block story from QA (critical|high|medium|low)',
116
+ helpGroup: 'Review Workflow',
117
+ }),
118
+ 'review-max-fix': Flags.integer({
119
+ description: 'Maximum self-heal fix iterations (default: from config or 3)',
120
+ helpGroup: 'Review Workflow',
121
+ }),
122
+ 'review-scanners': Flags.string({
123
+ description: 'Scanner names to run (e.g., lint, ai, coderabbit). Repeatable.',
124
+ helpGroup: 'Review Workflow',
125
+ multiple: true,
126
+ }),
93
127
  qa: Flags.boolean({
94
128
  default: false,
95
129
  description: 'Run QA workflow after development completes',
@@ -124,9 +158,17 @@ export default class Workflow extends Command {
124
158
  default: 60,
125
159
  description: 'Seconds between story development',
126
160
  }),
127
- timeout: Flags.integer({
161
+ timeout: Flags.custom({
162
+ parse: async (input) => parseDuration(input),
163
+ })({
128
164
  default: 2_700_000,
129
- description: 'Agent execution timeout in milliseconds (default: 2700000 = 45 minutes)',
165
+ description: 'Agent execution timeout accepts durations like 30s, 5m, 1h, 90m, or raw milliseconds (default: 45m)',
166
+ }),
167
+ 'review-timeout': Flags.custom({
168
+ parse: async (input) => parseDuration(input),
169
+ })({
170
+ description: 'AI review scanner timeout — overrides --timeout for review phase only (default: 5m)',
171
+ helpGroup: 'Review Workflow',
130
172
  }),
131
173
  'max-retries': Flags.integer({
132
174
  default: 0,
@@ -178,6 +220,35 @@ export default class Workflow extends Command {
178
220
  if (flags['dry-run']) {
179
221
  this.displayDryRunBanner();
180
222
  }
223
+ // Validate review flags when --review is enabled
224
+ if (flags.review) {
225
+ // Validate --review-scanners against registered scanner names
226
+ if (flags['review-scanners'] && flags['review-scanners'].length > 0) {
227
+ const validScanners = getRegisteredScannerNames();
228
+ const invalidScanners = flags['review-scanners'].filter((s) => !validScanners.includes(s));
229
+ if (invalidScanners.length > 0) {
230
+ this.error(`Unknown --review-scanners value(s): ${invalidScanners.join(', ')}. Valid scanners are: ${validScanners.join(', ')}`, { exit: 1 });
231
+ }
232
+ }
233
+ // Validate --review-max-fix is non-negative
234
+ if (flags['review-max-fix'] !== undefined && flags['review-max-fix'] < 0) {
235
+ this.error(`--review-max-fix must be a non-negative integer, got: ${flags['review-max-fix']}`, { exit: 1 });
236
+ }
237
+ // Validate --review-block-on against severity enum
238
+ if (flags['review-block-on'] !== undefined) {
239
+ const validSeverities = ['critical', 'high', 'medium', 'low'];
240
+ if (!validSeverities.includes(flags['review-block-on'])) {
241
+ this.error(`Invalid --review-block-on value: ${flags['review-block-on']}. Valid values are: ${validSeverities.join(', ')}`, { exit: 1 });
242
+ }
243
+ }
244
+ }
245
+ // Map --review-block-on string to Severity enum
246
+ const severityMap = {
247
+ critical: Severity.CRITICAL,
248
+ high: Severity.HIGH,
249
+ low: Severity.LOW,
250
+ medium: Severity.MEDIUM,
251
+ };
181
252
  // Build workflow configuration
182
253
  const config = {
183
254
  autoFix: flags['auto-fix'],
@@ -186,6 +257,9 @@ export default class Workflow extends Command {
186
257
  epicInterval: flags['epic-interval'],
187
258
  input: args.input,
188
259
  maxRetries: flags['max-retries'],
260
+ mcp: flags.mcp || undefined,
261
+ mcpPhases: flags['mcp-phases'] ? flags['mcp-phases'].split(',').map((s) => s.trim()) : undefined,
262
+ mcpPreset: flags['mcp-preset'],
189
263
  model: flags.model,
190
264
  parallel: flags.parallel,
191
265
  pipeline: flags.pipeline,
@@ -197,6 +271,11 @@ export default class Workflow extends Command {
197
271
  qaRetries: flags['qa-retries'],
198
272
  references: flags.reference || [],
199
273
  retryBackoffMs: flags['retry-backoff'],
274
+ review: flags.review,
275
+ reviewBlockOn: flags['review-block-on'] ? severityMap[flags['review-block-on']] : undefined,
276
+ reviewMaxFix: flags['review-max-fix'],
277
+ reviewScanners: flags['review-scanners'],
278
+ reviewTimeout: flags['review-timeout'],
200
279
  skipDev: flags['skip-dev'],
201
280
  skipEpics: flags['skip-epics'],
202
281
  skipStories: flags['skip-stories'],
@@ -204,6 +283,35 @@ export default class Workflow extends Command {
204
283
  timeout: flags.timeout,
205
284
  verbose: flags.verbose,
206
285
  };
286
+ // Validate --mcp-phases when --mcp is enabled
287
+ if (config.mcp && config.mcpPhases) {
288
+ const validPhases = ['epic', 'story', 'dev', 'review', 'qa'];
289
+ const invalidPhases = config.mcpPhases.filter((p) => !validPhases.includes(p));
290
+ if (invalidPhases.length > 0) {
291
+ this.error(`Invalid --mcp-phases value(s): ${invalidPhases.join(', ')}. Valid phases are: ${validPhases.join(', ')}`, { exit: 1 });
292
+ }
293
+ }
294
+ // MCP gateway health pre-check (AC: #5, #6)
295
+ if (config.mcp) {
296
+ try {
297
+ const gatewayUrl = 'http://localhost:8080';
298
+ const healthUrl = `${gatewayUrl}/health`;
299
+ const response = await fetch(healthUrl, { signal: AbortSignal.timeout(5000) });
300
+ if (response.ok) {
301
+ if (flags.verbose) {
302
+ this.log(colors.info('MCP gateway is healthy'));
303
+ }
304
+ }
305
+ else {
306
+ this.log(colors.warning(`MCP gateway returned status ${response.status} — continuing without MCP`));
307
+ config.mcp = false;
308
+ }
309
+ }
310
+ catch {
311
+ this.log(colors.warning('MCP gateway is not reachable — continuing without MCP'));
312
+ config.mcp = false;
313
+ }
314
+ }
207
315
  // Log configuration if verbose
208
316
  if (flags.verbose) {
209
317
  this.logger.info({ config }, 'Workflow configuration');
@@ -0,0 +1,99 @@
1
+ /**
2
+ * MCP Gateway Type Definitions
3
+ *
4
+ * Shared interfaces for MCP gateway services (health checker, config manager, credential manager).
5
+ * These types define the contracts that Story 2.4 and Epic 1 services will implement.
6
+ */
7
+ /**
8
+ * Health status of a single component
9
+ */
10
+ export type HealthStatus = 'disabled' | 'healthy' | 'offline' | 'unhealthy';
11
+ /**
12
+ * Health status for an individual MCP server
13
+ */
14
+ export interface ServerHealthStatus {
15
+ error: null | string;
16
+ latency: null | number;
17
+ name: string;
18
+ status: HealthStatus;
19
+ useCase?: string;
20
+ }
21
+ /**
22
+ * Gateway-level health status
23
+ */
24
+ export interface GatewayHealthStatus {
25
+ latency: null | number;
26
+ status: HealthStatus;
27
+ url: string;
28
+ }
29
+ /**
30
+ * Complete health report for the MCP gateway and all servers
31
+ */
32
+ export interface HealthReport {
33
+ gateway: GatewayHealthStatus;
34
+ servers: ServerHealthStatus[];
35
+ }
36
+ /**
37
+ * Credential entry with masked value
38
+ */
39
+ export interface CredentialEntry {
40
+ configured: boolean;
41
+ key: string;
42
+ requiredByPreset: boolean;
43
+ }
44
+ /**
45
+ * MCP server configuration entry
46
+ */
47
+ export interface McpServerConfig {
48
+ apiKeyRequired?: boolean;
49
+ enabled: boolean;
50
+ name: string;
51
+ useCase?: string;
52
+ }
53
+ /**
54
+ * Server template as defined in server-templates/*.yaml
55
+ */
56
+ export interface ServerTemplate {
57
+ api_key_required: boolean;
58
+ args?: string[];
59
+ command?: string;
60
+ enabled: boolean;
61
+ env?: Record<string, string>;
62
+ health_check: {
63
+ command: string;
64
+ method: 'http' | 'tool_call';
65
+ timeout: number;
66
+ };
67
+ layer?: 'direct' | 'gateway';
68
+ name: string;
69
+ transport: 'sse' | 'stdio';
70
+ url?: string;
71
+ use_case: string;
72
+ }
73
+ /**
74
+ * Interface contract for McpHealthChecker (Story 2.4)
75
+ */
76
+ export interface IHealthChecker {
77
+ checkAll(): Promise<HealthReport>;
78
+ }
79
+ /**
80
+ * Interface contract for McpConfigManager (Epic 1)
81
+ */
82
+ export interface IConfigManager {
83
+ addServer(template: ServerTemplate): void;
84
+ applyPreset(presetName: string): void;
85
+ getEnabledServers(): McpServerConfig[];
86
+ getGatewayUrl(): string;
87
+ getPresetName(): string;
88
+ removeServer(name: string): boolean;
89
+ }
90
+ /**
91
+ * Interface contract for McpCredentialManager (Epic 1)
92
+ */
93
+ export interface ICredentialManager {
94
+ get(key: string): string | null;
95
+ list(): CredentialEntry[];
96
+ remove(key: string): boolean;
97
+ resolve(key: string): string | null;
98
+ set(key: string, value: string): void;
99
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP Gateway Type Definitions
3
+ *
4
+ * Shared interfaces for MCP gateway services (health checker, config manager, credential manager).
5
+ * These types define the contracts that Story 2.4 and Epic 1 services will implement.
6
+ */
7
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Docker Availability Detection Utilities
3
+ *
4
+ * Provides functions to check Docker availability, resolve the gateway
5
+ * compose file path, and format Docker errors into user-friendly messages.
6
+ */
7
+ /**
8
+ * Result of Docker availability check
9
+ */
10
+ export interface DockerAvailabilityResult {
11
+ available: boolean;
12
+ error?: string;
13
+ reason?: 'daemon-not-running' | 'not-installed';
14
+ }
15
+ /**
16
+ * Check if Docker is available and the daemon is running
17
+ *
18
+ * Executes `docker info` to verify both installation and daemon status.
19
+ *
20
+ * @returns DockerAvailabilityResult with availability status and error details
21
+ */
22
+ export declare function isDockerAvailable(): DockerAvailabilityResult;
23
+ /**
24
+ * Resolve the path to the Docker Compose gateway file
25
+ *
26
+ * The compose file is expected at src/mcp/infrastructure/docker-compose.gateway.yml
27
+ * relative to the CLI package root.
28
+ *
29
+ * @returns Absolute path to docker-compose.gateway.yml
30
+ */
31
+ export declare function getDockerComposeFilePath(): string;
32
+ /**
33
+ * Check if the Docker Compose gateway file exists
34
+ *
35
+ * @returns true if the compose file exists at the expected path
36
+ */
37
+ export declare function dockerComposeFileExists(): boolean;
38
+ /**
39
+ * Format a Docker error into a user-friendly message with troubleshooting hints
40
+ *
41
+ * @param result - Docker availability check result
42
+ * @returns Formatted error message string
43
+ */
44
+ export declare function formatDockerError(result: DockerAvailabilityResult): string;
45
+ /**
46
+ * Default gateway health endpoint URL
47
+ */
48
+ export declare const DEFAULT_GATEWAY_URL = "http://localhost:8080";
49
+ /**
50
+ * Default health check poll interval in milliseconds
51
+ */
52
+ export declare const HEALTH_CHECK_INTERVAL_MS = 2000;
53
+ /**
54
+ * Default health check timeout in milliseconds
55
+ */
56
+ export declare const HEALTH_CHECK_TIMEOUT_MS = 30000;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Docker Availability Detection Utilities
3
+ *
4
+ * Provides functions to check Docker availability, resolve the gateway
5
+ * compose file path, and format Docker errors into user-friendly messages.
6
+ */
7
+ import { execSync } from 'node:child_process';
8
+ import { existsSync } from 'node:fs';
9
+ import { dirname, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { createLogger } from '../../utils/logger.js';
12
+ const logger = createLogger({ namespace: 'mcp:utils:docker' });
13
+ /**
14
+ * Check if Docker is available and the daemon is running
15
+ *
16
+ * Executes `docker info` to verify both installation and daemon status.
17
+ *
18
+ * @returns DockerAvailabilityResult with availability status and error details
19
+ */
20
+ export function isDockerAvailable() {
21
+ try {
22
+ execSync('docker info', { stdio: 'pipe', timeout: 10_000 });
23
+ logger.debug('Docker is available');
24
+ return { available: true };
25
+ }
26
+ catch (error) {
27
+ const err = error;
28
+ const stderr = err.stderr ? err.stderr.toString() : err.message;
29
+ if (stderr.includes('command not found') || stderr.includes('not recognized') || stderr.includes('ENOENT')) {
30
+ logger.warn('Docker is not installed');
31
+ return {
32
+ available: false,
33
+ error: stderr,
34
+ reason: 'not-installed',
35
+ };
36
+ }
37
+ logger.warn('Docker daemon is not running');
38
+ return {
39
+ available: false,
40
+ error: stderr,
41
+ reason: 'daemon-not-running',
42
+ };
43
+ }
44
+ }
45
+ /**
46
+ * Resolve the path to the Docker Compose gateway file
47
+ *
48
+ * The compose file is expected at src/mcp/infrastructure/docker-compose.gateway.yml
49
+ * relative to the CLI package root.
50
+ *
51
+ * @returns Absolute path to docker-compose.gateway.yml
52
+ */
53
+ export function getDockerComposeFilePath() {
54
+ const currentDir = dirname(fileURLToPath(import.meta.url));
55
+ // From src/mcp/utils/ → src/mcp/infrastructure/
56
+ const composePath = resolve(currentDir, '..', 'infrastructure', 'docker-compose.gateway.yml');
57
+ logger.debug('Resolved Docker Compose file path: %s', composePath);
58
+ return composePath;
59
+ }
60
+ /**
61
+ * Check if the Docker Compose gateway file exists
62
+ *
63
+ * @returns true if the compose file exists at the expected path
64
+ */
65
+ export function dockerComposeFileExists() {
66
+ const composePath = getDockerComposeFilePath();
67
+ return existsSync(composePath);
68
+ }
69
+ /**
70
+ * Format a Docker error into a user-friendly message with troubleshooting hints
71
+ *
72
+ * @param result - Docker availability check result
73
+ * @returns Formatted error message string
74
+ */
75
+ export function formatDockerError(result) {
76
+ if (result.reason === 'not-installed') {
77
+ return [
78
+ 'Docker is not installed on this system.',
79
+ '',
80
+ 'To install Docker:',
81
+ ' macOS: brew install --cask docker',
82
+ ' Linux: https://docs.docker.com/engine/install/',
83
+ ' Windows: https://docs.docker.com/desktop/install/windows-install/',
84
+ ].join('\n');
85
+ }
86
+ if (result.reason === 'daemon-not-running') {
87
+ return [
88
+ 'Docker daemon is not running.',
89
+ '',
90
+ 'To start Docker:',
91
+ ' macOS/Windows: Open the Docker Desktop application',
92
+ ' Linux: sudo systemctl start docker',
93
+ ].join('\n');
94
+ }
95
+ return `Docker error: ${result.error || 'Unknown error'}`;
96
+ }
97
+ /**
98
+ * Default gateway health endpoint URL
99
+ */
100
+ export const DEFAULT_GATEWAY_URL = 'http://localhost:8080';
101
+ /**
102
+ * Default health check poll interval in milliseconds
103
+ */
104
+ export const HEALTH_CHECK_INTERVAL_MS = 2000;
105
+ /**
106
+ * Default health check timeout in milliseconds
107
+ */
108
+ export const HEALTH_CHECK_TIMEOUT_MS = 30_000;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Server Template Loader
3
+ *
4
+ * Loads and validates MCP server template YAML files from the
5
+ * src/mcp/server-templates/ directory.
6
+ */
7
+ import type { ServerTemplate } from '../types.js';
8
+ /**
9
+ * List all available server template names by scanning the server-templates directory
10
+ *
11
+ * @returns Array of template names (without .yaml extension)
12
+ */
13
+ export declare function listAvailableTemplates(): string[];
14
+ /**
15
+ * Load and parse a server template by name
16
+ *
17
+ * @param name - Server template name (e.g., 'context7', 'exa')
18
+ * @returns Parsed ServerTemplate object
19
+ * @throws Error if template not found, with available templates listed
20
+ */
21
+ export declare function loadServerTemplate(name: string): ServerTemplate;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Server Template Loader
3
+ *
4
+ * Loads and validates MCP server template YAML files from the
5
+ * src/mcp/server-templates/ directory.
6
+ */
7
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
8
+ import { dirname, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import yaml from 'js-yaml';
11
+ import { createLogger } from '../../utils/logger.js';
12
+ const logger = createLogger({ namespace: 'mcp:utils:template-loader' });
13
+ /**
14
+ * Resolve the absolute path to the server-templates directory
15
+ */
16
+ function getTemplatesDir() {
17
+ const currentDir = dirname(fileURLToPath(import.meta.url));
18
+ return resolve(currentDir, '..', 'server-templates');
19
+ }
20
+ /**
21
+ * List all available server template names by scanning the server-templates directory
22
+ *
23
+ * @returns Array of template names (without .yaml extension)
24
+ */
25
+ export function listAvailableTemplates() {
26
+ const templatesDir = getTemplatesDir();
27
+ logger.debug('Scanning templates directory: %s', templatesDir);
28
+ if (!existsSync(templatesDir)) {
29
+ logger.warn('Templates directory not found: %s', templatesDir);
30
+ return [];
31
+ }
32
+ const files = readdirSync(templatesDir);
33
+ return files
34
+ .filter((f) => f.endsWith('.yaml') && !f.startsWith('__'))
35
+ .map((f) => f.replace('.yaml', ''))
36
+ .sort();
37
+ }
38
+ /**
39
+ * Load and parse a server template by name
40
+ *
41
+ * @param name - Server template name (e.g., 'context7', 'exa')
42
+ * @returns Parsed ServerTemplate object
43
+ * @throws Error if template not found, with available templates listed
44
+ */
45
+ export function loadServerTemplate(name) {
46
+ const templatesDir = getTemplatesDir();
47
+ const templatePath = resolve(templatesDir, `${name}.yaml`);
48
+ logger.debug('Loading template: %s from %s', name, templatePath);
49
+ if (!existsSync(templatePath)) {
50
+ const available = listAvailableTemplates();
51
+ throw new Error(`Server template '${name}' not found. Available templates: ${available.join(', ')}`);
52
+ }
53
+ const content = readFileSync(templatePath, 'utf8');
54
+ const parsed = yaml.load(content);
55
+ if (!parsed || typeof parsed !== 'object' || !parsed.name) {
56
+ throw new Error(`Invalid server template format in '${name}.yaml': missing required 'name' field`);
57
+ }
58
+ logger.info('Loaded template: %s (transport=%s, api_key_required=%s)', parsed.name, parsed.transport, parsed.api_key_required);
59
+ return parsed;
60
+ }
@@ -2,7 +2,7 @@ import type { AgentResult } from './agent-result.js';
2
2
  /**
3
3
  * Available BMAD agent types
4
4
  */
5
- export type AgentType = 'analyst' | 'architect' | 'dev' | 'pm' | 'prd-fixer' | 'quick-flow-solo-dev' | 'sm' | 'tea' | 'tech-writer' | 'ux-designer';
5
+ export type AgentType = 'analyst' | 'architect' | 'dev' | 'pm' | 'prd-fixer' | 'qa' | 'quick-flow-solo-dev' | 'sm' | 'tea' | 'tech-writer' | 'ux-designer';
6
6
  /**
7
7
  * Agent options without prompt and callbacks (used internally)
8
8
  */
@@ -23,6 +23,10 @@ export interface AgentOptions {
23
23
  * The type of agent to execute
24
24
  */
25
25
  agentType: AgentType;
26
+ /**
27
+ * Working directory for the agent process
28
+ */
29
+ cwd?: string;
26
30
  /**
27
31
  * Additional CLI flags to pass to the Claude executable
28
32
  */
@@ -50,6 +54,11 @@ export interface AgentOptions {
50
54
  * File references to include in the agent execution
51
55
  */
52
56
  references?: string[];
57
+ /**
58
+ * System prompt to append via --append-system-prompt (Claude only).
59
+ * Used to enforce output format constraints (e.g., YAML-only output).
60
+ */
61
+ systemPrompt?: string;
53
62
  /**
54
63
  * Timeout in milliseconds (default: 1800000ms = 30 minutes)
55
64
  */
@@ -1,3 +1,4 @@
1
+ import type { Severity } from '../services/review/types.js';
1
2
  /**
2
3
  * Configuration for workflow execution across all phases
3
4
  *
@@ -77,6 +78,31 @@ export interface WorkflowConfig {
77
78
  * - OpenCode: anthropic/claude-3-5-sonnet, openai/gpt-4o
78
79
  */
79
80
  model?: string;
81
+ /**
82
+ * Enable MCP tool discovery in agent prompts
83
+ *
84
+ * When true, MCP context (available tools/instructions) is prepended to
85
+ * agent prompts before each spawn. Requires McpContextInjector to be
86
+ * configured in the orchestrator.
87
+ * @default false
88
+ */
89
+ mcp?: boolean;
90
+ /**
91
+ * Restrict MCP injection to specific workflow phases
92
+ *
93
+ * When set, only the listed phases receive MCP tool context.
94
+ * When omitted (and mcp=true), all phases receive MCP context.
95
+ * @example ['epic', 'dev'] — only epic and dev phases get MCP tools
96
+ */
97
+ mcpPhases?: string[];
98
+ /**
99
+ * MCP preset name for tool configuration
100
+ *
101
+ * Overrides the configured preset for the pipeline run.
102
+ * Presets define which MCP servers and tools are available.
103
+ * @example 'research'
104
+ */
105
+ mcpPreset?: string;
80
106
  /**
81
107
  * Filename prefix for generated files
82
108
  *
@@ -90,6 +116,37 @@ export interface WorkflowConfig {
90
116
  * @default 'claude'
91
117
  */
92
118
  provider?: 'claude' | 'gemini' | 'opencode';
119
+ /**
120
+ * Run automated code review between dev and QA phases
121
+ *
122
+ * When true, completed stories are reviewed before forwarding to QA.
123
+ * In pipeline mode, review runs concurrently via ReviewQueue.
124
+ * Only PASS stories proceed to QA; FAIL stories are excluded with a warning.
125
+ * @default false
126
+ */
127
+ review?: boolean;
128
+ /**
129
+ * Minimum severity that blocks the pipeline during review
130
+ *
131
+ * Issues at or above this severity after self-heal will cause a FAIL verdict.
132
+ * @default Severity.HIGH
133
+ */
134
+ reviewBlockOn?: Severity;
135
+ /**
136
+ * Maximum self-heal iterations during review
137
+ *
138
+ * Controls how many scan→fix cycles the review phase attempts before
139
+ * giving up on blocking issues.
140
+ * @default 3
141
+ */
142
+ reviewMaxFix?: number;
143
+ /**
144
+ * List of scanner IDs to enable during review
145
+ *
146
+ * When specified, only these scanners are used (e.g., ['lint', 'ai']).
147
+ * When omitted, all configured scanners run.
148
+ */
149
+ reviewScanners?: string[];
93
150
  /**
94
151
  * Run QA workflow after development completes
95
152
  *
@@ -119,6 +176,13 @@ export interface WorkflowConfig {
119
176
  * @example ['docs/architecture.md', 'docs/tech-stack.md']
120
177
  */
121
178
  references: string[];
179
+ /**
180
+ * Absolute path to the workflow session directory
181
+ *
182
+ * When set, session-level reports (review summary, tech debt backlog)
183
+ * are written to this directory. When undefined, session reports are skipped.
184
+ */
185
+ sessionDir?: string;
122
186
  /**
123
187
  * Skip development phase
124
188
  *
@@ -147,8 +211,21 @@ export interface WorkflowConfig {
147
211
  * @default 60
148
212
  */
149
213
  storyInterval: number;
214
+ /**
215
+ * AI review scanner timeout in milliseconds
216
+ *
217
+ * Overrides the global timeout for the AI review phase only.
218
+ * When not set, falls back to the global timeout or the default (5 minutes).
219
+ * Accepts human-readable durations via CLI (e.g., "10m", "1h").
220
+ * @default 300_000 (5 minutes)
221
+ */
222
+ reviewTimeout?: number;
150
223
  /**
151
224
  * Agent execution timeout in milliseconds
225
+ *
226
+ * Global timeout ceiling for all agent spawns, review scanners, and
227
+ * infrastructure operations. Accepts human-readable durations via CLI
228
+ * (e.g., "45m", "1h", "90m").
152
229
  * @default 2_700_000 (45 minutes)
153
230
  */
154
231
  timeout?: number;
@@ -141,6 +141,13 @@ export interface WorkflowResult {
141
141
  * Undefined if QA phase was skipped or not executed.
142
142
  */
143
143
  qaPhase?: PhaseResult;
144
+ /**
145
+ * Review phase result
146
+ *
147
+ * Undefined if review phase was skipped or not executed.
148
+ * Present when config.review === true.
149
+ */
150
+ reviewPhase?: PhaseResult;
144
151
  /**
145
152
  * Story creation phase result
146
153
  *