@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,223 @@
1
+ /**
2
+ * FileManager Service
3
+ *
4
+ * Provides safe file system operations with error handling and logging.
5
+ * All file operations throughout the CLI should use this service for consistency and testability.
6
+ */
7
+ import fs from 'fs-extra';
8
+ import { dirname } from 'node:path';
9
+ import { FileSystemError } from '../../utils/errors.js';
10
+ /**
11
+ * FileManager service for all file system operations
12
+ *
13
+ * Abstracts file system operations behind a consistent interface with
14
+ * comprehensive error handling and logging. All methods use async/await
15
+ * for consistency.
16
+ */
17
+ export class FileManager {
18
+ /**
19
+ * Logger instance for file operations
20
+ */
21
+ logger;
22
+ /**
23
+ * Create a new FileManager instance
24
+ *
25
+ * @param logger - Pino logger instance for logging file operations
26
+ * @example
27
+ * const logger = createLogger({ namespace: 'services:file-system' })
28
+ * const fileManager = new FileManager(logger)
29
+ */
30
+ constructor(logger) {
31
+ this.logger = logger;
32
+ }
33
+ /**
34
+ * Create a directory (and parent directories if needed)
35
+ *
36
+ * Uses ensureDir which is idempotent - safe to call if directory already exists.
37
+ *
38
+ * @param path - Path to the directory to create
39
+ * @throws {FileSystemError} If directory cannot be created (permission denied, invalid path, etc.)
40
+ * @example
41
+ * await fileManager.createDirectory('docs/epics')
42
+ */
43
+ async createDirectory(path) {
44
+ this.logger.info('Creating directory: %s', path);
45
+ try {
46
+ await fs.ensureDir(path);
47
+ this.logger.info('Directory created successfully: %s', path);
48
+ }
49
+ catch (error) {
50
+ const err = error;
51
+ this.logger.error('Error creating directory %s: %O', path, err);
52
+ throw new FileSystemError(`Failed to create directory: ${path}: ${err.message}`, {
53
+ operation: 'createDirectory',
54
+ originalError: err.message,
55
+ path,
56
+ });
57
+ }
58
+ }
59
+ /**
60
+ * Check if a file or directory exists
61
+ *
62
+ * @param path - Path to check for existence
63
+ * @returns True if path exists, false otherwise
64
+ * @example
65
+ * const exists = await fileManager.fileExists('docs/prd.md')
66
+ * if (!exists) {
67
+ * throw new Error('PRD not found')
68
+ * }
69
+ */
70
+ async fileExists(path) {
71
+ this.logger.debug('Checking file existence: %s', path);
72
+ try {
73
+ const exists = await fs.pathExists(path);
74
+ this.logger.debug('File existence check result for %s: %s', path, exists);
75
+ return exists;
76
+ }
77
+ catch (error) {
78
+ // pathExists should not throw, but handle defensively
79
+ const err = error;
80
+ this.logger.error('Error checking file existence %s: %O', path, err);
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * List files in a directory matching an optional pattern
86
+ *
87
+ * @param directory - Directory to list files from
88
+ * @param pattern - Optional glob pattern to filter files (e.g., '*.md', 'STORY-*.md')
89
+ * @returns Array of file paths matching the pattern
90
+ * @throws {FileSystemError} If directory cannot be read
91
+ * @example
92
+ * const storyFiles = await fileManager.listFiles('docs/stories', 'STORY-*.md')
93
+ */
94
+ async listFiles(directory, pattern) {
95
+ this.logger.info('Listing files in directory: %s with pattern: %s', directory, pattern ?? 'none');
96
+ try {
97
+ const files = await fs.readdir(directory);
98
+ // Filter files by pattern if provided
99
+ const filteredFiles = pattern ? files.filter((file) => this.matchesPattern(file, pattern)) : files;
100
+ const filePaths = filteredFiles.map((file) => `${directory}/${file}`);
101
+ this.logger.info('Found %d files in directory: %s', filePaths.length, directory);
102
+ return filePaths;
103
+ }
104
+ catch (error) {
105
+ const err = error;
106
+ this.logger.error('Error listing files in directory %s: %O', directory, err);
107
+ throw new FileSystemError(`Failed to list files in directory: ${directory}: ${err.message}`, {
108
+ directory,
109
+ operation: 'listFiles',
110
+ originalError: err.message,
111
+ pattern,
112
+ });
113
+ }
114
+ }
115
+ /**
116
+ * Move a file from source to destination
117
+ *
118
+ * Creates destination directory if it doesn't exist.
119
+ * Overwrites destination file if it exists.
120
+ *
121
+ * @param source - Source file path
122
+ * @param dest - Destination file path
123
+ * @throws {FileSystemError} If file cannot be moved (source not found, permission denied, etc.)
124
+ * @example
125
+ * await fileManager.moveFile('docs/stories/1.1-story.md', 'docs/qa/stories/1.1-story.md')
126
+ */
127
+ async moveFile(source, dest) {
128
+ this.logger.info('Moving file from %s to %s', source, dest);
129
+ try {
130
+ // Ensure destination directory exists
131
+ await fs.ensureDir(dirname(dest));
132
+ // Move file with overwrite
133
+ await fs.move(source, dest, { overwrite: true });
134
+ this.logger.info('File moved successfully from %s to %s', source, dest);
135
+ }
136
+ catch (error) {
137
+ const err = error;
138
+ this.logger.error('Error moving file from %s to %s: %O', source, dest, err);
139
+ throw new FileSystemError(`Failed to move file from ${source} to ${dest}: ${err.message}`, {
140
+ dest,
141
+ operation: 'moveFile',
142
+ originalError: err.message,
143
+ source,
144
+ });
145
+ }
146
+ }
147
+ /**
148
+ * Read file contents as UTF-8 string
149
+ *
150
+ * @param path - Path to the file to read
151
+ * @returns File content as string
152
+ * @throws {FileSystemError} If file cannot be read (not found, permission denied, etc.)
153
+ * @example
154
+ * const content = await fileManager.readFile('docs/prd.md')
155
+ */
156
+ async readFile(path) {
157
+ this.logger.info('Reading file: %s', path);
158
+ try {
159
+ const content = await fs.readFile(path, 'utf8');
160
+ this.logger.info('File read successfully: %s', path);
161
+ return content;
162
+ }
163
+ catch (error) {
164
+ const err = error;
165
+ this.logger.error('Error reading file %s: %O', path, err);
166
+ throw new FileSystemError(`Failed to read file: ${path}: ${err.message}`, {
167
+ operation: 'readFile',
168
+ originalError: err.message,
169
+ path,
170
+ });
171
+ }
172
+ }
173
+ /**
174
+ * Write content to file as UTF-8
175
+ *
176
+ * Creates parent directories if they don't exist.
177
+ * Overwrites existing file if present.
178
+ *
179
+ * @param path - Path to the file to write
180
+ * @param content - Content to write to file
181
+ * @throws {FileSystemError} If file cannot be written (permission denied, invalid path, etc.)
182
+ * @example
183
+ * await fileManager.writeFile('docs/epics/epic-1.md', epicContent)
184
+ */
185
+ async writeFile(path, content) {
186
+ this.logger.info('Writing file: %s', path);
187
+ try {
188
+ // Ensure parent directory exists
189
+ await fs.ensureDir(dirname(path));
190
+ // Write file with UTF-8 encoding
191
+ await fs.writeFile(path, content, 'utf8');
192
+ this.logger.info('File written successfully: %s', path);
193
+ }
194
+ catch (error) {
195
+ const err = error;
196
+ this.logger.error('Error writing file %s: %O', path, err);
197
+ throw new FileSystemError(`Failed to write file: ${path}: ${err.message}`, {
198
+ operation: 'writeFile',
199
+ originalError: err.message,
200
+ path,
201
+ });
202
+ }
203
+ }
204
+ /**
205
+ * Match a file name against a simple glob pattern
206
+ *
207
+ * Supports * wildcard only (e.g., 'STORY-*.md', '*.txt')
208
+ *
209
+ * @param fileName - File name to check
210
+ * @param pattern - Glob pattern with * wildcards
211
+ * @returns True if file matches pattern
212
+ * @private
213
+ */
214
+ matchesPattern(fileName, pattern) {
215
+ // Convert glob pattern to regex
216
+ // Escape special regex characters except *
217
+ const regexPattern = pattern
218
+ .replaceAll(/[.+?^${}()|[\]\\]/g, String.raw `\$&`) // Escape special chars
219
+ .replaceAll('*', '.*'); // Convert * to .*
220
+ const regex = new RegExp(`^${regexPattern}$`);
221
+ return regex.test(fileName);
222
+ }
223
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * GlobMatcher Service
3
+ *
4
+ * Provides glob pattern matching functionality with wildcard support.
5
+ * Enables flexible file pattern matching for story and epic file selection.
6
+ */
7
+ import type pino from 'pino';
8
+ import { FileManager } from './file-manager.js';
9
+ /**
10
+ * GlobMatcher service for file pattern matching
11
+ *
12
+ * Expands glob patterns (with * and ? wildcards) to lists of matching files.
13
+ * All results are filtered to existing files only and sorted alphabetically.
14
+ *
15
+ * @example
16
+ * const globMatcher = new GlobMatcher(fileManager, logger)
17
+ * const files = await globMatcher.expandPattern('docs/stories/AUTH-*.md')
18
+ * // Returns: ['docs/stories/AUTH-1.md', 'docs/stories/AUTH-2.md']
19
+ */
20
+ export declare class GlobMatcher {
21
+ /**
22
+ * FileManager instance for file existence checks
23
+ */
24
+ private readonly fileManager;
25
+ /**
26
+ * Logger instance for glob operations
27
+ */
28
+ private readonly logger;
29
+ /**
30
+ * Create a new GlobMatcher instance
31
+ *
32
+ * @param fileManager - FileManager instance for file operations
33
+ * @param logger - Pino logger instance for logging glob operations
34
+ * @example
35
+ * const logger = createLogger({ namespace: 'services:file-system:glob-matcher' })
36
+ * const fileManager = new FileManager(logger)
37
+ * const globMatcher = new GlobMatcher(fileManager, logger)
38
+ */
39
+ constructor(fileManager: FileManager, logger: pino.Logger);
40
+ /**
41
+ * Expand glob pattern to list of matching file paths
42
+ *
43
+ * Supports wildcards:
44
+ * - * matches zero or more characters (e.g., AUTH-* matches AUTH-1.md, AUTH-feature.md)
45
+ * - ? matches exactly one character (e.g., file?.txt matches file1.txt, fileA.txt)
46
+ * - ** matches directories recursively (e.g., docs/** /AUTH-* matches files in any subdirectory)
47
+ *
48
+ * Results are filtered to only include existing files and sorted alphabetically (case-insensitive).
49
+ *
50
+ * @param pattern - Glob pattern with wildcards (* and ?)
51
+ * @returns Array of matching file paths, sorted alphabetically (empty array if no matches)
52
+ * @throws {FileSystemError} If pattern expansion fails
53
+ * @example
54
+ * // Simple wildcard
55
+ * const files = await globMatcher.expandPattern('docs/stories/*.md')
56
+ *
57
+ * // Multiple wildcards
58
+ * const files = await globMatcher.expandPattern('docs/* /AUTH-*.md')
59
+ *
60
+ * // Question mark wildcard
61
+ * const files = await globMatcher.expandPattern('file?.txt')
62
+ */
63
+ expandPattern(pattern: string): Promise<string[]>;
64
+ /**
65
+ * Filter paths to only include existing files
66
+ *
67
+ * Uses FileManager to check file existence for each path.
68
+ * This provides a double-check beyond fast-glob's results.
69
+ *
70
+ * @param paths - Array of file paths to filter
71
+ * @returns Array of paths that exist
72
+ * @private
73
+ */
74
+ private filterExisting;
75
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * GlobMatcher Service
3
+ *
4
+ * Provides glob pattern matching functionality with wildcard support.
5
+ * Enables flexible file pattern matching for story and epic file selection.
6
+ */
7
+ import fg from 'fast-glob';
8
+ import { FileSystemError } from '../../utils/errors.js';
9
+ /**
10
+ * GlobMatcher service for file pattern matching
11
+ *
12
+ * Expands glob patterns (with * and ? wildcards) to lists of matching files.
13
+ * All results are filtered to existing files only and sorted alphabetically.
14
+ *
15
+ * @example
16
+ * const globMatcher = new GlobMatcher(fileManager, logger)
17
+ * const files = await globMatcher.expandPattern('docs/stories/AUTH-*.md')
18
+ * // Returns: ['docs/stories/AUTH-1.md', 'docs/stories/AUTH-2.md']
19
+ */
20
+ export class GlobMatcher {
21
+ /**
22
+ * FileManager instance for file existence checks
23
+ */
24
+ fileManager;
25
+ /**
26
+ * Logger instance for glob operations
27
+ */
28
+ logger;
29
+ /**
30
+ * Create a new GlobMatcher instance
31
+ *
32
+ * @param fileManager - FileManager instance for file operations
33
+ * @param logger - Pino logger instance for logging glob operations
34
+ * @example
35
+ * const logger = createLogger({ namespace: 'services:file-system:glob-matcher' })
36
+ * const fileManager = new FileManager(logger)
37
+ * const globMatcher = new GlobMatcher(fileManager, logger)
38
+ */
39
+ constructor(fileManager, logger) {
40
+ this.fileManager = fileManager;
41
+ this.logger = logger;
42
+ }
43
+ /**
44
+ * Expand glob pattern to list of matching file paths
45
+ *
46
+ * Supports wildcards:
47
+ * - * matches zero or more characters (e.g., AUTH-* matches AUTH-1.md, AUTH-feature.md)
48
+ * - ? matches exactly one character (e.g., file?.txt matches file1.txt, fileA.txt)
49
+ * - ** matches directories recursively (e.g., docs/** /AUTH-* matches files in any subdirectory)
50
+ *
51
+ * Results are filtered to only include existing files and sorted alphabetically (case-insensitive).
52
+ *
53
+ * @param pattern - Glob pattern with wildcards (* and ?)
54
+ * @returns Array of matching file paths, sorted alphabetically (empty array if no matches)
55
+ * @throws {FileSystemError} If pattern expansion fails
56
+ * @example
57
+ * // Simple wildcard
58
+ * const files = await globMatcher.expandPattern('docs/stories/*.md')
59
+ *
60
+ * // Multiple wildcards
61
+ * const files = await globMatcher.expandPattern('docs/* /AUTH-*.md')
62
+ *
63
+ * // Question mark wildcard
64
+ * const files = await globMatcher.expandPattern('file?.txt')
65
+ */
66
+ async expandPattern(pattern) {
67
+ this.logger.debug({ operation: 'expandPattern', pattern }, 'Expanding glob pattern');
68
+ // Validate pattern is not empty
69
+ if (!pattern || pattern.trim().length === 0) {
70
+ this.logger.error({ operation: 'expandPattern', pattern }, 'Empty glob pattern provided');
71
+ throw new FileSystemError('Glob pattern cannot be empty', {
72
+ operation: 'expandPattern',
73
+ pattern,
74
+ });
75
+ }
76
+ try {
77
+ // Expand glob pattern using fast-glob
78
+ const matches = await fg(pattern, {
79
+ absolute: false, // Return relative paths
80
+ dot: false, // Don't match dotfiles
81
+ ignore: ['node_modules/**', '.git/**', 'dist/**', 'coverage/**'], // Ignore common directories
82
+ onlyFiles: true, // Only return files, not directories
83
+ });
84
+ this.logger.debug({ matchCount: matches.length, operation: 'expandPattern', pattern }, 'Fast-glob expansion complete');
85
+ // Filter to only existing files (double-check with FileManager)
86
+ const existing = await this.filterExisting(matches);
87
+ // Sort alphabetically (case-insensitive)
88
+ const sorted = existing.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
89
+ this.logger.info({ existingCount: sorted.length, matchCount: matches.length, operation: 'expandPattern', pattern }, 'Glob pattern expansion complete');
90
+ return sorted;
91
+ }
92
+ catch (error) {
93
+ const err = error;
94
+ // Check if it's already a FileSystemError
95
+ if (err instanceof FileSystemError) {
96
+ throw err;
97
+ }
98
+ this.logger.error({ error: err.message, operation: 'expandPattern', pattern, stack: err.stack }, 'Error expanding glob pattern');
99
+ throw new FileSystemError(`Failed to expand glob pattern: ${pattern}: ${err.message}`, {
100
+ operation: 'expandPattern',
101
+ originalError: err.message,
102
+ pattern,
103
+ });
104
+ }
105
+ }
106
+ /**
107
+ * Filter paths to only include existing files
108
+ *
109
+ * Uses FileManager to check file existence for each path.
110
+ * This provides a double-check beyond fast-glob's results.
111
+ *
112
+ * @param paths - Array of file paths to filter
113
+ * @returns Array of paths that exist
114
+ * @private
115
+ */
116
+ async filterExisting(paths) {
117
+ this.logger.debug({ operation: 'filterExisting', pathCount: paths.length }, 'Filtering to existing files');
118
+ const checks = await Promise.all(paths.map(async (path) => ({
119
+ exists: await this.fileManager.fileExists(path),
120
+ path,
121
+ })));
122
+ const existing = checks.filter((check) => check.exists).map((check) => check.path);
123
+ this.logger.debug({ existingCount: existing.length, operation: 'filterExisting', pathCount: paths.length }, 'File existence filtering complete');
124
+ return existing;
125
+ }
126
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * PathResolver Service
3
+ *
4
+ * Resolves and validates file paths from core-config.yaml configuration.
5
+ * All commands use this service to get correct directories for epics, stories, and PRD files.
6
+ */
7
+ import type pino from 'pino';
8
+ import { FileManager } from './file-manager.js';
9
+ /**
10
+ * PathResolver service for resolving and validating file paths
11
+ *
12
+ * Loads configuration from .bmad-core/core-config.yaml and provides methods
13
+ * to get correct directories for epics, stories, QA, and PRD files. All paths
14
+ * are resolved relative to the project root and validated for existence.
15
+ */
16
+ export declare class PathResolver {
17
+ /**
18
+ * Cached configuration to avoid repeated file reads
19
+ */
20
+ private cachedConfig;
21
+ /**
22
+ * Cached resolved paths
23
+ */
24
+ private cachedPaths;
25
+ /**
26
+ * FileManager instance for file operations
27
+ */
28
+ private readonly fileManager;
29
+ /**
30
+ * Logger instance for path resolution operations
31
+ */
32
+ private readonly logger;
33
+ /**
34
+ * Project root directory (current working directory)
35
+ */
36
+ private readonly projectRoot;
37
+ /**
38
+ * Create a new PathResolver instance
39
+ *
40
+ * @param fileManager - FileManager instance for file operations
41
+ * @param logger - Pino logger instance for logging path operations
42
+ * @example
43
+ * const logger = createLogger({ namespace: 'services:path-resolver' })
44
+ * const fileManager = new FileManager(logger)
45
+ * const pathResolver = new PathResolver(fileManager, logger)
46
+ */
47
+ constructor(fileManager: FileManager, logger: pino.Logger);
48
+ /**
49
+ * Get all story directories for existence checks
50
+ *
51
+ * Returns an array of all story directories to check when determining
52
+ * if a story already exists. This includes:
53
+ * - docs/stories (dev stories)
54
+ * - docs/qa/stories (QA stories)
55
+ * - docs/done/stories (completed stories)
56
+ *
57
+ * @returns Array of absolute paths to story directories
58
+ * @example
59
+ * const allDirs = pathResolver.getAllStoryDirs()
60
+ * // Returns: ['/path/to/project/docs/stories', '/path/to/project/docs/qa/stories', '/path/to/project/docs/done/stories']
61
+ */
62
+ getAllStoryDirs(): string[];
63
+ /**
64
+ * Get the configuration file path
65
+ *
66
+ * Returns the absolute path to .bmad-core/core-config.yaml
67
+ *
68
+ * @returns Absolute path to configuration file
69
+ * @example
70
+ * const configPath = pathResolver.getConfigPath()
71
+ * // Returns: '/path/to/project/.bmad-core/core-config.yaml'
72
+ */
73
+ getConfigPath(): string;
74
+ /**
75
+ * Get the Done story directory path
76
+ *
77
+ * Returns the docs/done/stories directory for completed stories.
78
+ *
79
+ * @returns Absolute path to Done story directory
80
+ * @example
81
+ * const doneStoryDir = pathResolver.getDoneStoryDir()
82
+ * // Returns: '/path/to/project/docs/done/stories'
83
+ */
84
+ getDoneStoryDir(): string;
85
+ /**
86
+ * Get the epic directory path
87
+ *
88
+ * Derives epic directory from story location or explicit configuration.
89
+ * By convention, epics are in docs/epics if stories are in docs/stories.
90
+ *
91
+ * @returns Absolute path to epic directory
92
+ * @throws {FileSystemError} If epic directory does not exist
93
+ * @example
94
+ * const epicDir = pathResolver.getEpicDir()
95
+ * // Returns: '/path/to/project/docs/epics'
96
+ */
97
+ getEpicDir(): string;
98
+ /**
99
+ * Get the PRD file path
100
+ *
101
+ * Returns the configured PRD file path from core-config.yaml.
102
+ *
103
+ * @returns Absolute path to PRD file
104
+ * @example
105
+ * const prdFile = pathResolver.getPrdFile()
106
+ * // Returns: '/path/to/project/docs/prd.md'
107
+ */
108
+ getPrdFile(): string;
109
+ /**
110
+ * Get the QA story directory path
111
+ *
112
+ * Returns the configured QA location plus /stories subdirectory.
113
+ *
114
+ * @returns Absolute path to QA story directory
115
+ * @throws {FileSystemError} If QA story directory does not exist
116
+ * @example
117
+ * const qaStoryDir = pathResolver.getQaStoryDir()
118
+ * // Returns: '/path/to/project/docs/qa/stories'
119
+ */
120
+ getQaStoryDir(): string;
121
+ /**
122
+ * Get the story directory path
123
+ *
124
+ * Returns the configured story location from core-config.yaml.
125
+ *
126
+ * @returns Absolute path to story directory
127
+ * @throws {FileSystemError} If story directory does not exist
128
+ * @example
129
+ * const storyDir = pathResolver.getStoryDir()
130
+ * // Returns: '/path/to/project/docs/stories'
131
+ */
132
+ getStoryDir(): string;
133
+ /**
134
+ * Find configuration file by searching up the directory tree
135
+ *
136
+ * Searches for .bmad-core/core-config.yaml starting from project root
137
+ * and walking up to 5 parent directories.
138
+ *
139
+ * @returns Path to found config file, or null if not found
140
+ */
141
+ private findConfigFile;
142
+ /**
143
+ * Get resolved paths (with caching)
144
+ *
145
+ * Loads configuration and resolves all paths on first access,
146
+ * then returns cached paths on subsequent calls.
147
+ *
148
+ * @returns Resolved paths structure
149
+ */
150
+ private getResolvedPaths;
151
+ /**
152
+ * Load configuration from .bmad-core/core-config.yaml
153
+ *
154
+ * Reads and parses YAML configuration file, validates structure,
155
+ * and caches the result for subsequent calls. Searches up to 5 parent
156
+ * directories to find the config file.
157
+ *
158
+ * @returns Parsed and validated configuration
159
+ * @throws {FileSystemError} If configuration file is missing
160
+ * @throws {ConfigurationError} If configuration structure is invalid
161
+ */
162
+ private loadConfig;
163
+ /**
164
+ * Validate configuration structure
165
+ *
166
+ * Checks that all required fields are present and have correct types.
167
+ *
168
+ * @param config - Configuration object to validate
169
+ * @throws {ConfigurationError} If configuration is invalid
170
+ */
171
+ private validateConfig;
172
+ /**
173
+ * Validate that a directory exists (synchronous)
174
+ *
175
+ * Checks directory existence and throws error if not found.
176
+ * Uses synchronous fs check for initialization validation.
177
+ *
178
+ * @param dirPath - Absolute path to directory
179
+ * @param name - Human-readable directory name for error messages
180
+ * @throws {FileSystemError} If directory does not exist
181
+ */
182
+ private validateDirectorySync;
183
+ }