@hyperdrive.bot/bmad-workflow 1.0.21 → 1.0.23
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/assets/agents/dev-barry.md +69 -0
- package/assets/agents/dev.md +323 -0
- package/assets/agents/qa.md +92 -0
- package/assets/agents/sm-bob.md +65 -0
- package/assets/agents/sm.md +296 -0
- package/assets/config/default-config.yaml +6 -0
- package/assets/templates/epic-tmpl.yaml +277 -0
- package/assets/templates/prd-tmpl.yaml +261 -0
- package/assets/templates/qa-gate-tmpl.yaml +103 -0
- package/assets/templates/story-tmpl.yaml +138 -0
- package/dist/commands/eject.d.ts +76 -0
- package/dist/commands/eject.js +232 -0
- package/dist/commands/init.d.ts +47 -0
- package/dist/commands/init.js +265 -0
- package/dist/commands/stories/develop.js +1 -0
- package/dist/commands/stories/qa.d.ts +1 -0
- package/dist/commands/stories/qa.js +7 -0
- package/dist/commands/workflow.d.ts +6 -3
- package/dist/commands/workflow.js +106 -26
- package/dist/models/bmad-config-schema.d.ts +51 -0
- package/dist/models/bmad-config-schema.js +53 -0
- package/dist/services/agents/gemini-agent-runner.js +7 -2
- package/dist/services/agents/opencode-agent-runner.js +7 -2
- package/dist/services/file-system/asset-resolver.d.ts +117 -0
- package/dist/services/file-system/asset-resolver.js +234 -0
- package/dist/services/file-system/file-manager.d.ts +13 -0
- package/dist/services/file-system/file-manager.js +32 -0
- package/dist/services/file-system/path-resolver.d.ts +22 -1
- package/dist/services/file-system/path-resolver.js +36 -9
- package/dist/services/orchestration/dependency-graph-executor.js +1 -0
- package/dist/services/orchestration/workflow-orchestrator.d.ts +11 -0
- package/dist/services/orchestration/workflow-orchestrator.js +79 -10
- package/dist/utils/config-merge.d.ts +60 -0
- package/dist/utils/config-merge.js +52 -0
- package/package.json +4 -2
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AssetResolver Service
|
|
3
|
+
*
|
|
4
|
+
* Resolves agents, templates, and config through a three-level chain:
|
|
5
|
+
* 1. CLI flags (highest priority)
|
|
6
|
+
* 2. .bmad-workflow.yaml config file
|
|
7
|
+
* 3. Bundled defaults (shipped inside npm package)
|
|
8
|
+
*
|
|
9
|
+
* This enables the CLI to work both standalone (zero-config) and
|
|
10
|
+
* in existing BMAD setups with .bmad-core/.
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'fs-extra';
|
|
13
|
+
import { load as yamlLoad } from 'js-yaml';
|
|
14
|
+
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { parseDuration } from '../../utils/duration.js';
|
|
17
|
+
/**
|
|
18
|
+
* Valid AI provider names
|
|
19
|
+
*/
|
|
20
|
+
const VALID_PROVIDERS = ['claude', 'gemini', 'opencode'];
|
|
21
|
+
/**
|
|
22
|
+
* AssetResolver resolves asset paths using a three-level resolution chain.
|
|
23
|
+
*/
|
|
24
|
+
export class AssetResolver {
|
|
25
|
+
bundledPath;
|
|
26
|
+
config;
|
|
27
|
+
flags;
|
|
28
|
+
/**
|
|
29
|
+
* Create a new AssetResolver
|
|
30
|
+
*
|
|
31
|
+
* @param flags - CLI flag overrides for agent selection
|
|
32
|
+
*/
|
|
33
|
+
constructor(flags = {}) {
|
|
34
|
+
this.flags = flags;
|
|
35
|
+
this.bundledPath = this.computeBundledPath();
|
|
36
|
+
this.config = this.loadConfigFile();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve an agent file path.
|
|
40
|
+
*
|
|
41
|
+
* Resolution order: CLI flag → config file → bundled default
|
|
42
|
+
*
|
|
43
|
+
* @param role - Agent role (dev, sm, qa)
|
|
44
|
+
* @param flagValue - Optional CLI flag override
|
|
45
|
+
* @returns Resolved asset with path and source
|
|
46
|
+
*/
|
|
47
|
+
resolveAgent(role, flagValue) {
|
|
48
|
+
// Level 1: CLI flag
|
|
49
|
+
const flag = flagValue || this.getFlagForRole(role);
|
|
50
|
+
if (flag) {
|
|
51
|
+
return { path: this.resolveAgentValue(flag), source: 'flag' };
|
|
52
|
+
}
|
|
53
|
+
// Level 2: Config file
|
|
54
|
+
const configKey = `${role}_agent`;
|
|
55
|
+
const configValue = this.config?.[configKey];
|
|
56
|
+
if (configValue) {
|
|
57
|
+
return { path: this.resolveAgentValue(configValue), source: 'config' };
|
|
58
|
+
}
|
|
59
|
+
// Level 3: Bundled default
|
|
60
|
+
return {
|
|
61
|
+
path: join(this.bundledPath, 'agents', `${role}.md`),
|
|
62
|
+
source: 'bundled',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a template file path.
|
|
67
|
+
*
|
|
68
|
+
* Checks bmad_path config directory first, falls back to bundled.
|
|
69
|
+
*
|
|
70
|
+
* @param name - Template filename (e.g., 'epic-tmpl.yaml')
|
|
71
|
+
* @returns Resolved asset with path and source
|
|
72
|
+
*/
|
|
73
|
+
resolveTemplate(name) {
|
|
74
|
+
// Config bmad_path override (resolved relative to project root)
|
|
75
|
+
if (this.config?.bmad_path) {
|
|
76
|
+
const local = join(process.cwd(), this.config.bmad_path, 'templates', name);
|
|
77
|
+
if (fs.existsSync(local)) {
|
|
78
|
+
return { path: local, source: 'config' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Bundled default
|
|
82
|
+
return {
|
|
83
|
+
path: join(this.bundledPath, 'templates', name),
|
|
84
|
+
source: 'bundled',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the config file path.
|
|
89
|
+
*
|
|
90
|
+
* Returns the absolute path to the bundled default-config.yaml.
|
|
91
|
+
* Callers (PathResolver) decide whether to prefer local .bmad-core/ over this.
|
|
92
|
+
*
|
|
93
|
+
* @returns Absolute path to bundled config file
|
|
94
|
+
*/
|
|
95
|
+
resolveConfig() {
|
|
96
|
+
return join(this.bundledPath, 'config', 'default-config.yaml');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extract execution defaults from the loaded config file.
|
|
100
|
+
*
|
|
101
|
+
* Parses and validates: parallel, timeout, provider, model, qa_enabled, pipeline.
|
|
102
|
+
* Invalid values are silently ignored (field omitted from result).
|
|
103
|
+
*
|
|
104
|
+
* @returns Parsed execution defaults, or empty object if no config file
|
|
105
|
+
*/
|
|
106
|
+
getExecutionDefaults() {
|
|
107
|
+
if (!this.config)
|
|
108
|
+
return {};
|
|
109
|
+
const defaults = {};
|
|
110
|
+
// parallel — must be positive integer
|
|
111
|
+
if (this.config.parallel !== undefined) {
|
|
112
|
+
const val = Number(this.config.parallel);
|
|
113
|
+
if (Number.isInteger(val) && val > 0) {
|
|
114
|
+
defaults.parallel = val;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.warn(`[bmad-workflow] Ignoring invalid config value parallel: ${this.config.parallel} (expected positive integer)`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// timeout — human-readable string or number, converted to ms
|
|
121
|
+
if (this.config.timeout !== undefined) {
|
|
122
|
+
try {
|
|
123
|
+
defaults.timeout = parseDuration(this.config.timeout);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
console.warn(`[bmad-workflow] Ignoring invalid config value timeout: ${this.config.timeout}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// provider — must be one of valid providers
|
|
130
|
+
if (this.config.provider !== undefined) {
|
|
131
|
+
const val = String(this.config.provider);
|
|
132
|
+
if (VALID_PROVIDERS.includes(val)) {
|
|
133
|
+
defaults.provider = val;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.warn(`[bmad-workflow] Ignoring invalid config value provider: ${val} (expected: ${VALID_PROVIDERS.join(', ')})`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// model — any non-empty string
|
|
140
|
+
if (this.config.model !== undefined && typeof this.config.model === 'string' && this.config.model.trim()) {
|
|
141
|
+
defaults.model = this.config.model.trim();
|
|
142
|
+
}
|
|
143
|
+
// qa_enabled — must be boolean
|
|
144
|
+
if (this.config.qa_enabled !== undefined) {
|
|
145
|
+
if (typeof this.config.qa_enabled === 'boolean') {
|
|
146
|
+
defaults.qa_enabled = this.config.qa_enabled;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.warn(`[bmad-workflow] Ignoring invalid config value qa_enabled: ${this.config.qa_enabled} (expected boolean)`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// pipeline — must be boolean
|
|
153
|
+
if (this.config.pipeline !== undefined) {
|
|
154
|
+
if (typeof this.config.pipeline === 'boolean') {
|
|
155
|
+
defaults.pipeline = this.config.pipeline;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.warn(`[bmad-workflow] Ignoring invalid config value pipeline: ${this.config.pipeline} (expected boolean)`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return defaults;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Compute the bundled assets root path.
|
|
165
|
+
*
|
|
166
|
+
* From compiled dist/services/file-system/asset-resolver.js,
|
|
167
|
+
* the assets directory is three levels up: ../../.. → <pkg>/assets/
|
|
168
|
+
*
|
|
169
|
+
* @returns Absolute path to bundled assets directory
|
|
170
|
+
*/
|
|
171
|
+
computeBundledPath() {
|
|
172
|
+
// Support both ESM (__dirname equivalent) and compiled contexts
|
|
173
|
+
try {
|
|
174
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
175
|
+
const currentDir = dirname(currentFile);
|
|
176
|
+
return join(currentDir, '..', '..', '..', 'assets');
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Fallback for environments where import.meta.url is not available
|
|
180
|
+
return join(__dirname, '..', '..', 'assets');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get CLI flag value for a given role.
|
|
185
|
+
*/
|
|
186
|
+
getFlagForRole(role) {
|
|
187
|
+
const map = {
|
|
188
|
+
dev: this.flags.devAgent,
|
|
189
|
+
qa: this.flags.qaAgent,
|
|
190
|
+
sm: this.flags.smAgent,
|
|
191
|
+
};
|
|
192
|
+
return map[role];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Load .bmad-workflow.yaml from project root (if exists).
|
|
196
|
+
*
|
|
197
|
+
* @returns Parsed config or null if file absent
|
|
198
|
+
*/
|
|
199
|
+
loadConfigFile() {
|
|
200
|
+
const configPath = join(process.cwd(), '.bmad-workflow.yaml');
|
|
201
|
+
if (!fs.existsSync(configPath))
|
|
202
|
+
return null;
|
|
203
|
+
try {
|
|
204
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
205
|
+
return yamlLoad(content);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Resolve an agent value — could be a built-in name or a file path.
|
|
213
|
+
*
|
|
214
|
+
* Built-in names (no path separators, no .md extension) map to bundled agents.
|
|
215
|
+
* File paths are resolved relative to project root.
|
|
216
|
+
*
|
|
217
|
+
* @param value - Agent name or file path
|
|
218
|
+
* @returns Absolute path to agent file
|
|
219
|
+
*/
|
|
220
|
+
resolveAgentValue(value) {
|
|
221
|
+
// Built-in name (no path separators, no extension)
|
|
222
|
+
if (!value.includes('/') && !value.endsWith('.md')) {
|
|
223
|
+
// Check bmad_path first (resolved relative to project root)
|
|
224
|
+
if (this.config?.bmad_path) {
|
|
225
|
+
const local = join(process.cwd(), this.config.bmad_path, 'agents', `${value}.md`);
|
|
226
|
+
if (fs.existsSync(local))
|
|
227
|
+
return local;
|
|
228
|
+
}
|
|
229
|
+
return join(this.bundledPath, 'agents', `${value}.md`);
|
|
230
|
+
}
|
|
231
|
+
// File path — resolve relative to project root
|
|
232
|
+
return resolve(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -37,6 +37,19 @@ export declare class FileManager {
|
|
|
37
37
|
* await fileManager.createDirectory('docs/epics')
|
|
38
38
|
*/
|
|
39
39
|
createDirectory(path: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Copy a file from source to destination
|
|
42
|
+
*
|
|
43
|
+
* Creates parent directories if they don't exist.
|
|
44
|
+
* Overwrites destination file if it exists.
|
|
45
|
+
*
|
|
46
|
+
* @param source - Source file path
|
|
47
|
+
* @param dest - Destination file path
|
|
48
|
+
* @throws {FileSystemError} If file cannot be copied (source not found, permission denied, etc.)
|
|
49
|
+
* @example
|
|
50
|
+
* await fileManager.copyFile('assets/agents/dev.md', '.bmad-core/agents/dev.md')
|
|
51
|
+
*/
|
|
52
|
+
copyFile(source: string, dest: string): Promise<void>;
|
|
40
53
|
/**
|
|
41
54
|
* Check if a file or directory exists
|
|
42
55
|
*
|
|
@@ -56,6 +56,38 @@ export class FileManager {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Copy a file from source to destination
|
|
61
|
+
*
|
|
62
|
+
* Creates parent directories if they don't exist.
|
|
63
|
+
* Overwrites destination file if it exists.
|
|
64
|
+
*
|
|
65
|
+
* @param source - Source file path
|
|
66
|
+
* @param dest - Destination file path
|
|
67
|
+
* @throws {FileSystemError} If file cannot be copied (source not found, permission denied, etc.)
|
|
68
|
+
* @example
|
|
69
|
+
* await fileManager.copyFile('assets/agents/dev.md', '.bmad-core/agents/dev.md')
|
|
70
|
+
*/
|
|
71
|
+
async copyFile(source, dest) {
|
|
72
|
+
this.logger.info('Copying file from %s to %s', source, dest);
|
|
73
|
+
try {
|
|
74
|
+
// Ensure destination directory exists
|
|
75
|
+
await fs.ensureDir(dirname(dest));
|
|
76
|
+
// Copy file
|
|
77
|
+
await fs.copy(source, dest, { overwrite: true });
|
|
78
|
+
this.logger.info('File copied successfully from %s to %s', source, dest);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const err = error;
|
|
82
|
+
this.logger.error('Error copying file from %s to %s: %O', source, dest, err);
|
|
83
|
+
throw new FileSystemError(`Failed to copy file from ${source} to ${dest}: ${err.message}`, {
|
|
84
|
+
dest,
|
|
85
|
+
operation: 'copyFile',
|
|
86
|
+
originalError: err.message,
|
|
87
|
+
source,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
59
91
|
/**
|
|
60
92
|
* Check if a file or directory exists
|
|
61
93
|
*
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type pino from 'pino';
|
|
8
8
|
import type { ReviewConfig } from '../validation/config-validator.js';
|
|
9
|
+
import { AssetResolver } from './asset-resolver.js';
|
|
9
10
|
import { FileManager } from './file-manager.js';
|
|
10
11
|
/**
|
|
11
12
|
* PathResolver service for resolving and validating file paths
|
|
@@ -15,10 +16,18 @@ import { FileManager } from './file-manager.js';
|
|
|
15
16
|
* are resolved relative to the project root and validated for existence.
|
|
16
17
|
*/
|
|
17
18
|
export declare class PathResolver {
|
|
19
|
+
/**
|
|
20
|
+
* AssetResolver for bundled asset fallback
|
|
21
|
+
*/
|
|
22
|
+
private readonly assetResolver;
|
|
18
23
|
/**
|
|
19
24
|
* Cached configuration to avoid repeated file reads
|
|
20
25
|
*/
|
|
21
26
|
private cachedConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Cached path to the configuration file (local or bundled)
|
|
29
|
+
*/
|
|
30
|
+
private cachedConfigPath;
|
|
22
31
|
/**
|
|
23
32
|
* Cached resolved paths
|
|
24
33
|
*/
|
|
@@ -40,12 +49,13 @@ export declare class PathResolver {
|
|
|
40
49
|
*
|
|
41
50
|
* @param fileManager - FileManager instance for file operations
|
|
42
51
|
* @param logger - Pino logger instance for logging path operations
|
|
52
|
+
* @param assetResolver - Optional AssetResolver instance (DI for testability)
|
|
43
53
|
* @example
|
|
44
54
|
* const logger = createLogger({ namespace: 'services:path-resolver' })
|
|
45
55
|
* const fileManager = new FileManager(logger)
|
|
46
56
|
* const pathResolver = new PathResolver(fileManager, logger)
|
|
47
57
|
*/
|
|
48
|
-
constructor(fileManager: FileManager, logger: pino.Logger);
|
|
58
|
+
constructor(fileManager: FileManager, logger: pino.Logger, assetResolver?: AssetResolver);
|
|
49
59
|
/**
|
|
50
60
|
* Get all story directories for existence checks
|
|
51
61
|
*
|
|
@@ -170,6 +180,17 @@ export declare class PathResolver {
|
|
|
170
180
|
* @throws {ConfigurationError} If configuration structure is invalid
|
|
171
181
|
*/
|
|
172
182
|
private loadConfig;
|
|
183
|
+
/**
|
|
184
|
+
* Read, parse, and validate a config file at the given path.
|
|
185
|
+
*
|
|
186
|
+
* Sets cachedConfig and cachedConfigPath on success.
|
|
187
|
+
* Applies hardcoded defaults for missing fields.
|
|
188
|
+
*
|
|
189
|
+
* @param configPath - Absolute path to YAML config file
|
|
190
|
+
* @returns Parsed and validated configuration
|
|
191
|
+
* @throws {ConfigurationError} If configuration structure is invalid
|
|
192
|
+
*/
|
|
193
|
+
private readAndParseConfig;
|
|
173
194
|
/**
|
|
174
195
|
* Validate configuration structure
|
|
175
196
|
*
|
|
@@ -8,6 +8,7 @@ import fs from 'fs-extra';
|
|
|
8
8
|
import { load as yamlLoad } from 'js-yaml';
|
|
9
9
|
import { dirname, resolve } from 'node:path';
|
|
10
10
|
import { ConfigurationError, FileSystemError } from '../../utils/errors.js';
|
|
11
|
+
import { AssetResolver } from './asset-resolver.js';
|
|
11
12
|
/**
|
|
12
13
|
* Configuration file path relative to project root
|
|
13
14
|
*/
|
|
@@ -20,10 +21,18 @@ const CONFIG_FILE_PATH = '.bmad-core/core-config.yaml';
|
|
|
20
21
|
* are resolved relative to the project root and validated for existence.
|
|
21
22
|
*/
|
|
22
23
|
export class PathResolver {
|
|
24
|
+
/**
|
|
25
|
+
* AssetResolver for bundled asset fallback
|
|
26
|
+
*/
|
|
27
|
+
assetResolver;
|
|
23
28
|
/**
|
|
24
29
|
* Cached configuration to avoid repeated file reads
|
|
25
30
|
*/
|
|
26
31
|
cachedConfig = null;
|
|
32
|
+
/**
|
|
33
|
+
* Cached path to the configuration file (local or bundled)
|
|
34
|
+
*/
|
|
35
|
+
cachedConfigPath = null;
|
|
27
36
|
/**
|
|
28
37
|
* Cached resolved paths
|
|
29
38
|
*/
|
|
@@ -45,14 +54,16 @@ export class PathResolver {
|
|
|
45
54
|
*
|
|
46
55
|
* @param fileManager - FileManager instance for file operations
|
|
47
56
|
* @param logger - Pino logger instance for logging path operations
|
|
57
|
+
* @param assetResolver - Optional AssetResolver instance (DI for testability)
|
|
48
58
|
* @example
|
|
49
59
|
* const logger = createLogger({ namespace: 'services:path-resolver' })
|
|
50
60
|
* const fileManager = new FileManager(logger)
|
|
51
61
|
* const pathResolver = new PathResolver(fileManager, logger)
|
|
52
62
|
*/
|
|
53
|
-
constructor(fileManager, logger) {
|
|
63
|
+
constructor(fileManager, logger, assetResolver) {
|
|
54
64
|
this.fileManager = fileManager;
|
|
55
65
|
this.logger = logger;
|
|
66
|
+
this.assetResolver = assetResolver || new AssetResolver({});
|
|
56
67
|
this.projectRoot = process.cwd();
|
|
57
68
|
this.logger.debug('PathResolver initialized with project root: %s', this.projectRoot);
|
|
58
69
|
}
|
|
@@ -87,7 +98,11 @@ export class PathResolver {
|
|
|
87
98
|
* // Returns: '/path/to/project/.bmad-core/core-config.yaml'
|
|
88
99
|
*/
|
|
89
100
|
getConfigPath() {
|
|
90
|
-
|
|
101
|
+
// Trigger config load if not yet loaded (populates cachedConfigPath)
|
|
102
|
+
if (!this.cachedConfigPath) {
|
|
103
|
+
this.loadConfig();
|
|
104
|
+
}
|
|
105
|
+
const configPath = this.cachedConfigPath;
|
|
91
106
|
this.logger.debug('Getting config path: %s', configPath);
|
|
92
107
|
return configPath;
|
|
93
108
|
}
|
|
@@ -274,23 +289,35 @@ export class PathResolver {
|
|
|
274
289
|
}
|
|
275
290
|
const configPath = this.findConfigFile();
|
|
276
291
|
if (!configPath) {
|
|
277
|
-
|
|
278
|
-
this.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
operation: 'loadConfig',
|
|
282
|
-
});
|
|
292
|
+
// Fallback to bundled default config via AssetResolver
|
|
293
|
+
const bundledPath = this.assetResolver.resolveConfig();
|
|
294
|
+
this.logger.info('No local config found, falling back to bundled default: %s', bundledPath);
|
|
295
|
+
return this.readAndParseConfig(bundledPath);
|
|
283
296
|
}
|
|
284
297
|
this.logger.info('Loading configuration from: %s', configPath);
|
|
298
|
+
return this.readAndParseConfig(configPath);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Read, parse, and validate a config file at the given path.
|
|
302
|
+
*
|
|
303
|
+
* Sets cachedConfig and cachedConfigPath on success.
|
|
304
|
+
* Applies hardcoded defaults for missing fields.
|
|
305
|
+
*
|
|
306
|
+
* @param configPath - Absolute path to YAML config file
|
|
307
|
+
* @returns Parsed and validated configuration
|
|
308
|
+
* @throws {ConfigurationError} If configuration structure is invalid
|
|
309
|
+
*/
|
|
310
|
+
readAndParseConfig(configPath) {
|
|
285
311
|
try {
|
|
286
312
|
// Read configuration file
|
|
287
313
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
288
|
-
this.logger.debug('Configuration file read successfully');
|
|
314
|
+
this.logger.debug('Configuration file read successfully from: %s', configPath);
|
|
289
315
|
// Parse YAML
|
|
290
316
|
const config = yamlLoad(content);
|
|
291
317
|
// Validate required fields
|
|
292
318
|
this.validateConfig(config);
|
|
293
319
|
this.cachedConfig = config;
|
|
320
|
+
this.cachedConfigPath = configPath;
|
|
294
321
|
this.logger.info('Configuration loaded and validated successfully');
|
|
295
322
|
return config;
|
|
296
323
|
}
|
|
@@ -308,6 +308,7 @@ Use the file at the path above to document:
|
|
|
308
308
|
// Execute agent
|
|
309
309
|
const result = await this.agentRunner.runAgent(fullPrompt, {
|
|
310
310
|
agentType,
|
|
311
|
+
cwd: this.cwd,
|
|
311
312
|
model: this.model,
|
|
312
313
|
references: task.targetFiles, // Pass target files as references
|
|
313
314
|
timeout: task.estimatedMinutes * 60 * 1000 * 1.5, // 1.5x estimated time as buffer
|
|
@@ -47,6 +47,7 @@ import type pino from 'pino';
|
|
|
47
47
|
import type { InputDetectionResult, WorkflowCallbacks, WorkflowConfig, WorkflowResult } from '../../models/index.js';
|
|
48
48
|
import type { AIProviderRunner } from '../agents/agent-runner.js';
|
|
49
49
|
import type { WorkflowLogger } from '../logging/workflow-logger.js';
|
|
50
|
+
import { AssetResolver } from '../file-system/asset-resolver.js';
|
|
50
51
|
import { FileManager } from '../file-system/file-manager.js';
|
|
51
52
|
import { PathResolver } from '../file-system/path-resolver.js';
|
|
52
53
|
import { EpicParser } from '../parsers/epic-parser.js';
|
|
@@ -79,6 +80,8 @@ export interface InputDetector {
|
|
|
79
80
|
export interface WorkflowOrchestratorConfig {
|
|
80
81
|
/** Service to execute AI agents (Claude or Gemini) */
|
|
81
82
|
agentRunner: AIProviderRunner;
|
|
83
|
+
/** Optional AssetResolver for three-level path resolution (CLI flag → config → bundled) */
|
|
84
|
+
assetResolver?: AssetResolver;
|
|
82
85
|
/** Service to handle parallel batch processing */
|
|
83
86
|
batchProcessor: BatchProcessor;
|
|
84
87
|
/** Service to parse epic files and extract stories */
|
|
@@ -147,6 +150,7 @@ export interface StoryPromptOptions {
|
|
|
147
150
|
*/
|
|
148
151
|
export declare class WorkflowOrchestrator {
|
|
149
152
|
private readonly agentRunner;
|
|
153
|
+
private readonly assetResolver;
|
|
150
154
|
private readonly batchProcessor;
|
|
151
155
|
private readonly callbacks?;
|
|
152
156
|
private readonly epicParser;
|
|
@@ -730,6 +734,13 @@ export declare class WorkflowOrchestrator {
|
|
|
730
734
|
* @private
|
|
731
735
|
*/
|
|
732
736
|
private sleep;
|
|
737
|
+
/**
|
|
738
|
+
* Parse the `### Dev Context` section from a story file's content.
|
|
739
|
+
*
|
|
740
|
+
* Returns sidecar reference paths and an optional deploy target string.
|
|
741
|
+
* If the section is missing or empty, returns empty arrays / undefined (backward compatible).
|
|
742
|
+
*/
|
|
743
|
+
private parseDevContext;
|
|
733
744
|
/**
|
|
734
745
|
* Update story status in file
|
|
735
746
|
*
|