@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.
- package/LICENSE +21 -0
- package/README.md +1017 -0
- package/bin/dev +5 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/config/show.d.ts +34 -0
- package/dist/commands/config/show.js +108 -0
- package/dist/commands/config/validate.d.ts +29 -0
- package/dist/commands/config/validate.js +131 -0
- package/dist/commands/decompose.d.ts +79 -0
- package/dist/commands/decompose.js +327 -0
- package/dist/commands/demo.d.ts +18 -0
- package/dist/commands/demo.js +107 -0
- package/dist/commands/epics/create.d.ts +123 -0
- package/dist/commands/epics/create.js +459 -0
- package/dist/commands/epics/list.d.ts +120 -0
- package/dist/commands/epics/list.js +280 -0
- package/dist/commands/hello/index.d.ts +12 -0
- package/dist/commands/hello/index.js +34 -0
- package/dist/commands/hello/world.d.ts +8 -0
- package/dist/commands/hello/world.js +24 -0
- package/dist/commands/prd/fix.d.ts +39 -0
- package/dist/commands/prd/fix.js +140 -0
- package/dist/commands/prd/validate.d.ts +112 -0
- package/dist/commands/prd/validate.js +302 -0
- package/dist/commands/stories/create.d.ts +95 -0
- package/dist/commands/stories/create.js +431 -0
- package/dist/commands/stories/develop.d.ts +91 -0
- package/dist/commands/stories/develop.js +460 -0
- package/dist/commands/stories/list.d.ts +84 -0
- package/dist/commands/stories/list.js +291 -0
- package/dist/commands/stories/move.d.ts +66 -0
- package/dist/commands/stories/move.js +273 -0
- package/dist/commands/stories/qa.d.ts +99 -0
- package/dist/commands/stories/qa.js +530 -0
- package/dist/commands/workflow.d.ts +97 -0
- package/dist/commands/workflow.js +390 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/agent-options.d.ts +50 -0
- package/dist/models/agent-options.js +1 -0
- package/dist/models/agent-result.d.ts +29 -0
- package/dist/models/agent-result.js +1 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.js +10 -0
- package/dist/models/phase-result.d.ts +65 -0
- package/dist/models/phase-result.js +7 -0
- package/dist/models/provider.d.ts +28 -0
- package/dist/models/provider.js +18 -0
- package/dist/models/story.d.ts +154 -0
- package/dist/models/story.js +18 -0
- package/dist/models/workflow-config.d.ts +148 -0
- package/dist/models/workflow-config.js +1 -0
- package/dist/models/workflow-result.d.ts +164 -0
- package/dist/models/workflow-result.js +7 -0
- package/dist/services/agents/agent-runner-factory.d.ts +31 -0
- package/dist/services/agents/agent-runner-factory.js +44 -0
- package/dist/services/agents/agent-runner.d.ts +46 -0
- package/dist/services/agents/agent-runner.js +29 -0
- package/dist/services/agents/claude-agent-runner.d.ts +81 -0
- package/dist/services/agents/claude-agent-runner.js +332 -0
- package/dist/services/agents/gemini-agent-runner.d.ts +82 -0
- package/dist/services/agents/gemini-agent-runner.js +350 -0
- package/dist/services/agents/index.d.ts +7 -0
- package/dist/services/agents/index.js +7 -0
- package/dist/services/file-system/file-manager.d.ts +110 -0
- package/dist/services/file-system/file-manager.js +223 -0
- package/dist/services/file-system/glob-matcher.d.ts +75 -0
- package/dist/services/file-system/glob-matcher.js +126 -0
- package/dist/services/file-system/path-resolver.d.ts +183 -0
- package/dist/services/file-system/path-resolver.js +400 -0
- package/dist/services/logging/workflow-logger.d.ts +232 -0
- package/dist/services/logging/workflow-logger.js +552 -0
- package/dist/services/orchestration/batch-processor.d.ts +113 -0
- package/dist/services/orchestration/batch-processor.js +187 -0
- package/dist/services/orchestration/dependency-graph-executor.d.ts +60 -0
- package/dist/services/orchestration/dependency-graph-executor.js +447 -0
- package/dist/services/orchestration/index.d.ts +10 -0
- package/dist/services/orchestration/index.js +8 -0
- package/dist/services/orchestration/input-detector.d.ts +125 -0
- package/dist/services/orchestration/input-detector.js +381 -0
- package/dist/services/orchestration/story-queue.d.ts +94 -0
- package/dist/services/orchestration/story-queue.js +170 -0
- package/dist/services/orchestration/story-type-detector.d.ts +80 -0
- package/dist/services/orchestration/story-type-detector.js +258 -0
- package/dist/services/orchestration/task-decomposition-service.d.ts +67 -0
- package/dist/services/orchestration/task-decomposition-service.js +607 -0
- package/dist/services/orchestration/workflow-orchestrator.d.ts +659 -0
- package/dist/services/orchestration/workflow-orchestrator.js +2201 -0
- package/dist/services/parsers/epic-parser.d.ts +117 -0
- package/dist/services/parsers/epic-parser.js +264 -0
- package/dist/services/parsers/prd-fixer.d.ts +86 -0
- package/dist/services/parsers/prd-fixer.js +194 -0
- package/dist/services/parsers/prd-parser.d.ts +123 -0
- package/dist/services/parsers/prd-parser.js +286 -0
- package/dist/services/parsers/standalone-story-parser.d.ts +114 -0
- package/dist/services/parsers/standalone-story-parser.js +255 -0
- package/dist/services/parsers/story-parser-factory.d.ts +81 -0
- package/dist/services/parsers/story-parser-factory.js +108 -0
- package/dist/services/parsers/story-parser.d.ts +122 -0
- package/dist/services/parsers/story-parser.js +262 -0
- package/dist/services/scaffolding/decompose-session-scaffolder.d.ts +74 -0
- package/dist/services/scaffolding/decompose-session-scaffolder.js +315 -0
- package/dist/services/scaffolding/file-scaffolder.d.ts +94 -0
- package/dist/services/scaffolding/file-scaffolder.js +314 -0
- package/dist/services/validation/config-validator.d.ts +88 -0
- package/dist/services/validation/config-validator.js +167 -0
- package/dist/types/task-graph.d.ts +142 -0
- package/dist/types/task-graph.js +5 -0
- package/dist/utils/colors.d.ts +49 -0
- package/dist/utils/colors.js +50 -0
- package/dist/utils/error-formatter.d.ts +64 -0
- package/dist/utils/error-formatter.js +279 -0
- package/dist/utils/errors.d.ts +170 -0
- package/dist/utils/errors.js +233 -0
- package/dist/utils/formatters.d.ts +84 -0
- package/dist/utils/formatters.js +162 -0
- package/dist/utils/logger.d.ts +63 -0
- package/dist/utils/logger.js +78 -0
- package/dist/utils/progress.d.ts +104 -0
- package/dist/utils/progress.js +161 -0
- package/dist/utils/retry.d.ts +114 -0
- package/dist/utils/retry.js +160 -0
- package/dist/utils/shared-flags.d.ts +28 -0
- package/dist/utils/shared-flags.js +43 -0
- package/package.json +119 -0
|
@@ -0,0 +1,400 @@
|
|
|
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 fs from 'fs-extra';
|
|
8
|
+
import { load as yamlLoad } from 'js-yaml';
|
|
9
|
+
import { dirname, resolve } from 'node:path';
|
|
10
|
+
import { ConfigurationError, FileSystemError } from '../../utils/errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Configuration file path relative to project root
|
|
13
|
+
*/
|
|
14
|
+
const CONFIG_FILE_PATH = '.bmad-core/core-config.yaml';
|
|
15
|
+
/**
|
|
16
|
+
* PathResolver service for resolving and validating file paths
|
|
17
|
+
*
|
|
18
|
+
* Loads configuration from .bmad-core/core-config.yaml and provides methods
|
|
19
|
+
* to get correct directories for epics, stories, QA, and PRD files. All paths
|
|
20
|
+
* are resolved relative to the project root and validated for existence.
|
|
21
|
+
*/
|
|
22
|
+
export class PathResolver {
|
|
23
|
+
/**
|
|
24
|
+
* Cached configuration to avoid repeated file reads
|
|
25
|
+
*/
|
|
26
|
+
cachedConfig = null;
|
|
27
|
+
/**
|
|
28
|
+
* Cached resolved paths
|
|
29
|
+
*/
|
|
30
|
+
cachedPaths = null;
|
|
31
|
+
/**
|
|
32
|
+
* FileManager instance for file operations
|
|
33
|
+
*/
|
|
34
|
+
fileManager;
|
|
35
|
+
/**
|
|
36
|
+
* Logger instance for path resolution operations
|
|
37
|
+
*/
|
|
38
|
+
logger;
|
|
39
|
+
/**
|
|
40
|
+
* Project root directory (current working directory)
|
|
41
|
+
*/
|
|
42
|
+
projectRoot;
|
|
43
|
+
/**
|
|
44
|
+
* Create a new PathResolver instance
|
|
45
|
+
*
|
|
46
|
+
* @param fileManager - FileManager instance for file operations
|
|
47
|
+
* @param logger - Pino logger instance for logging path operations
|
|
48
|
+
* @example
|
|
49
|
+
* const logger = createLogger({ namespace: 'services:path-resolver' })
|
|
50
|
+
* const fileManager = new FileManager(logger)
|
|
51
|
+
* const pathResolver = new PathResolver(fileManager, logger)
|
|
52
|
+
*/
|
|
53
|
+
constructor(fileManager, logger) {
|
|
54
|
+
this.fileManager = fileManager;
|
|
55
|
+
this.logger = logger;
|
|
56
|
+
this.projectRoot = process.cwd();
|
|
57
|
+
this.logger.debug('PathResolver initialized with project root: %s', this.projectRoot);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get all story directories for existence checks
|
|
61
|
+
*
|
|
62
|
+
* Returns an array of all story directories to check when determining
|
|
63
|
+
* if a story already exists. This includes:
|
|
64
|
+
* - docs/stories (dev stories)
|
|
65
|
+
* - docs/qa/stories (QA stories)
|
|
66
|
+
* - docs/done/stories (completed stories)
|
|
67
|
+
*
|
|
68
|
+
* @returns Array of absolute paths to story directories
|
|
69
|
+
* @example
|
|
70
|
+
* const allDirs = pathResolver.getAllStoryDirs()
|
|
71
|
+
* // Returns: ['/path/to/project/docs/stories', '/path/to/project/docs/qa/stories', '/path/to/project/docs/done/stories']
|
|
72
|
+
*/
|
|
73
|
+
getAllStoryDirs() {
|
|
74
|
+
const paths = this.getResolvedPaths();
|
|
75
|
+
const dirs = [paths.storyDir, paths.qaStoryDir, paths.doneStoryDir];
|
|
76
|
+
this.logger.debug('Getting all story directories: %O', dirs);
|
|
77
|
+
return dirs;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the configuration file path
|
|
81
|
+
*
|
|
82
|
+
* Returns the absolute path to .bmad-core/core-config.yaml
|
|
83
|
+
*
|
|
84
|
+
* @returns Absolute path to configuration file
|
|
85
|
+
* @example
|
|
86
|
+
* const configPath = pathResolver.getConfigPath()
|
|
87
|
+
* // Returns: '/path/to/project/.bmad-core/core-config.yaml'
|
|
88
|
+
*/
|
|
89
|
+
getConfigPath() {
|
|
90
|
+
const configPath = resolve(this.projectRoot, CONFIG_FILE_PATH);
|
|
91
|
+
this.logger.debug('Getting config path: %s', configPath);
|
|
92
|
+
return configPath;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get the Done story directory path
|
|
96
|
+
*
|
|
97
|
+
* Returns the docs/done/stories directory for completed stories.
|
|
98
|
+
*
|
|
99
|
+
* @returns Absolute path to Done story directory
|
|
100
|
+
* @example
|
|
101
|
+
* const doneStoryDir = pathResolver.getDoneStoryDir()
|
|
102
|
+
* // Returns: '/path/to/project/docs/done/stories'
|
|
103
|
+
*/
|
|
104
|
+
getDoneStoryDir() {
|
|
105
|
+
const paths = this.getResolvedPaths();
|
|
106
|
+
this.logger.debug('Getting Done story directory: %s', paths.doneStoryDir);
|
|
107
|
+
return paths.doneStoryDir;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the epic directory path
|
|
111
|
+
*
|
|
112
|
+
* Derives epic directory from story location or explicit configuration.
|
|
113
|
+
* By convention, epics are in docs/epics if stories are in docs/stories.
|
|
114
|
+
*
|
|
115
|
+
* @returns Absolute path to epic directory
|
|
116
|
+
* @throws {FileSystemError} If epic directory does not exist
|
|
117
|
+
* @example
|
|
118
|
+
* const epicDir = pathResolver.getEpicDir()
|
|
119
|
+
* // Returns: '/path/to/project/docs/epics'
|
|
120
|
+
*/
|
|
121
|
+
getEpicDir() {
|
|
122
|
+
const paths = this.getResolvedPaths();
|
|
123
|
+
this.logger.debug('Getting epic directory: %s', paths.epicDir);
|
|
124
|
+
return paths.epicDir;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the PRD file path
|
|
128
|
+
*
|
|
129
|
+
* Returns the configured PRD file path from core-config.yaml.
|
|
130
|
+
*
|
|
131
|
+
* @returns Absolute path to PRD file
|
|
132
|
+
* @example
|
|
133
|
+
* const prdFile = pathResolver.getPrdFile()
|
|
134
|
+
* // Returns: '/path/to/project/docs/prd.md'
|
|
135
|
+
*/
|
|
136
|
+
getPrdFile() {
|
|
137
|
+
const paths = this.getResolvedPaths();
|
|
138
|
+
this.logger.debug('Getting PRD file: %s', paths.prdFile);
|
|
139
|
+
return paths.prdFile;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the QA story directory path
|
|
143
|
+
*
|
|
144
|
+
* Returns the configured QA location plus /stories subdirectory.
|
|
145
|
+
*
|
|
146
|
+
* @returns Absolute path to QA story directory
|
|
147
|
+
* @throws {FileSystemError} If QA story directory does not exist
|
|
148
|
+
* @example
|
|
149
|
+
* const qaStoryDir = pathResolver.getQaStoryDir()
|
|
150
|
+
* // Returns: '/path/to/project/docs/qa/stories'
|
|
151
|
+
*/
|
|
152
|
+
getQaStoryDir() {
|
|
153
|
+
const paths = this.getResolvedPaths();
|
|
154
|
+
this.logger.debug('Getting QA story directory: %s', paths.qaStoryDir);
|
|
155
|
+
return paths.qaStoryDir;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get the story directory path
|
|
159
|
+
*
|
|
160
|
+
* Returns the configured story location from core-config.yaml.
|
|
161
|
+
*
|
|
162
|
+
* @returns Absolute path to story directory
|
|
163
|
+
* @throws {FileSystemError} If story directory does not exist
|
|
164
|
+
* @example
|
|
165
|
+
* const storyDir = pathResolver.getStoryDir()
|
|
166
|
+
* // Returns: '/path/to/project/docs/stories'
|
|
167
|
+
*/
|
|
168
|
+
getStoryDir() {
|
|
169
|
+
const paths = this.getResolvedPaths();
|
|
170
|
+
this.logger.debug('Getting story directory: %s', paths.storyDir);
|
|
171
|
+
return paths.storyDir;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find configuration file by searching up the directory tree
|
|
175
|
+
*
|
|
176
|
+
* Searches for .bmad-core/core-config.yaml starting from project root
|
|
177
|
+
* and walking up to 5 parent directories.
|
|
178
|
+
*
|
|
179
|
+
* @returns Path to found config file, or null if not found
|
|
180
|
+
*/
|
|
181
|
+
findConfigFile() {
|
|
182
|
+
const MAX_LEVELS = 5;
|
|
183
|
+
let currentDir = this.projectRoot;
|
|
184
|
+
for (let i = 0; i <= MAX_LEVELS; i++) {
|
|
185
|
+
const configPath = resolve(currentDir, CONFIG_FILE_PATH);
|
|
186
|
+
this.logger.debug('Searching for config at level %d: %s', i, configPath);
|
|
187
|
+
if (fs.existsSync(configPath)) {
|
|
188
|
+
this.logger.info('Found configuration file at: %s', configPath);
|
|
189
|
+
return configPath;
|
|
190
|
+
}
|
|
191
|
+
const parentDir = dirname(currentDir);
|
|
192
|
+
// Stop if we've reached the filesystem root
|
|
193
|
+
if (parentDir === currentDir) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
currentDir = parentDir;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get resolved paths (with caching)
|
|
202
|
+
*
|
|
203
|
+
* Loads configuration and resolves all paths on first access,
|
|
204
|
+
* then returns cached paths on subsequent calls.
|
|
205
|
+
*
|
|
206
|
+
* @returns Resolved paths structure
|
|
207
|
+
*/
|
|
208
|
+
getResolvedPaths() {
|
|
209
|
+
if (this.cachedPaths) {
|
|
210
|
+
return this.cachedPaths;
|
|
211
|
+
}
|
|
212
|
+
const config = this.loadConfig();
|
|
213
|
+
// Resolve story directory
|
|
214
|
+
const storyDir = resolve(this.projectRoot, config.devStoryLocation);
|
|
215
|
+
// Derive epic directory from story directory
|
|
216
|
+
// Convention: if stories are in docs/stories, epics are in docs/epics
|
|
217
|
+
const epicDir = resolve(this.projectRoot, config.devStoryLocation.replace('stories', 'epics'));
|
|
218
|
+
// Resolve QA story directory
|
|
219
|
+
const qaLocation = config.qa?.qaLocation || 'docs/qa';
|
|
220
|
+
const qaStoryDir = resolve(this.projectRoot, qaLocation, 'stories');
|
|
221
|
+
// Resolve Done story directory
|
|
222
|
+
const doneStoryDir = resolve(this.projectRoot, 'docs/done/stories');
|
|
223
|
+
// Resolve PRD file
|
|
224
|
+
const prdFile = resolve(this.projectRoot, config.prd?.prdFile || 'docs/prd.md');
|
|
225
|
+
// Validate directories exist
|
|
226
|
+
this.validateDirectorySync(storyDir, 'story directory');
|
|
227
|
+
this.validateDirectorySync(epicDir, 'epic directory');
|
|
228
|
+
this.validateDirectorySync(qaStoryDir, 'QA story directory');
|
|
229
|
+
// Note: doneStoryDir is optional, don't validate it
|
|
230
|
+
// Stories may not have been moved to done yet
|
|
231
|
+
this.cachedPaths = {
|
|
232
|
+
doneStoryDir,
|
|
233
|
+
epicDir,
|
|
234
|
+
prdFile,
|
|
235
|
+
qaStoryDir,
|
|
236
|
+
storyDir,
|
|
237
|
+
};
|
|
238
|
+
this.logger.info({
|
|
239
|
+
doneStoryDir,
|
|
240
|
+
epicDir,
|
|
241
|
+
prdFile,
|
|
242
|
+
qaStoryDir,
|
|
243
|
+
storyDir,
|
|
244
|
+
}, 'Paths resolved successfully');
|
|
245
|
+
return this.cachedPaths;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Load configuration from .bmad-core/core-config.yaml
|
|
249
|
+
*
|
|
250
|
+
* Reads and parses YAML configuration file, validates structure,
|
|
251
|
+
* and caches the result for subsequent calls. Searches up to 5 parent
|
|
252
|
+
* directories to find the config file.
|
|
253
|
+
*
|
|
254
|
+
* @returns Parsed and validated configuration
|
|
255
|
+
* @throws {FileSystemError} If configuration file is missing
|
|
256
|
+
* @throws {ConfigurationError} If configuration structure is invalid
|
|
257
|
+
*/
|
|
258
|
+
loadConfig() {
|
|
259
|
+
if (this.cachedConfig) {
|
|
260
|
+
this.logger.debug('Using cached configuration');
|
|
261
|
+
return this.cachedConfig;
|
|
262
|
+
}
|
|
263
|
+
const configPath = this.findConfigFile();
|
|
264
|
+
if (!configPath) {
|
|
265
|
+
const searchStart = resolve(this.projectRoot, CONFIG_FILE_PATH);
|
|
266
|
+
this.logger.error('Configuration file not found after searching up to 5 parent directories');
|
|
267
|
+
throw new FileSystemError(`Configuration file not found: ${searchStart} (searched up to 5 parent directories)`, {
|
|
268
|
+
configPath: searchStart,
|
|
269
|
+
operation: 'loadConfig',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
this.logger.info('Loading configuration from: %s', configPath);
|
|
273
|
+
try {
|
|
274
|
+
// Read configuration file
|
|
275
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
276
|
+
this.logger.debug('Configuration file read successfully');
|
|
277
|
+
// Parse YAML
|
|
278
|
+
const config = yamlLoad(content);
|
|
279
|
+
// Validate required fields
|
|
280
|
+
this.validateConfig(config);
|
|
281
|
+
this.cachedConfig = config;
|
|
282
|
+
this.logger.info('Configuration loaded and validated successfully');
|
|
283
|
+
return config;
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (error instanceof FileSystemError || error instanceof ConfigurationError) {
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
const err = error;
|
|
290
|
+
this.logger.error('Error loading configuration: %O', err);
|
|
291
|
+
throw new ConfigurationError(`Failed to load configuration: ${err.message}`, {
|
|
292
|
+
configPath,
|
|
293
|
+
originalError: err.message,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Validate configuration structure
|
|
299
|
+
*
|
|
300
|
+
* Checks that all required fields are present and have correct types.
|
|
301
|
+
*
|
|
302
|
+
* @param config - Configuration object to validate
|
|
303
|
+
* @throws {ConfigurationError} If configuration is invalid
|
|
304
|
+
*/
|
|
305
|
+
validateConfig(config) {
|
|
306
|
+
if (!config || typeof config !== 'object') {
|
|
307
|
+
throw new ConfigurationError('Configuration must be an object', {
|
|
308
|
+
actualType: typeof config,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
const cfg = config;
|
|
312
|
+
// Validate required field: devStoryLocation
|
|
313
|
+
if (!cfg.devStoryLocation || typeof cfg.devStoryLocation !== 'string') {
|
|
314
|
+
throw new ConfigurationError('Missing or invalid required field: devStoryLocation', {
|
|
315
|
+
field: 'devStoryLocation',
|
|
316
|
+
value: cfg.devStoryLocation,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
// Validate optional prd field
|
|
320
|
+
if (cfg.prd) {
|
|
321
|
+
if (typeof cfg.prd !== 'object') {
|
|
322
|
+
throw new ConfigurationError('Invalid prd field: must be an object', {
|
|
323
|
+
field: 'prd',
|
|
324
|
+
value: cfg.prd,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const prd = cfg.prd;
|
|
328
|
+
if (prd.prdFile && typeof prd.prdFile !== 'string') {
|
|
329
|
+
throw new ConfigurationError('Invalid prd.prdFile field: must be a string', {
|
|
330
|
+
field: 'prd.prdFile',
|
|
331
|
+
value: prd.prdFile,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Validate optional qa field
|
|
336
|
+
if (cfg.qa) {
|
|
337
|
+
if (typeof cfg.qa !== 'object') {
|
|
338
|
+
throw new ConfigurationError('Invalid qa field: must be an object', {
|
|
339
|
+
field: 'qa',
|
|
340
|
+
value: cfg.qa,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const qa = cfg.qa;
|
|
344
|
+
if (qa.qaLocation && typeof qa.qaLocation !== 'string') {
|
|
345
|
+
throw new ConfigurationError('Invalid qa.qaLocation field: must be a string', {
|
|
346
|
+
field: 'qa.qaLocation',
|
|
347
|
+
value: qa.qaLocation,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
this.logger.debug('Configuration validation passed');
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Validate that a directory exists (synchronous)
|
|
355
|
+
*
|
|
356
|
+
* Checks directory existence and throws error if not found.
|
|
357
|
+
* Uses synchronous fs check for initialization validation.
|
|
358
|
+
*
|
|
359
|
+
* @param dirPath - Absolute path to directory
|
|
360
|
+
* @param name - Human-readable directory name for error messages
|
|
361
|
+
* @throws {FileSystemError} If directory does not exist
|
|
362
|
+
*/
|
|
363
|
+
validateDirectorySync(dirPath, name) {
|
|
364
|
+
this.logger.debug('Validating directory: %s (%s)', dirPath, name);
|
|
365
|
+
try {
|
|
366
|
+
if (!fs.existsSync(dirPath)) {
|
|
367
|
+
this.logger.error('Directory does not exist: %s (%s)', dirPath, name);
|
|
368
|
+
throw new FileSystemError(`${name} does not exist: ${dirPath}`, {
|
|
369
|
+
dirPath,
|
|
370
|
+
name,
|
|
371
|
+
operation: 'validateDirectory',
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Check if it's actually a directory
|
|
375
|
+
const stats = fs.statSync(dirPath);
|
|
376
|
+
if (!stats.isDirectory()) {
|
|
377
|
+
this.logger.error('Path is not a directory: %s (%s)', dirPath, name);
|
|
378
|
+
throw new FileSystemError(`${name} is not a directory: ${dirPath}`, {
|
|
379
|
+
dirPath,
|
|
380
|
+
name,
|
|
381
|
+
operation: 'validateDirectory',
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
this.logger.debug('Directory validation passed: %s (%s)', dirPath, name);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
if (error instanceof FileSystemError) {
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
const err = error;
|
|
391
|
+
this.logger.error('Error validating directory %s (%s): %O', dirPath, name, err);
|
|
392
|
+
throw new FileSystemError(`Failed to validate ${name}: ${err.message}`, {
|
|
393
|
+
dirPath,
|
|
394
|
+
name,
|
|
395
|
+
operation: 'validateDirectory',
|
|
396
|
+
originalError: err.message,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkflowLogger Service
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive logging for workflow executions including:
|
|
5
|
+
* - Execution metadata and configuration
|
|
6
|
+
* - Prompts sent to Claude agents
|
|
7
|
+
* - Responses received from agents
|
|
8
|
+
* - Execution timeline and state
|
|
9
|
+
* - Success/failure tracking
|
|
10
|
+
*/
|
|
11
|
+
import type { Logger } from 'pino';
|
|
12
|
+
import type { AgentOptions, AgentResult, WorkflowConfig, WorkflowResult } from '../../models/index.js';
|
|
13
|
+
export interface LogEntry {
|
|
14
|
+
action: string;
|
|
15
|
+
details: Record<string, unknown>;
|
|
16
|
+
level: 'debug' | 'error' | 'info' | 'warn';
|
|
17
|
+
phase: 'dev' | 'epic' | 'story' | 'workflow';
|
|
18
|
+
timestamp: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PromptLogEntry {
|
|
21
|
+
agentType: string;
|
|
22
|
+
itemIdentifier: string;
|
|
23
|
+
phase: 'dev' | 'epic' | 'story';
|
|
24
|
+
prompt: string;
|
|
25
|
+
promptLength: number;
|
|
26
|
+
references: string[];
|
|
27
|
+
timeout: number;
|
|
28
|
+
timestamp: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ResponseLogEntry {
|
|
31
|
+
agentType: string;
|
|
32
|
+
duration: number;
|
|
33
|
+
errors: string;
|
|
34
|
+
exitCode: number;
|
|
35
|
+
itemIdentifier: string;
|
|
36
|
+
output: string;
|
|
37
|
+
outputLength: number;
|
|
38
|
+
phase: 'dev' | 'epic' | 'story';
|
|
39
|
+
success: boolean;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ExecutionPlan {
|
|
43
|
+
epicsExisting: number;
|
|
44
|
+
epicsToCreate: number;
|
|
45
|
+
estimatedDuration: string;
|
|
46
|
+
storiesExisting: number;
|
|
47
|
+
storiesToCreate: number;
|
|
48
|
+
storiesToDevelop: number;
|
|
49
|
+
}
|
|
50
|
+
export interface PipelineState {
|
|
51
|
+
activeWorkers: number;
|
|
52
|
+
storiesCompleted: number;
|
|
53
|
+
storiesCreating: number;
|
|
54
|
+
storiesDeveloping: number;
|
|
55
|
+
storiesQueued: number;
|
|
56
|
+
}
|
|
57
|
+
export interface WorkflowLogFile {
|
|
58
|
+
configuration: WorkflowConfig;
|
|
59
|
+
errors: Array<{
|
|
60
|
+
error: string;
|
|
61
|
+
identifier: string;
|
|
62
|
+
phase: string;
|
|
63
|
+
stack?: string;
|
|
64
|
+
timestamp: string;
|
|
65
|
+
}>;
|
|
66
|
+
executionPlan: ExecutionPlan;
|
|
67
|
+
metadata: {
|
|
68
|
+
duration?: number;
|
|
69
|
+
endTime?: string;
|
|
70
|
+
startTime: string;
|
|
71
|
+
status: 'completed' | 'failed' | 'running';
|
|
72
|
+
workflowId: string;
|
|
73
|
+
};
|
|
74
|
+
pipeline?: PipelineState;
|
|
75
|
+
prompts: PromptLogEntry[];
|
|
76
|
+
responses: ResponseLogEntry[];
|
|
77
|
+
summary: {
|
|
78
|
+
completed: {
|
|
79
|
+
developments: number;
|
|
80
|
+
epics: number;
|
|
81
|
+
stories: number;
|
|
82
|
+
};
|
|
83
|
+
failed: {
|
|
84
|
+
developments: number;
|
|
85
|
+
epics: number;
|
|
86
|
+
stories: number;
|
|
87
|
+
};
|
|
88
|
+
pending: {
|
|
89
|
+
developments: number;
|
|
90
|
+
epics: number;
|
|
91
|
+
stories: number;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
timeline: LogEntry[];
|
|
95
|
+
}
|
|
96
|
+
export declare class WorkflowLogger {
|
|
97
|
+
private readonly logger;
|
|
98
|
+
private readonly logsDir;
|
|
99
|
+
private logFile;
|
|
100
|
+
private logFilePath;
|
|
101
|
+
private workflowId;
|
|
102
|
+
constructor(logger: Logger, logsDir?: string);
|
|
103
|
+
/**
|
|
104
|
+
* Generate pipeline summary table for concurrent phases
|
|
105
|
+
*
|
|
106
|
+
* Creates a formatted table showing the status of each phase in the pipeline,
|
|
107
|
+
* including pending, in progress, completed, and failed counts.
|
|
108
|
+
*
|
|
109
|
+
* @returns Formatted summary table string
|
|
110
|
+
*/
|
|
111
|
+
generatePipelineSummaryTable(): string;
|
|
112
|
+
/**
|
|
113
|
+
* Get log file path
|
|
114
|
+
*/
|
|
115
|
+
getLogFilePath(): string;
|
|
116
|
+
/**
|
|
117
|
+
* Get current pipeline state
|
|
118
|
+
*/
|
|
119
|
+
getPipelineState(): PipelineState | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Get workflow ID
|
|
122
|
+
*/
|
|
123
|
+
getWorkflowId(): string;
|
|
124
|
+
/**
|
|
125
|
+
* Initialize workflow log with configuration
|
|
126
|
+
*/
|
|
127
|
+
initialize(config: WorkflowConfig): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Initialize pipeline state tracking
|
|
130
|
+
*/
|
|
131
|
+
initializePipelineState(): void;
|
|
132
|
+
/**
|
|
133
|
+
* Log phase completion
|
|
134
|
+
*/
|
|
135
|
+
logPhaseComplete(phase: 'dev' | 'epic' | 'story', successCount: number, failureCount: number, duration: number): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Log phase start
|
|
138
|
+
*/
|
|
139
|
+
logPhaseStart(phase: 'dev' | 'epic' | 'story', details: Record<string, unknown>): Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Log a prompt being sent to Claude
|
|
142
|
+
*/
|
|
143
|
+
logPrompt(phase: 'dev' | 'epic' | 'story', itemIdentifier: string, prompt: string, options: AgentOptions): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Log a response received from Claude
|
|
146
|
+
*/
|
|
147
|
+
logResponse(phase: 'dev' | 'epic' | 'story', itemIdentifier: string, result: AgentResult): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Log story development completed in pipeline mode
|
|
150
|
+
*/
|
|
151
|
+
logStoryCompleted(storyIdentifier: string, duration: number, success: boolean): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Log story created in pipeline mode
|
|
154
|
+
*/
|
|
155
|
+
logStoryCreated(storyIdentifier: string): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Log story development started in pipeline mode
|
|
158
|
+
*/
|
|
159
|
+
logStoryDeveloping(storyIdentifier: string, workerId: number): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Log story queued for development in pipeline mode
|
|
162
|
+
*/
|
|
163
|
+
logStoryQueued(storyIdentifier: string, queuePosition?: number): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Log verbose story transition with timestamp
|
|
166
|
+
*
|
|
167
|
+
* Formats transition as: [HH:MM:SS] Story X.Y 'Title' → STATE
|
|
168
|
+
*
|
|
169
|
+
* @param storyNumber - Story number (e.g., "1.1")
|
|
170
|
+
* @param storyTitle - Story title
|
|
171
|
+
* @param state - New state (CREATING, QUEUED, DEVELOPING, COMPLETED, FAILED)
|
|
172
|
+
* @param details - Optional additional details
|
|
173
|
+
*/
|
|
174
|
+
logVerboseTransition(storyNumber: string, storyTitle: string, state: 'COMPLETED' | 'CREATING' | 'DEVELOPING' | 'FAILED' | 'QUEUED', details?: Record<string, unknown>): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Log workflow completion
|
|
177
|
+
*/
|
|
178
|
+
logWorkflowComplete(result: WorkflowResult): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* Set execution plan
|
|
181
|
+
*/
|
|
182
|
+
setExecutionPlan(plan: ExecutionPlan): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Update active worker count in pipeline mode
|
|
185
|
+
*/
|
|
186
|
+
updateActiveWorkers(count: number): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Add a timeline entry
|
|
189
|
+
*/
|
|
190
|
+
private addLogEntry;
|
|
191
|
+
/**
|
|
192
|
+
* Build markdown summary content
|
|
193
|
+
*/
|
|
194
|
+
private buildMarkdownSummary;
|
|
195
|
+
/**
|
|
196
|
+
* Format duration in human-readable format
|
|
197
|
+
*/
|
|
198
|
+
private formatDuration;
|
|
199
|
+
/**
|
|
200
|
+
* Format time as HH:MM:SS
|
|
201
|
+
*
|
|
202
|
+
* @param date - Date to format
|
|
203
|
+
* @returns Formatted time string
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
private formatTime;
|
|
207
|
+
/**
|
|
208
|
+
* Generate human-readable markdown summary
|
|
209
|
+
*/
|
|
210
|
+
private generateMarkdownSummary;
|
|
211
|
+
/**
|
|
212
|
+
* Generate unique workflow ID
|
|
213
|
+
*/
|
|
214
|
+
private generateWorkflowId;
|
|
215
|
+
/**
|
|
216
|
+
* Log an error
|
|
217
|
+
*/
|
|
218
|
+
private logError;
|
|
219
|
+
/**
|
|
220
|
+
* Pad a number to a specific width with spaces
|
|
221
|
+
*
|
|
222
|
+
* @param num - Number to pad
|
|
223
|
+
* @param width - Target width
|
|
224
|
+
* @returns Padded string
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
private padNumber;
|
|
228
|
+
/**
|
|
229
|
+
* Write log file to disk
|
|
230
|
+
*/
|
|
231
|
+
private writeLogFile;
|
|
232
|
+
}
|