@hyperdrive.bot/bmad-workflow 1.0.2

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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1017 -0
  3. package/bin/dev +5 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/dev.js +5 -0
  6. package/bin/run +5 -0
  7. package/bin/run.cmd +3 -0
  8. package/bin/run.js +5 -0
  9. package/dist/commands/config/show.d.ts +34 -0
  10. package/dist/commands/config/show.js +108 -0
  11. package/dist/commands/config/validate.d.ts +29 -0
  12. package/dist/commands/config/validate.js +131 -0
  13. package/dist/commands/decompose.d.ts +79 -0
  14. package/dist/commands/decompose.js +327 -0
  15. package/dist/commands/demo.d.ts +18 -0
  16. package/dist/commands/demo.js +107 -0
  17. package/dist/commands/epics/create.d.ts +123 -0
  18. package/dist/commands/epics/create.js +459 -0
  19. package/dist/commands/epics/list.d.ts +120 -0
  20. package/dist/commands/epics/list.js +280 -0
  21. package/dist/commands/hello/index.d.ts +12 -0
  22. package/dist/commands/hello/index.js +34 -0
  23. package/dist/commands/hello/world.d.ts +8 -0
  24. package/dist/commands/hello/world.js +24 -0
  25. package/dist/commands/prd/fix.d.ts +39 -0
  26. package/dist/commands/prd/fix.js +140 -0
  27. package/dist/commands/prd/validate.d.ts +112 -0
  28. package/dist/commands/prd/validate.js +302 -0
  29. package/dist/commands/stories/create.d.ts +95 -0
  30. package/dist/commands/stories/create.js +431 -0
  31. package/dist/commands/stories/develop.d.ts +91 -0
  32. package/dist/commands/stories/develop.js +460 -0
  33. package/dist/commands/stories/list.d.ts +84 -0
  34. package/dist/commands/stories/list.js +291 -0
  35. package/dist/commands/stories/move.d.ts +66 -0
  36. package/dist/commands/stories/move.js +273 -0
  37. package/dist/commands/stories/qa.d.ts +99 -0
  38. package/dist/commands/stories/qa.js +530 -0
  39. package/dist/commands/workflow.d.ts +97 -0
  40. package/dist/commands/workflow.js +390 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +1 -0
  43. package/dist/models/agent-options.d.ts +50 -0
  44. package/dist/models/agent-options.js +1 -0
  45. package/dist/models/agent-result.d.ts +29 -0
  46. package/dist/models/agent-result.js +1 -0
  47. package/dist/models/index.d.ts +10 -0
  48. package/dist/models/index.js +10 -0
  49. package/dist/models/phase-result.d.ts +65 -0
  50. package/dist/models/phase-result.js +7 -0
  51. package/dist/models/provider.d.ts +28 -0
  52. package/dist/models/provider.js +18 -0
  53. package/dist/models/story.d.ts +154 -0
  54. package/dist/models/story.js +18 -0
  55. package/dist/models/workflow-config.d.ts +148 -0
  56. package/dist/models/workflow-config.js +1 -0
  57. package/dist/models/workflow-result.d.ts +164 -0
  58. package/dist/models/workflow-result.js +7 -0
  59. package/dist/services/agents/agent-runner-factory.d.ts +31 -0
  60. package/dist/services/agents/agent-runner-factory.js +44 -0
  61. package/dist/services/agents/agent-runner.d.ts +46 -0
  62. package/dist/services/agents/agent-runner.js +29 -0
  63. package/dist/services/agents/claude-agent-runner.d.ts +81 -0
  64. package/dist/services/agents/claude-agent-runner.js +332 -0
  65. package/dist/services/agents/gemini-agent-runner.d.ts +82 -0
  66. package/dist/services/agents/gemini-agent-runner.js +350 -0
  67. package/dist/services/agents/index.d.ts +7 -0
  68. package/dist/services/agents/index.js +7 -0
  69. package/dist/services/file-system/file-manager.d.ts +110 -0
  70. package/dist/services/file-system/file-manager.js +223 -0
  71. package/dist/services/file-system/glob-matcher.d.ts +75 -0
  72. package/dist/services/file-system/glob-matcher.js +126 -0
  73. package/dist/services/file-system/path-resolver.d.ts +183 -0
  74. package/dist/services/file-system/path-resolver.js +400 -0
  75. package/dist/services/logging/workflow-logger.d.ts +232 -0
  76. package/dist/services/logging/workflow-logger.js +552 -0
  77. package/dist/services/orchestration/batch-processor.d.ts +113 -0
  78. package/dist/services/orchestration/batch-processor.js +187 -0
  79. package/dist/services/orchestration/dependency-graph-executor.d.ts +60 -0
  80. package/dist/services/orchestration/dependency-graph-executor.js +447 -0
  81. package/dist/services/orchestration/index.d.ts +10 -0
  82. package/dist/services/orchestration/index.js +8 -0
  83. package/dist/services/orchestration/input-detector.d.ts +125 -0
  84. package/dist/services/orchestration/input-detector.js +381 -0
  85. package/dist/services/orchestration/story-queue.d.ts +94 -0
  86. package/dist/services/orchestration/story-queue.js +170 -0
  87. package/dist/services/orchestration/story-type-detector.d.ts +80 -0
  88. package/dist/services/orchestration/story-type-detector.js +258 -0
  89. package/dist/services/orchestration/task-decomposition-service.d.ts +67 -0
  90. package/dist/services/orchestration/task-decomposition-service.js +607 -0
  91. package/dist/services/orchestration/workflow-orchestrator.d.ts +659 -0
  92. package/dist/services/orchestration/workflow-orchestrator.js +2201 -0
  93. package/dist/services/parsers/epic-parser.d.ts +117 -0
  94. package/dist/services/parsers/epic-parser.js +264 -0
  95. package/dist/services/parsers/prd-fixer.d.ts +86 -0
  96. package/dist/services/parsers/prd-fixer.js +194 -0
  97. package/dist/services/parsers/prd-parser.d.ts +123 -0
  98. package/dist/services/parsers/prd-parser.js +286 -0
  99. package/dist/services/parsers/standalone-story-parser.d.ts +114 -0
  100. package/dist/services/parsers/standalone-story-parser.js +255 -0
  101. package/dist/services/parsers/story-parser-factory.d.ts +81 -0
  102. package/dist/services/parsers/story-parser-factory.js +108 -0
  103. package/dist/services/parsers/story-parser.d.ts +122 -0
  104. package/dist/services/parsers/story-parser.js +262 -0
  105. package/dist/services/scaffolding/decompose-session-scaffolder.d.ts +74 -0
  106. package/dist/services/scaffolding/decompose-session-scaffolder.js +315 -0
  107. package/dist/services/scaffolding/file-scaffolder.d.ts +94 -0
  108. package/dist/services/scaffolding/file-scaffolder.js +314 -0
  109. package/dist/services/validation/config-validator.d.ts +88 -0
  110. package/dist/services/validation/config-validator.js +167 -0
  111. package/dist/types/task-graph.d.ts +142 -0
  112. package/dist/types/task-graph.js +5 -0
  113. package/dist/utils/colors.d.ts +49 -0
  114. package/dist/utils/colors.js +50 -0
  115. package/dist/utils/error-formatter.d.ts +64 -0
  116. package/dist/utils/error-formatter.js +279 -0
  117. package/dist/utils/errors.d.ts +170 -0
  118. package/dist/utils/errors.js +233 -0
  119. package/dist/utils/formatters.d.ts +84 -0
  120. package/dist/utils/formatters.js +162 -0
  121. package/dist/utils/logger.d.ts +63 -0
  122. package/dist/utils/logger.js +78 -0
  123. package/dist/utils/progress.d.ts +104 -0
  124. package/dist/utils/progress.js +161 -0
  125. package/dist/utils/retry.d.ts +114 -0
  126. package/dist/utils/retry.js +160 -0
  127. package/dist/utils/shared-flags.d.ts +28 -0
  128. package/dist/utils/shared-flags.js +43 -0
  129. package/package.json +119 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * File Scaffolder Service
3
+ *
4
+ * Creates scaffolded markdown files for epics and stories with pre-populated
5
+ * metadata headers and empty content sections following template structure.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const scaffolder = new FileScaffolder(logger)
10
+ * const epicContent = scaffolder.scaffoldEpic({
11
+ * epicNumber: 1,
12
+ * epicTitle: 'Foundation Setup',
13
+ * prefix: 'SIGN-TEMPLATES'
14
+ * })
15
+ * ```
16
+ */
17
+ import type pino from 'pino';
18
+ /**
19
+ * Epic scaffolding data
20
+ */
21
+ export interface EpicScaffoldData {
22
+ /**
23
+ * Epic number (1, 2, 3, etc.)
24
+ */
25
+ epicNumber: number;
26
+ /**
27
+ * Epic title
28
+ */
29
+ epicTitle: string;
30
+ /**
31
+ * Project prefix (e.g., 'SIGN-TEMPLATES', 'USER-GROUPS')
32
+ */
33
+ prefix: string;
34
+ }
35
+ /**
36
+ * Story scaffolding data
37
+ */
38
+ export interface StoryScaffoldData {
39
+ /**
40
+ * Epic number
41
+ */
42
+ epicNumber: number;
43
+ /**
44
+ * Story number within epic
45
+ */
46
+ storyNumber: number;
47
+ /**
48
+ * Story title
49
+ */
50
+ storyTitle: string;
51
+ }
52
+ /**
53
+ * File Scaffolder Service
54
+ *
55
+ * Generates structured markdown files with populated metadata sections
56
+ * and empty content sections ready for AI agent population.
57
+ */
58
+ export declare class FileScaffolder {
59
+ private readonly logger;
60
+ /**
61
+ * Create a new FileScaffolder instance
62
+ *
63
+ * @param logger - Logger instance for structured logging
64
+ */
65
+ constructor(logger: pino.Logger);
66
+ /**
67
+ * Scaffold an epic markdown file
68
+ *
69
+ * Creates a structured epic file with:
70
+ * - Populated metadata: Epic ID, Status (Draft), Creation Date
71
+ * - Empty content sections: Epic Goal, Description, Stories, etc.
72
+ *
73
+ * @param data - Epic scaffolding data
74
+ * @returns Scaffolded epic markdown content
75
+ */
76
+ scaffoldEpic(data: EpicScaffoldData): string;
77
+ /**
78
+ * Scaffold a story markdown file
79
+ *
80
+ * Creates a structured story file with:
81
+ * - Populated metadata: Status (Draft)
82
+ * - Empty content sections: Story, Acceptance Criteria, Tasks, etc.
83
+ *
84
+ * @param data - Story scaffolding data
85
+ * @returns Scaffolded story markdown content
86
+ */
87
+ scaffoldStory(data: StoryScaffoldData): string;
88
+ /**
89
+ * Get current date in YYYY-MM-DD format
90
+ *
91
+ * @returns Current date string
92
+ */
93
+ private getCurrentDate;
94
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * File Scaffolder Service
3
+ *
4
+ * Creates scaffolded markdown files for epics and stories with pre-populated
5
+ * metadata headers and empty content sections following template structure.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const scaffolder = new FileScaffolder(logger)
10
+ * const epicContent = scaffolder.scaffoldEpic({
11
+ * epicNumber: 1,
12
+ * epicTitle: 'Foundation Setup',
13
+ * prefix: 'SIGN-TEMPLATES'
14
+ * })
15
+ * ```
16
+ */
17
+ /**
18
+ * File Scaffolder Service
19
+ *
20
+ * Generates structured markdown files with populated metadata sections
21
+ * and empty content sections ready for AI agent population.
22
+ */
23
+ export class FileScaffolder {
24
+ logger;
25
+ /**
26
+ * Create a new FileScaffolder instance
27
+ *
28
+ * @param logger - Logger instance for structured logging
29
+ */
30
+ constructor(logger) {
31
+ this.logger = logger;
32
+ }
33
+ /**
34
+ * Scaffold an epic markdown file
35
+ *
36
+ * Creates a structured epic file with:
37
+ * - Populated metadata: Epic ID, Status (Draft), Creation Date
38
+ * - Empty content sections: Epic Goal, Description, Stories, etc.
39
+ *
40
+ * @param data - Epic scaffolding data
41
+ * @returns Scaffolded epic markdown content
42
+ */
43
+ scaffoldEpic(data) {
44
+ const epicId = `${data.prefix}-EPIC-${data.epicNumber}`;
45
+ const currentDate = this.getCurrentDate();
46
+ this.logger.info({
47
+ epicId,
48
+ epicNumber: data.epicNumber,
49
+ epicTitle: data.epicTitle,
50
+ }, 'Scaffolding epic file');
51
+ // Hardcoded epic scaffold following epic-tmpl.yaml structure
52
+ const content = `# Epic ${data.epicNumber}: ${data.epicTitle}
53
+
54
+ <!-- Powered by BMAD™ Core -->
55
+
56
+ ## Epic Header
57
+
58
+ **Epic ID:** ${epicId}
59
+ **Epic Type:** _[To be determined by sm agent]_
60
+ **Status:** Draft
61
+ **Priority:** _[To be determined by sm agent]_
62
+ **Created:** ${currentDate}
63
+
64
+ ---
65
+
66
+ ## Epic Goal
67
+
68
+ _[AI Agent will populate: 2-3 sentences describing what this epic will accomplish and the end state it will deliver]_
69
+
70
+ ---
71
+
72
+ ## Epic Description
73
+
74
+ ### Existing System Context
75
+
76
+ _[AI Agent will populate if Brownfield Enhancement]_
77
+
78
+ **Current relevant functionality:**
79
+
80
+ **Technology stack:**
81
+
82
+ **Integration points:**
83
+
84
+ ### Enhancement Details
85
+
86
+ #### What's Being Added/Changed
87
+
88
+ _[AI Agent will populate: Specific deliverables with file locations]_
89
+
90
+ #### How It Integrates
91
+
92
+ _[AI Agent will populate: Integration approach and patterns]_
93
+
94
+ #### Success Criteria
95
+
96
+ _[AI Agent will populate: Measurable outcomes defining "done"]_
97
+
98
+ ---
99
+
100
+ ## Stories
101
+
102
+ _[AI Agent will populate: List of stories with descriptions and key tasks]_
103
+
104
+ ---
105
+
106
+ ## Compatibility Requirements
107
+
108
+ _[AI Agent will populate: Backward compatibility checklist]_
109
+
110
+ - [ ] Existing APIs remain unchanged (or document breaking changes)
111
+ - [ ] Database schema changes are backward compatible
112
+ - [ ] UI changes follow existing patterns
113
+ - [ ] Performance impact is minimal
114
+ - [ ] No regression in existing functionality
115
+
116
+ ---
117
+
118
+ ## Risk Mitigation
119
+
120
+ ### Primary Risk
121
+
122
+ _[AI Agent will populate]_
123
+
124
+ ### Mitigation
125
+
126
+ _[AI Agent will populate: Specific actions to prevent or reduce risk]_
127
+
128
+ ### Rollback Plan
129
+
130
+ _[AI Agent will populate: Step-by-step rollback procedure]_
131
+
132
+ ---
133
+
134
+ ## Definition of Done
135
+
136
+ - [ ] All stories completed with acceptance criteria met
137
+ - [ ] Unit test coverage ≥ 80% for new code
138
+ - [ ] Integration tests verify end-to-end flows
139
+ - [ ] TypeScript compilation passes with strict mode
140
+ - [ ] Code follows project style guides
141
+ - [ ] Documentation updated appropriately
142
+ - [ ] No regression in existing features verified
143
+ - [ ] Performance targets met
144
+
145
+ ---
146
+
147
+ ## Validation Checklist
148
+
149
+ ### Scope Validation
150
+
151
+ - [ ] Epic contains appropriate number of stories (1-10)
152
+ - [ ] No major architectural changes (or documented if required)
153
+ - [ ] Enhancement follows existing patterns where applicable
154
+ - [ ] Integration complexity is manageable
155
+
156
+ ### Risk Assessment
157
+
158
+ - [ ] Risk to existing system is acceptable
159
+ - [ ] Rollback plan is feasible
160
+ - [ ] Testing approach covers critical paths
161
+ - [ ] Team has sufficient knowledge/expertise
162
+
163
+ ### Completeness Check
164
+
165
+ - [ ] Epic goal is clear and achievable
166
+ - [ ] Stories are properly scoped and sequenced
167
+ - [ ] Success criteria are measurable
168
+ - [ ] Dependencies are identified
169
+
170
+ ---
171
+
172
+ ## Technical Implementation Notes
173
+
174
+ _[AI Agent will populate if needed: Key integration patterns, architecture decisions, file locations, testing strategy]_
175
+
176
+ ---
177
+
178
+ ## Story Manager Handoff
179
+
180
+ _[AI Agent will populate: Handoff message to Story Manager with context and requirements]_
181
+
182
+ ---
183
+
184
+ ## Change Log
185
+
186
+ | Date | Version | Description | Author |
187
+ |------|---------|-------------|--------|
188
+ | ${currentDate} | 1.0 | Epic created | AI Agent |
189
+ `;
190
+ this.logger.debug({ epicId, length: content.length }, 'Epic scaffold generated');
191
+ return content;
192
+ }
193
+ /**
194
+ * Scaffold a story markdown file
195
+ *
196
+ * Creates a structured story file with:
197
+ * - Populated metadata: Status (Draft)
198
+ * - Empty content sections: Story, Acceptance Criteria, Tasks, etc.
199
+ *
200
+ * @param data - Story scaffolding data
201
+ * @returns Scaffolded story markdown content
202
+ */
203
+ scaffoldStory(data) {
204
+ const storyId = `${data.epicNumber}.${data.storyNumber}`;
205
+ const currentDate = this.getCurrentDate();
206
+ this.logger.info({
207
+ storyId,
208
+ storyTitle: data.storyTitle,
209
+ }, 'Scaffolding story file');
210
+ // Hardcoded story scaffold following story-tmpl.yaml structure
211
+ const content = `# Story ${storyId}: ${data.storyTitle}
212
+
213
+ <!-- Powered by BMAD™ Core -->
214
+
215
+ ## Status
216
+
217
+ Draft
218
+
219
+ ---
220
+
221
+ ## Story
222
+
223
+ **As a** _[AI Agent will populate]_,
224
+ **I want** _[AI Agent will populate]_,
225
+ **so that** _[AI Agent will populate]_
226
+
227
+ ---
228
+
229
+ ## Acceptance Criteria
230
+
231
+ _[AI Agent will populate: Numbered list copied from epic]_
232
+
233
+ 1. _[Criterion 1]_
234
+ 2. _[Criterion 2]_
235
+ 3. _[Criterion 3]_
236
+
237
+ ---
238
+
239
+ ## Tasks / Subtasks
240
+
241
+ _[AI Agent will populate: Break down into specific tasks with subtasks]_
242
+
243
+ - [ ] Task 1 (AC: #)
244
+ - [ ] Subtask 1.1
245
+ - [ ] Subtask 1.2
246
+ - [ ] Task 2 (AC: #)
247
+ - [ ] Subtask 2.1
248
+ - [ ] Task 3 (AC: #)
249
+ - [ ] Subtask 3.1
250
+
251
+ ---
252
+
253
+ ## Dev Notes
254
+
255
+ _[AI Agent will populate: Relevant architecture info, source tree context, and previous story notes]_
256
+
257
+ ### Testing
258
+
259
+ _[AI Agent will populate: Testing standards, file locations, frameworks, patterns]_
260
+
261
+ - Test file location:
262
+ - Test standards:
263
+ - Testing frameworks:
264
+ - Specific requirements:
265
+
266
+ ---
267
+
268
+ ## Change Log
269
+
270
+ | Date | Version | Description | Author |
271
+ |------|---------|-------------|--------|
272
+ | ${currentDate} | 1.0 | Story created | AI Agent |
273
+
274
+ ---
275
+
276
+ ## Dev Agent Record
277
+
278
+ _[This section will be populated by the development agent during implementation]_
279
+
280
+ ### Agent Model Used
281
+
282
+ _[AI Agent will record model and version]_
283
+
284
+ ### Debug Log References
285
+
286
+ _[AI Agent will record debug log references]_
287
+
288
+ ### Completion Notes List
289
+
290
+ _[AI Agent will record completion notes and issues encountered]_
291
+
292
+ ### File List
293
+
294
+ _[AI Agent will list all files created, modified, or affected]_
295
+
296
+ ---
297
+
298
+ ## QA Results
299
+
300
+ _[This section will be populated by QA Agent after review]_
301
+ `;
302
+ this.logger.debug({ length: content.length, storyId }, 'Story scaffold generated');
303
+ return content;
304
+ }
305
+ /**
306
+ * Get current date in YYYY-MM-DD format
307
+ *
308
+ * @returns Current date string
309
+ */
310
+ getCurrentDate() {
311
+ const now = new Date();
312
+ return now.toISOString().split('T')[0];
313
+ }
314
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ConfigValidator Service
3
+ *
4
+ * Validates configuration files against schemas using Zod.
5
+ * Ensures configuration has all required fields with correct types and that
6
+ * referenced directories exist.
7
+ */
8
+ import type pino from 'pino';
9
+ import { z } from 'zod';
10
+ import type { FileManager } from '../file-system/file-manager.js';
11
+ /**
12
+ * Zod schema for core configuration
13
+ *
14
+ * Defines the expected structure of .bmad-core/core-config.yaml
15
+ * with all required fields for the BMAD Orchestrator CLI.
16
+ */
17
+ declare const configSchema: z.ZodObject<{
18
+ devStoryLocation: z.ZodString;
19
+ prd: z.ZodOptional<z.ZodObject<{
20
+ prdFile: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>>;
22
+ qa: z.ZodOptional<z.ZodObject<{
23
+ qaLocation: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$strip>>;
25
+ }, z.core.$strip>;
26
+ /**
27
+ * Inferred TypeScript type from Zod schema
28
+ */
29
+ export type CoreConfig = z.infer<typeof configSchema>;
30
+ /**
31
+ * ConfigValidator service validates configuration files against schemas
32
+ *
33
+ * Uses Zod for schema validation and provides clear, actionable error messages
34
+ * when validation fails. Also validates that referenced directories exist.
35
+ */
36
+ export declare class ConfigValidator {
37
+ /**
38
+ * FileManager instance for directory existence checks
39
+ */
40
+ private readonly fileManager;
41
+ /**
42
+ * Logger instance for validation operations
43
+ */
44
+ private readonly logger;
45
+ /**
46
+ * Create a new ConfigValidator instance
47
+ *
48
+ * @param fileManager - FileManager instance for file system operations
49
+ * @param logger - Pino logger instance for logging validation operations
50
+ * @example
51
+ * const logger = createLogger({ namespace: 'services:validation' })
52
+ * const fileManager = new FileManager(logger)
53
+ * const validator = new ConfigValidator(fileManager, logger)
54
+ */
55
+ constructor(fileManager: FileManager, logger: pino.Logger);
56
+ /**
57
+ * Validate configuration object against schema
58
+ *
59
+ * Validates that:
60
+ * 1. All required fields are present
61
+ * 2. All fields have correct types
62
+ * 3. Referenced directories exist
63
+ *
64
+ * @param config - Unknown configuration object to validate
65
+ * @returns Validated CoreConfig object
66
+ * @throws {ValidationError} If validation fails with detailed error messages
67
+ * @example
68
+ * const config = { epicDir: 'docs/epics', storyDir: 'docs/stories', ... }
69
+ * const validConfig = validator.validate(config)
70
+ */
71
+ validate(config: unknown): Promise<CoreConfig>;
72
+ /**
73
+ * Get custom error message and helpful suggestion based on Zod validation error
74
+ *
75
+ * @param error - Zod issue from validation failure
76
+ * @param config - The configuration object being validated
77
+ * @returns Object with error message and suggestion
78
+ */
79
+ private getErrorMessageAndSuggestion;
80
+ /**
81
+ * Validate that all directory paths in config exist
82
+ *
83
+ * @param config - Validated configuration object
84
+ * @throws {ValidationError} If any directory does not exist
85
+ */
86
+ private validateDirectories;
87
+ }
88
+ export {};
@@ -0,0 +1,167 @@
1
+ /**
2
+ * ConfigValidator Service
3
+ *
4
+ * Validates configuration files against schemas using Zod.
5
+ * Ensures configuration has all required fields with correct types and that
6
+ * referenced directories exist.
7
+ */
8
+ import { z } from 'zod';
9
+ import { ValidationError } from '../../utils/errors.js';
10
+ /**
11
+ * Zod schema for core configuration
12
+ *
13
+ * Defines the expected structure of .bmad-core/core-config.yaml
14
+ * with all required fields for the BMAD Orchestrator CLI.
15
+ */
16
+ const configSchema = z.object({
17
+ devStoryLocation: z.string().min(1, { message: 'Story directory path (devStoryLocation) is required' }),
18
+ prd: z
19
+ .object({
20
+ prdFile: z.string().optional(),
21
+ })
22
+ .optional(),
23
+ qa: z
24
+ .object({
25
+ qaLocation: z.string().optional(),
26
+ })
27
+ .optional(),
28
+ });
29
+ /**
30
+ * ConfigValidator service validates configuration files against schemas
31
+ *
32
+ * Uses Zod for schema validation and provides clear, actionable error messages
33
+ * when validation fails. Also validates that referenced directories exist.
34
+ */
35
+ export class ConfigValidator {
36
+ /**
37
+ * FileManager instance for directory existence checks
38
+ */
39
+ fileManager;
40
+ /**
41
+ * Logger instance for validation operations
42
+ */
43
+ logger;
44
+ /**
45
+ * Create a new ConfigValidator instance
46
+ *
47
+ * @param fileManager - FileManager instance for file system operations
48
+ * @param logger - Pino logger instance for logging validation operations
49
+ * @example
50
+ * const logger = createLogger({ namespace: 'services:validation' })
51
+ * const fileManager = new FileManager(logger)
52
+ * const validator = new ConfigValidator(fileManager, logger)
53
+ */
54
+ constructor(fileManager, logger) {
55
+ this.fileManager = fileManager;
56
+ this.logger = logger;
57
+ }
58
+ /**
59
+ * Validate configuration object against schema
60
+ *
61
+ * Validates that:
62
+ * 1. All required fields are present
63
+ * 2. All fields have correct types
64
+ * 3. Referenced directories exist
65
+ *
66
+ * @param config - Unknown configuration object to validate
67
+ * @returns Validated CoreConfig object
68
+ * @throws {ValidationError} If validation fails with detailed error messages
69
+ * @example
70
+ * const config = { epicDir: 'docs/epics', storyDir: 'docs/stories', ... }
71
+ * const validConfig = validator.validate(config)
72
+ */
73
+ async validate(config) {
74
+ this.logger.info('Validating configuration');
75
+ // Step 1: Validate schema (required fields, types)
76
+ const result = configSchema.safeParse(config);
77
+ if (!result.success) {
78
+ const firstError = result.error.issues[0];
79
+ const fieldPath = firstError.path.join('.');
80
+ // Get custom error message and suggestion
81
+ const { message: errorMessage, suggestion } = this.getErrorMessageAndSuggestion(firstError, config);
82
+ const fullMessage = `Configuration validation failed: ${errorMessage}. ${suggestion}`;
83
+ this.logger.error({ field: fieldPath }, 'Configuration validation failed: %s', errorMessage);
84
+ throw new ValidationError(fullMessage, {
85
+ field: fieldPath,
86
+ received: config?.[firstError.path[0]],
87
+ });
88
+ }
89
+ const validConfig = result.data;
90
+ // Step 2: Validate directory existence
91
+ await this.validateDirectories(validConfig);
92
+ this.logger.info('Configuration validated successfully');
93
+ return validConfig;
94
+ }
95
+ /**
96
+ * Get custom error message and helpful suggestion based on Zod validation error
97
+ *
98
+ * @param error - Zod issue from validation failure
99
+ * @param config - The configuration object being validated
100
+ * @returns Object with error message and suggestion
101
+ */
102
+ getErrorMessageAndSuggestion(error, config) {
103
+ const fieldName = error.path[0];
104
+ const examples = {
105
+ devStoryLocation: 'docs/stories',
106
+ 'prd.prdFile': 'docs/prd.md',
107
+ 'qa.qaLocation': 'docs/qa',
108
+ };
109
+ const fieldDisplayNames = {
110
+ devStoryLocation: 'Story directory path (devStoryLocation)',
111
+ 'prd.prdFile': 'PRD file path (prd.prdFile)',
112
+ 'qa.qaLocation': 'QA location path (qa.qaLocation)',
113
+ };
114
+ const example = examples[fieldName] || 'path/to/directory';
115
+ const displayName = fieldDisplayNames[fieldName] || fieldName;
116
+ // Missing field or empty string
117
+ if (error.code === 'too_small') {
118
+ return {
119
+ message: `${displayName} is required`,
120
+ suggestion: `Add this field to .bmad-core/core-config.yaml with a valid path (e.g., "${example}").`,
121
+ };
122
+ }
123
+ // Wrong type or missing field
124
+ if (error.code === 'invalid_type') {
125
+ const invalidTypeError = error;
126
+ // Check if field exists in config (to differentiate missing vs wrong type)
127
+ const configObj = config;
128
+ const fieldValue = configObj?.[fieldName];
129
+ const fieldIsMissing = fieldValue === undefined || fieldValue === null;
130
+ // If field is missing, provide custom message
131
+ if (fieldIsMissing) {
132
+ return {
133
+ message: `${displayName} is required`,
134
+ suggestion: `Add this field to .bmad-core/core-config.yaml with a valid path (e.g., "${example}").`,
135
+ };
136
+ }
137
+ // Wrong type provided - use Zod's message which includes the types
138
+ return {
139
+ message: error.message, // e.g., "Invalid input: expected string, received number"
140
+ suggestion: `Update the value in .bmad-core/core-config.yaml to a valid ${invalidTypeError.expected}.`,
141
+ };
142
+ }
143
+ // Generic fallback
144
+ return {
145
+ message: error.message,
146
+ suggestion: `Check the value for '${fieldName}' in .bmad-core/core-config.yaml.`,
147
+ };
148
+ }
149
+ /**
150
+ * Validate that all directory paths in config exist
151
+ *
152
+ * @param config - Validated configuration object
153
+ * @throws {ValidationError} If any directory does not exist
154
+ */
155
+ async validateDirectories(config) {
156
+ // Validate story directory exists
157
+ const storyDir = config.devStoryLocation;
158
+ const exists = await this.fileManager.fileExists(storyDir);
159
+ if (!exists) {
160
+ const message = `Configuration validation failed: Story directory '${storyDir}' does not exist. ` +
161
+ `Create the directory with 'mkdir -p ${storyDir}' or update 'devStoryLocation' in .bmad-core/core-config.yaml.`;
162
+ this.logger.error({ field: 'devStoryLocation', path: storyDir }, 'Directory validation failed: %s', message);
163
+ throw new ValidationError(message, { field: 'devStoryLocation', path: storyDir });
164
+ }
165
+ this.logger.debug({ field: 'devStoryLocation' }, 'Directory exists: %s', storyDir);
166
+ }
167
+ }