@hyperdrive.bot/bmad-workflow 1.0.18 → 1.0.20

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 +9 -0
  36. package/dist/commands/workflow.js +114 -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 +85 -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';
@@ -54,6 +57,9 @@ export default class Workflow extends Command {
54
57
  cwd: Flags.string({
55
58
  description: 'Working directory path to pass to AI agents. Agents will operate in this directory.',
56
59
  }),
60
+ 'dev-agent': Flags.string({
61
+ description: 'Absolute path to a custom dev agent file (default: .bmad-core/agents/dev.md)',
62
+ }),
57
63
  'dry-run': Flags.boolean({
58
64
  default: false,
59
65
  description: 'Preview actions without execution',
@@ -90,6 +96,37 @@ export default class Workflow extends Command {
90
96
  description: 'AI provider to use (claude, gemini, or opencode)',
91
97
  options: ['claude', 'gemini', 'opencode'],
92
98
  }),
99
+ mcp: Flags.boolean({
100
+ default: false,
101
+ description: 'Enable MCP tool injection for pipeline phases via Docker gateway',
102
+ helpGroup: 'MCP',
103
+ }),
104
+ 'mcp-phases': Flags.string({
105
+ description: 'Comma-separated list of phases to inject MCP tools into (valid: epic,story,dev,review,qa)',
106
+ helpGroup: 'MCP',
107
+ }),
108
+ 'mcp-preset': Flags.string({
109
+ description: 'MCP preset name to use for tool configuration (overrides config file)',
110
+ helpGroup: 'MCP',
111
+ }),
112
+ review: Flags.boolean({
113
+ default: false,
114
+ description: 'Enable automated code review phase between dev and QA',
115
+ helpGroup: 'Review Workflow',
116
+ }),
117
+ 'review-block-on': Flags.string({
118
+ description: 'Minimum severity to block story from QA (critical|high|medium|low)',
119
+ helpGroup: 'Review Workflow',
120
+ }),
121
+ 'review-max-fix': Flags.integer({
122
+ description: 'Maximum self-heal fix iterations (default: from config or 3)',
123
+ helpGroup: 'Review Workflow',
124
+ }),
125
+ 'review-scanners': Flags.string({
126
+ description: 'Scanner names to run (e.g., lint, ai, coderabbit). Repeatable.',
127
+ helpGroup: 'Review Workflow',
128
+ multiple: true,
129
+ }),
93
130
  qa: Flags.boolean({
94
131
  default: false,
95
132
  description: 'Run QA workflow after development completes',
@@ -124,9 +161,17 @@ export default class Workflow extends Command {
124
161
  default: 60,
125
162
  description: 'Seconds between story development',
126
163
  }),
127
- timeout: Flags.integer({
164
+ timeout: Flags.custom({
165
+ parse: async (input) => parseDuration(input),
166
+ })({
128
167
  default: 2_700_000,
129
- description: 'Agent execution timeout in milliseconds (default: 2700000 = 45 minutes)',
168
+ description: 'Agent execution timeout accepts durations like 30s, 5m, 1h, 90m, or raw milliseconds (default: 45m)',
169
+ }),
170
+ 'review-timeout': Flags.custom({
171
+ parse: async (input) => parseDuration(input),
172
+ })({
173
+ description: 'AI review scanner timeout — overrides --timeout for review phase only (default: 5m)',
174
+ helpGroup: 'Review Workflow',
130
175
  }),
131
176
  'max-retries': Flags.integer({
132
177
  default: 0,
@@ -178,14 +223,47 @@ export default class Workflow extends Command {
178
223
  if (flags['dry-run']) {
179
224
  this.displayDryRunBanner();
180
225
  }
226
+ // Validate review flags when --review is enabled
227
+ if (flags.review) {
228
+ // Validate --review-scanners against registered scanner names
229
+ if (flags['review-scanners'] && flags['review-scanners'].length > 0) {
230
+ const validScanners = getRegisteredScannerNames();
231
+ const invalidScanners = flags['review-scanners'].filter((s) => !validScanners.includes(s));
232
+ if (invalidScanners.length > 0) {
233
+ this.error(`Unknown --review-scanners value(s): ${invalidScanners.join(', ')}. Valid scanners are: ${validScanners.join(', ')}`, { exit: 1 });
234
+ }
235
+ }
236
+ // Validate --review-max-fix is non-negative
237
+ if (flags['review-max-fix'] !== undefined && flags['review-max-fix'] < 0) {
238
+ this.error(`--review-max-fix must be a non-negative integer, got: ${flags['review-max-fix']}`, { exit: 1 });
239
+ }
240
+ // Validate --review-block-on against severity enum
241
+ if (flags['review-block-on'] !== undefined) {
242
+ const validSeverities = ['critical', 'high', 'medium', 'low'];
243
+ if (!validSeverities.includes(flags['review-block-on'])) {
244
+ this.error(`Invalid --review-block-on value: ${flags['review-block-on']}. Valid values are: ${validSeverities.join(', ')}`, { exit: 1 });
245
+ }
246
+ }
247
+ }
248
+ // Map --review-block-on string to Severity enum
249
+ const severityMap = {
250
+ critical: Severity.CRITICAL,
251
+ high: Severity.HIGH,
252
+ low: Severity.LOW,
253
+ medium: Severity.MEDIUM,
254
+ };
181
255
  // Build workflow configuration
182
256
  const config = {
183
257
  autoFix: flags['auto-fix'],
184
258
  cwd: flags.cwd,
259
+ devAgent: flags['dev-agent'],
185
260
  dryRun: flags['dry-run'],
186
261
  epicInterval: flags['epic-interval'],
187
262
  input: args.input,
188
263
  maxRetries: flags['max-retries'],
264
+ mcp: flags.mcp || undefined,
265
+ mcpPhases: flags['mcp-phases'] ? flags['mcp-phases'].split(',').map((s) => s.trim()) : undefined,
266
+ mcpPreset: flags['mcp-preset'],
189
267
  model: flags.model,
190
268
  parallel: flags.parallel,
191
269
  pipeline: flags.pipeline,
@@ -197,6 +275,11 @@ export default class Workflow extends Command {
197
275
  qaRetries: flags['qa-retries'],
198
276
  references: flags.reference || [],
199
277
  retryBackoffMs: flags['retry-backoff'],
278
+ review: flags.review,
279
+ reviewBlockOn: flags['review-block-on'] ? severityMap[flags['review-block-on']] : undefined,
280
+ reviewMaxFix: flags['review-max-fix'],
281
+ reviewScanners: flags['review-scanners'],
282
+ reviewTimeout: flags['review-timeout'],
200
283
  skipDev: flags['skip-dev'],
201
284
  skipEpics: flags['skip-epics'],
202
285
  skipStories: flags['skip-stories'],
@@ -204,6 +287,35 @@ export default class Workflow extends Command {
204
287
  timeout: flags.timeout,
205
288
  verbose: flags.verbose,
206
289
  };
290
+ // Validate --mcp-phases when --mcp is enabled
291
+ if (config.mcp && config.mcpPhases) {
292
+ const validPhases = ['epic', 'story', 'dev', 'review', 'qa'];
293
+ const invalidPhases = config.mcpPhases.filter((p) => !validPhases.includes(p));
294
+ if (invalidPhases.length > 0) {
295
+ this.error(`Invalid --mcp-phases value(s): ${invalidPhases.join(', ')}. Valid phases are: ${validPhases.join(', ')}`, { exit: 1 });
296
+ }
297
+ }
298
+ // MCP gateway health pre-check (AC: #5, #6)
299
+ if (config.mcp) {
300
+ try {
301
+ const gatewayUrl = 'http://localhost:8080';
302
+ const healthUrl = `${gatewayUrl}/health`;
303
+ const response = await fetch(healthUrl, { signal: AbortSignal.timeout(5000) });
304
+ if (response.ok) {
305
+ if (flags.verbose) {
306
+ this.log(colors.info('MCP gateway is healthy'));
307
+ }
308
+ }
309
+ else {
310
+ this.log(colors.warning(`MCP gateway returned status ${response.status} — continuing without MCP`));
311
+ config.mcp = false;
312
+ }
313
+ }
314
+ catch {
315
+ this.log(colors.warning('MCP gateway is not reachable — continuing without MCP'));
316
+ config.mcp = false;
317
+ }
318
+ }
207
319
  // Log configuration if verbose
208
320
  if (flags.verbose) {
209
321
  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
  *
@@ -14,6 +15,14 @@ export interface WorkflowConfig {
14
15
  * @default false
15
16
  */
16
17
  autoFix?: boolean;
18
+ /**
19
+ * Absolute path to a custom dev agent file
20
+ *
21
+ * When specified, this file is used instead of the default `.bmad-core/agents/dev.md`
22
+ * in dev phase prompts.
23
+ * @example '/Users/marcelo/Developer/ds/super-repo/_bmad/bmm/agents/ibra.md'
24
+ */
25
+ devAgent?: string;
17
26
  /**
18
27
  * Working directory for agent execution
19
28
  *
@@ -77,6 +86,31 @@ export interface WorkflowConfig {
77
86
  * - OpenCode: anthropic/claude-3-5-sonnet, openai/gpt-4o
78
87
  */
79
88
  model?: string;
89
+ /**
90
+ * Enable MCP tool discovery in agent prompts
91
+ *
92
+ * When true, MCP context (available tools/instructions) is prepended to
93
+ * agent prompts before each spawn. Requires McpContextInjector to be
94
+ * configured in the orchestrator.
95
+ * @default false
96
+ */
97
+ mcp?: boolean;
98
+ /**
99
+ * Restrict MCP injection to specific workflow phases
100
+ *
101
+ * When set, only the listed phases receive MCP tool context.
102
+ * When omitted (and mcp=true), all phases receive MCP context.
103
+ * @example ['epic', 'dev'] — only epic and dev phases get MCP tools
104
+ */
105
+ mcpPhases?: string[];
106
+ /**
107
+ * MCP preset name for tool configuration
108
+ *
109
+ * Overrides the configured preset for the pipeline run.
110
+ * Presets define which MCP servers and tools are available.
111
+ * @example 'research'
112
+ */
113
+ mcpPreset?: string;
80
114
  /**
81
115
  * Filename prefix for generated files
82
116
  *
@@ -90,6 +124,37 @@ export interface WorkflowConfig {
90
124
  * @default 'claude'
91
125
  */
92
126
  provider?: 'claude' | 'gemini' | 'opencode';
127
+ /**
128
+ * Run automated code review between dev and QA phases
129
+ *
130
+ * When true, completed stories are reviewed before forwarding to QA.
131
+ * In pipeline mode, review runs concurrently via ReviewQueue.
132
+ * Only PASS stories proceed to QA; FAIL stories are excluded with a warning.
133
+ * @default false
134
+ */
135
+ review?: boolean;
136
+ /**
137
+ * Minimum severity that blocks the pipeline during review
138
+ *
139
+ * Issues at or above this severity after self-heal will cause a FAIL verdict.
140
+ * @default Severity.HIGH
141
+ */
142
+ reviewBlockOn?: Severity;
143
+ /**
144
+ * Maximum self-heal iterations during review
145
+ *
146
+ * Controls how many scan→fix cycles the review phase attempts before
147
+ * giving up on blocking issues.
148
+ * @default 3
149
+ */
150
+ reviewMaxFix?: number;
151
+ /**
152
+ * List of scanner IDs to enable during review
153
+ *
154
+ * When specified, only these scanners are used (e.g., ['lint', 'ai']).
155
+ * When omitted, all configured scanners run.
156
+ */
157
+ reviewScanners?: string[];
93
158
  /**
94
159
  * Run QA workflow after development completes
95
160
  *
@@ -119,6 +184,13 @@ export interface WorkflowConfig {
119
184
  * @example ['docs/architecture.md', 'docs/tech-stack.md']
120
185
  */
121
186
  references: string[];
187
+ /**
188
+ * Absolute path to the workflow session directory
189
+ *
190
+ * When set, session-level reports (review summary, tech debt backlog)
191
+ * are written to this directory. When undefined, session reports are skipped.
192
+ */
193
+ sessionDir?: string;
122
194
  /**
123
195
  * Skip development phase
124
196
  *
@@ -147,8 +219,21 @@ export interface WorkflowConfig {
147
219
  * @default 60
148
220
  */
149
221
  storyInterval: number;
222
+ /**
223
+ * AI review scanner timeout in milliseconds
224
+ *
225
+ * Overrides the global timeout for the AI review phase only.
226
+ * When not set, falls back to the global timeout or the default (5 minutes).
227
+ * Accepts human-readable durations via CLI (e.g., "10m", "1h").
228
+ * @default 300_000 (5 minutes)
229
+ */
230
+ reviewTimeout?: number;
150
231
  /**
151
232
  * Agent execution timeout in milliseconds
233
+ *
234
+ * Global timeout ceiling for all agent spawns, review scanners, and
235
+ * infrastructure operations. Accepts human-readable durations via CLI
236
+ * (e.g., "45m", "1h", "90m").
152
237
  * @default 2_700_000 (45 minutes)
153
238
  */
154
239
  timeout?: number;