@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,350 @@
1
+ /**
2
+ * GeminiAgentRunner Service
3
+ *
4
+ * Encapsulates Gemini CLI process spawning with comprehensive error handling,
5
+ * logging, and timeout management for reliable AI agent execution.
6
+ *
7
+ * Key difference from Claude: Gemini CLI doesn't support @file references natively,
8
+ * so this runner preprocesses prompts to inline file content before execution.
9
+ */
10
+ import { exec } from 'node:child_process';
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { resolve } from 'node:path';
13
+ import { promisify } from 'node:util';
14
+ const execAsync = promisify(exec);
15
+ import { PROVIDER_CONFIGS } from '../../models/provider.js';
16
+ /**
17
+ * Track active child processes for cleanup on SIGINT
18
+ */
19
+ const activeProcesses = new Set();
20
+ /**
21
+ * Track if SIGINT handler has been registered
22
+ */
23
+ let sigintHandlerRegistered = false;
24
+ /**
25
+ * Register SIGINT handler for graceful process cleanup
26
+ */
27
+ const registerSigintHandler = (logger) => {
28
+ if (sigintHandlerRegistered)
29
+ return;
30
+ process.on('SIGINT', () => {
31
+ logger.info({
32
+ activeProcessCount: activeProcesses.size,
33
+ }, 'Received SIGINT, cleaning up active Gemini processes');
34
+ // Kill all active child processes
35
+ for (const childProcess of activeProcesses) {
36
+ try {
37
+ childProcess.kill('SIGTERM');
38
+ }
39
+ catch (error) {
40
+ logger.error({
41
+ error: error.message,
42
+ }, 'Error killing child process');
43
+ }
44
+ }
45
+ // Clear the set
46
+ activeProcesses.clear();
47
+ // Exit the main process
48
+ process.exit(130); // 130 = 128 + SIGINT signal number (2)
49
+ });
50
+ sigintHandlerRegistered = true;
51
+ };
52
+ /**
53
+ * Regex to match @file references in prompts
54
+ * Matches: @path/to/file.md, @./relative/path.ts, @../parent/file.yaml
55
+ */
56
+ const FILE_REFERENCE_REGEX = /@(\.{0,2}[\w./-]+\.\w+)/g;
57
+ /**
58
+ * GeminiAgentRunner service for executing Gemini AI agents
59
+ *
60
+ * Spawns Gemini CLI processes to execute AI agents with specified prompts.
61
+ * Preprocesses @file references to inline file content since Gemini CLI
62
+ * doesn't support native file references like Claude.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const logger = createLogger({ namespace: 'agent-runner' })
67
+ * const runner = new GeminiAgentRunner(logger)
68
+ * const result = await runner.runAgent({
69
+ * prompt: '@.bmad-core/agents/architect.md Create epic for user auth',
70
+ * agentType: 'architect',
71
+ * timeout: 300000
72
+ * })
73
+ * ```
74
+ */
75
+ export class GeminiAgentRunner {
76
+ provider = 'gemini';
77
+ config = PROVIDER_CONFIGS.gemini;
78
+ logger;
79
+ /**
80
+ * Create a new GeminiAgentRunner instance
81
+ *
82
+ * @param logger - Logger instance for structured logging
83
+ */
84
+ constructor(logger) {
85
+ this.logger = logger;
86
+ registerSigintHandler(logger);
87
+ }
88
+ /**
89
+ * Get the count of active processes
90
+ * (Useful for testing and monitoring purposes)
91
+ *
92
+ * @returns Number of currently active child processes
93
+ */
94
+ getActiveProcessCount() {
95
+ return activeProcesses.size;
96
+ }
97
+ /**
98
+ * Execute a Gemini AI agent with the specified prompt and options
99
+ *
100
+ * This method spawns a Gemini CLI process, captures output, handles errors,
101
+ * and enforces timeouts. It preprocesses @file references to inline content
102
+ * since Gemini CLI doesn't support native file references.
103
+ *
104
+ * @param prompt - The prompt to execute with the agent
105
+ * @param options - Agent execution options (without prompt)
106
+ * @returns AgentResult with success status, output, errors, and metadata
107
+ */
108
+ async runAgent(prompt, options) {
109
+ const startTime = Date.now();
110
+ const timeout = options.timeout ?? 1_800_000; // Default 30 minutes
111
+ // Input validation
112
+ if (!prompt || prompt.trim().length === 0) {
113
+ return this.returnWithCallback({
114
+ agentType: options.agentType,
115
+ duration: Date.now() - startTime,
116
+ errors: 'Prompt is required and cannot be empty',
117
+ exitCode: -1,
118
+ output: '',
119
+ success: false,
120
+ }, options.onResponse);
121
+ }
122
+ // Preprocess prompt to inline @file references
123
+ const processedPrompt = this.preprocessPrompt(prompt);
124
+ // Log execution start with metadata
125
+ this.logger.info({
126
+ agentType: options.agentType,
127
+ originalPromptLength: prompt.length,
128
+ processedPromptLength: processedPrompt.length,
129
+ references: options.references?.length ?? 0,
130
+ timeout,
131
+ }, 'Executing Gemini agent');
132
+ // Invoke onPrompt callback if provided
133
+ if (options.onPrompt) {
134
+ try {
135
+ // Create options object without callbacks for the callback
136
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
137
+ const { onPrompt, onResponse, ...callbackOptions } = options;
138
+ await onPrompt(processedPrompt, callbackOptions);
139
+ }
140
+ catch (error) {
141
+ this.logger.warn({ error: error.message }, 'Error in onPrompt callback');
142
+ }
143
+ }
144
+ // Build full command string
145
+ // Escape double quotes and backticks in the prompt
146
+ const escapedPrompt = processedPrompt
147
+ .replaceAll('\\', String.raw `\\`)
148
+ .replaceAll('"', String.raw `\"`)
149
+ .replaceAll('`', String.raw `\``)
150
+ .replaceAll('$', String.raw `\$`);
151
+ // Build command with provider config flags
152
+ // Gemini uses -p flag which expects the prompt as the next argument
153
+ const command = `${this.config.command} -p "${escapedPrompt}" --yolo < /dev/null`;
154
+ // Log the command being executed (truncated for readability)
155
+ this.logger.info({
156
+ commandLength: command.length,
157
+ promptLength: processedPrompt.length,
158
+ }, 'Executing Gemini command');
159
+ let stdoutData = '';
160
+ let stderrData = '';
161
+ try {
162
+ // Use exec instead of spawn for better shell compatibility
163
+ const { stderr, stdout } = await execAsync(command, {
164
+ env: process.env,
165
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
166
+ shell: process.env.SHELL || '/bin/bash',
167
+ timeout,
168
+ });
169
+ stdoutData = stdout;
170
+ stderrData = stderr;
171
+ const duration = Date.now() - startTime;
172
+ this.logger.info({
173
+ duration,
174
+ stderrLength: stderrData.length,
175
+ stdoutLength: stdoutData.length,
176
+ }, 'Gemini CLI process completed successfully');
177
+ const result = {
178
+ agentType: options.agentType,
179
+ duration,
180
+ errors: stderrData,
181
+ exitCode: 0,
182
+ output: stdoutData,
183
+ success: true,
184
+ };
185
+ return this.returnWithCallback(result, options.onResponse);
186
+ }
187
+ catch (error) {
188
+ // Handle exec errors (includes timeout, non-zero exit, etc.)
189
+ const duration = Date.now() - startTime;
190
+ // Extract stdout/stderr from exec error if available
191
+ const execError = error;
192
+ if (execError.stdout)
193
+ stdoutData = execError.stdout;
194
+ if (execError.stderr)
195
+ stderrData = execError.stderr;
196
+ const exitCode = execError.code ?? -1;
197
+ const isTimeout = execError.killed === true && execError.signal === 'SIGTERM';
198
+ const wasKilled = execError.killed === true;
199
+ const signal = execError.signal ?? null;
200
+ // Build detailed error context for debugging
201
+ const errorContext = this.buildErrorContext({
202
+ cmd: execError.cmd,
203
+ duration,
204
+ exitCode,
205
+ isTimeout,
206
+ signal,
207
+ stderrData,
208
+ stdoutData,
209
+ wasKilled,
210
+ });
211
+ this.logger.error({
212
+ cmd: execError.cmd,
213
+ duration,
214
+ error: execError.message,
215
+ exitCode,
216
+ isTimeout,
217
+ signal,
218
+ stderrLength: stderrData.length,
219
+ stdoutLength: stdoutData.length,
220
+ wasKilled,
221
+ }, isTimeout ? 'Gemini CLI process timeout' : 'Gemini CLI process error');
222
+ return this.returnWithCallback({
223
+ agentType: options.agentType,
224
+ duration,
225
+ errors: errorContext,
226
+ exitCode: isTimeout ? 124 : exitCode,
227
+ output: stdoutData,
228
+ success: false,
229
+ }, options.onResponse);
230
+ }
231
+ }
232
+ /**
233
+ * Build detailed error context for debugging process failures
234
+ * @private
235
+ */
236
+ buildErrorContext(info) {
237
+ const lines = [];
238
+ // Determine the type of failure
239
+ if (info.isTimeout) {
240
+ lines.push(`Process timeout after ${info.duration}ms`);
241
+ }
242
+ else if (info.wasKilled) {
243
+ lines.push('Process was killed externally');
244
+ // Explain common exit codes
245
+ switch (info.exitCode) {
246
+ case 130: {
247
+ lines.push('Exit code 130 = SIGINT (user interrupt, Ctrl+C)');
248
+ break;
249
+ }
250
+ case 137: {
251
+ lines.push('Exit code 137 = SIGKILL (process forcefully killed)', 'Possible causes:', ' - System OOM killer', ' - Exceeded memory limits', ' - Docker/container killed the process');
252
+ break;
253
+ }
254
+ case 143: {
255
+ lines.push('Exit code 143 = SIGTERM (process terminated)', 'Possible causes:', ' - System OOM killer terminated the process', ' - Another process sent SIGTERM', ' - Parent process cleanup', ' - Docker/container resource limits');
256
+ break;
257
+ }
258
+ // No default
259
+ }
260
+ if (info.signal) {
261
+ lines.push(`Signal received: ${info.signal}`);
262
+ }
263
+ else {
264
+ lines.push('No signal information available (signal: null)');
265
+ }
266
+ }
267
+ else {
268
+ lines.push(`Process exited with code ${info.exitCode}`);
269
+ }
270
+ // Add duration context
271
+ lines.push(`Duration: ${(info.duration / 1000).toFixed(2)}s`);
272
+ // Add output context
273
+ if (info.stdoutData.length > 0) {
274
+ lines.push(`Stdout (${info.stdoutData.length} chars):`);
275
+ // Truncate if too long, show last 500 chars which are often most relevant
276
+ const truncatedStdout = info.stdoutData.length > 500 ? `...[truncated]...\n${info.stdoutData.slice(-500)}` : info.stdoutData;
277
+ lines.push(truncatedStdout);
278
+ }
279
+ else {
280
+ lines.push('Stdout: (empty - no output captured)');
281
+ }
282
+ if (info.stderrData.length > 0) {
283
+ lines.push(`Stderr (${info.stderrData.length} chars):`);
284
+ const truncatedStderr = info.stderrData.length > 500 ? `...[truncated]...\n${info.stderrData.slice(-500)}` : info.stderrData;
285
+ lines.push(truncatedStderr);
286
+ }
287
+ else {
288
+ lines.push('Stderr: (empty - no error output)');
289
+ }
290
+ // Add command for reference (truncated if too long)
291
+ if (info.cmd) {
292
+ const truncatedCmd = info.cmd.length > 200 ? `${info.cmd.slice(0, 200)}...[truncated]` : info.cmd;
293
+ lines.push(`Command: ${truncatedCmd}`);
294
+ }
295
+ // Add troubleshooting suggestions for empty output
296
+ if (info.stdoutData.length === 0 && info.stderrData.length === 0) {
297
+ lines.push('', 'Troubleshooting (no output captured):', ' 1. Check system resources (memory, CPU)', ' 2. Verify Gemini CLI is properly installed: `gemini --version`', ' 3. Check for Gemini API issues', ' 4. Review system logs: `dmesg | tail -50` (for OOM killer)', ' 5. Try running the command manually to see output');
298
+ }
299
+ return lines.join('\n');
300
+ }
301
+ /**
302
+ * Preprocess prompt to inline @file references
303
+ *
304
+ * Since Gemini CLI doesn't support @file syntax natively, this method
305
+ * finds all @file references and replaces them with the actual file content.
306
+ *
307
+ * @param prompt - The original prompt with @file references
308
+ * @param basePath - Base path for resolving relative file paths
309
+ * @returns Preprocessed prompt with inlined file content
310
+ */
311
+ preprocessPrompt(prompt, basePath = process.cwd()) {
312
+ const inlinedFiles = [];
313
+ const processedPrompt = prompt.replaceAll(FILE_REFERENCE_REGEX, (match, filePath) => {
314
+ const absolutePath = resolve(basePath, filePath);
315
+ if (!existsSync(absolutePath)) {
316
+ this.logger.warn({ absolutePath, filePath }, 'File reference not found, keeping original reference');
317
+ return match; // Keep original @file reference if file doesn't exist
318
+ }
319
+ try {
320
+ const content = readFileSync(absolutePath, 'utf8');
321
+ inlinedFiles.push(filePath);
322
+ // Return the file content wrapped in a clear delimiter
323
+ return `\n--- BEGIN FILE: ${filePath} ---\n${content}\n--- END FILE: ${filePath} ---\n`;
324
+ }
325
+ catch (error) {
326
+ this.logger.error({ absolutePath, error: error.message, filePath }, 'Error reading file for inlining');
327
+ return match; // Keep original reference on error
328
+ }
329
+ });
330
+ if (inlinedFiles.length > 0) {
331
+ this.logger.info({ fileCount: inlinedFiles.length, files: inlinedFiles }, 'Inlined file references for Gemini CLI');
332
+ }
333
+ return processedPrompt;
334
+ }
335
+ /**
336
+ * Invoke onResponse callback and return result
337
+ * @private
338
+ */
339
+ async returnWithCallback(result, onResponse) {
340
+ if (onResponse) {
341
+ try {
342
+ await onResponse(result);
343
+ }
344
+ catch (error) {
345
+ this.logger.warn({ error: error.message }, 'Error in onResponse callback');
346
+ }
347
+ }
348
+ return result;
349
+ }
350
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Agent Services Exports
3
+ */
4
+ export * from './agent-runner-factory.js';
5
+ export * from './agent-runner.js';
6
+ export * from './claude-agent-runner.js';
7
+ export * from './gemini-agent-runner.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Agent Services Exports
3
+ */
4
+ export * from './agent-runner-factory.js';
5
+ export * from './agent-runner.js';
6
+ export * from './claude-agent-runner.js';
7
+ export * from './gemini-agent-runner.js';
@@ -0,0 +1,110 @@
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 type pino from 'pino';
8
+ /**
9
+ * FileManager service for all file system operations
10
+ *
11
+ * Abstracts file system operations behind a consistent interface with
12
+ * comprehensive error handling and logging. All methods use async/await
13
+ * for consistency.
14
+ */
15
+ export declare class FileManager {
16
+ /**
17
+ * Logger instance for file operations
18
+ */
19
+ private readonly logger;
20
+ /**
21
+ * Create a new FileManager instance
22
+ *
23
+ * @param logger - Pino logger instance for logging file operations
24
+ * @example
25
+ * const logger = createLogger({ namespace: 'services:file-system' })
26
+ * const fileManager = new FileManager(logger)
27
+ */
28
+ constructor(logger: pino.Logger);
29
+ /**
30
+ * Create a directory (and parent directories if needed)
31
+ *
32
+ * Uses ensureDir which is idempotent - safe to call if directory already exists.
33
+ *
34
+ * @param path - Path to the directory to create
35
+ * @throws {FileSystemError} If directory cannot be created (permission denied, invalid path, etc.)
36
+ * @example
37
+ * await fileManager.createDirectory('docs/epics')
38
+ */
39
+ createDirectory(path: string): Promise<void>;
40
+ /**
41
+ * Check if a file or directory exists
42
+ *
43
+ * @param path - Path to check for existence
44
+ * @returns True if path exists, false otherwise
45
+ * @example
46
+ * const exists = await fileManager.fileExists('docs/prd.md')
47
+ * if (!exists) {
48
+ * throw new Error('PRD not found')
49
+ * }
50
+ */
51
+ fileExists(path: string): Promise<boolean>;
52
+ /**
53
+ * List files in a directory matching an optional pattern
54
+ *
55
+ * @param directory - Directory to list files from
56
+ * @param pattern - Optional glob pattern to filter files (e.g., '*.md', 'STORY-*.md')
57
+ * @returns Array of file paths matching the pattern
58
+ * @throws {FileSystemError} If directory cannot be read
59
+ * @example
60
+ * const storyFiles = await fileManager.listFiles('docs/stories', 'STORY-*.md')
61
+ */
62
+ listFiles(directory: string, pattern?: string): Promise<string[]>;
63
+ /**
64
+ * Move a file from source to destination
65
+ *
66
+ * Creates destination directory if it doesn't exist.
67
+ * Overwrites destination file if it exists.
68
+ *
69
+ * @param source - Source file path
70
+ * @param dest - Destination file path
71
+ * @throws {FileSystemError} If file cannot be moved (source not found, permission denied, etc.)
72
+ * @example
73
+ * await fileManager.moveFile('docs/stories/1.1-story.md', 'docs/qa/stories/1.1-story.md')
74
+ */
75
+ moveFile(source: string, dest: string): Promise<void>;
76
+ /**
77
+ * Read file contents as UTF-8 string
78
+ *
79
+ * @param path - Path to the file to read
80
+ * @returns File content as string
81
+ * @throws {FileSystemError} If file cannot be read (not found, permission denied, etc.)
82
+ * @example
83
+ * const content = await fileManager.readFile('docs/prd.md')
84
+ */
85
+ readFile(path: string): Promise<string>;
86
+ /**
87
+ * Write content to file as UTF-8
88
+ *
89
+ * Creates parent directories if they don't exist.
90
+ * Overwrites existing file if present.
91
+ *
92
+ * @param path - Path to the file to write
93
+ * @param content - Content to write to file
94
+ * @throws {FileSystemError} If file cannot be written (permission denied, invalid path, etc.)
95
+ * @example
96
+ * await fileManager.writeFile('docs/epics/epic-1.md', epicContent)
97
+ */
98
+ writeFile(path: string, content: string): Promise<void>;
99
+ /**
100
+ * Match a file name against a simple glob pattern
101
+ *
102
+ * Supports * wildcard only (e.g., 'STORY-*.md', '*.txt')
103
+ *
104
+ * @param fileName - File name to check
105
+ * @param pattern - Glob pattern with * wildcards
106
+ * @returns True if file matches pattern
107
+ * @private
108
+ */
109
+ private matchesPattern;
110
+ }