@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,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decompose Command
|
|
3
|
+
*
|
|
4
|
+
* Decomposes large goals into executable task graphs with dependency management.
|
|
5
|
+
* Supports per-file task generation, parallel execution, and intelligent dependency resolution.
|
|
6
|
+
*/
|
|
7
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { createAgentRunner, isProviderSupported } from '../services/agents/agent-runner-factory.js';
|
|
10
|
+
import { FileManager } from '../services/file-system/file-manager.js';
|
|
11
|
+
import { GlobMatcher } from '../services/file-system/glob-matcher.js';
|
|
12
|
+
import { BatchProcessor } from '../services/orchestration/batch-processor.js';
|
|
13
|
+
import { DependencyGraphExecutor } from '../services/orchestration/dependency-graph-executor.js';
|
|
14
|
+
import { TaskDecompositionService } from '../services/orchestration/task-decomposition-service.js';
|
|
15
|
+
import { DecomposeSessionScaffolder } from '../services/scaffolding/decompose-session-scaffolder.js';
|
|
16
|
+
import * as colors from '../utils/colors.js';
|
|
17
|
+
import { formatBox, formatTable } from '../utils/formatters.js';
|
|
18
|
+
import { createLogger } from '../utils/logger.js';
|
|
19
|
+
/**
|
|
20
|
+
* Decompose Command
|
|
21
|
+
*
|
|
22
|
+
* Breaks down large goals into small, executable tasks with dependency management.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```bash
|
|
26
|
+
* # Decompose a goal with per-file mode
|
|
27
|
+
* bmad-cli decompose "Migrate project to TypeScript" --per-file --file-pattern "src/*.js"
|
|
28
|
+
*
|
|
29
|
+
* # Plan only without execution
|
|
30
|
+
* bmad-cli decompose "Add authentication" --plan-only
|
|
31
|
+
*
|
|
32
|
+
* # Execute with custom context
|
|
33
|
+
* bmad-cli decompose "Refactor API layer" --context package.json --context tsconfig.json --execute
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export default class Decompose extends Command {
|
|
37
|
+
static args = {
|
|
38
|
+
goal: Args.string({
|
|
39
|
+
description: 'The big goal to decompose into executable tasks',
|
|
40
|
+
required: true,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
static description = 'Decompose large goals into executable task graphs with dependency management';
|
|
44
|
+
static examples = [
|
|
45
|
+
'<%= config.bin %> <%= command.id %> "Migrate project to TypeScript 100%" --per-file --file-pattern "src/*.js"',
|
|
46
|
+
'<%= config.bin %> <%= command.id %> "Add user authentication" --plan-only',
|
|
47
|
+
'<%= config.bin %> <%= command.id %> "Refactor API layer" --execute --context package.json',
|
|
48
|
+
'<%= config.bin %> <%= command.id %> "Implement dark mode" --max-parallel 5 --context src/theme.ts',
|
|
49
|
+
'<%= config.bin %> <%= command.id %> "Refactor codebase" --story-format --story-prefix "REFACTOR"',
|
|
50
|
+
'<%= config.bin %> <%= command.id %> "Design new API endpoints" --agent architect --plan-only',
|
|
51
|
+
];
|
|
52
|
+
static flags = {
|
|
53
|
+
agent: Flags.string({
|
|
54
|
+
default: 'architect',
|
|
55
|
+
description: 'BMAD agent to use for planning/decomposition (the agent will choose best agents for each task)',
|
|
56
|
+
options: ['analyst', 'architect', 'dev', 'pm', 'quick-flow-solo-dev', 'sm', 'tea', 'tech-writer', 'ux-designer'],
|
|
57
|
+
}),
|
|
58
|
+
context: Flags.string({
|
|
59
|
+
description: 'Context files to provide to the planner (can be used multiple times)',
|
|
60
|
+
multiple: true,
|
|
61
|
+
}),
|
|
62
|
+
cwd: Flags.string({
|
|
63
|
+
description: 'Working directory path for task execution',
|
|
64
|
+
}),
|
|
65
|
+
execute: Flags.boolean({
|
|
66
|
+
default: false,
|
|
67
|
+
description: 'Execute tasks immediately after planning (skips confirmation)',
|
|
68
|
+
}),
|
|
69
|
+
'file-pattern': Flags.string({
|
|
70
|
+
description: 'Glob pattern for per-file mode (e.g., "src/**/*.js")',
|
|
71
|
+
}),
|
|
72
|
+
'max-parallel': Flags.integer({
|
|
73
|
+
default: 3,
|
|
74
|
+
description: 'Maximum number of tasks to run in parallel',
|
|
75
|
+
}),
|
|
76
|
+
'output-dir': Flags.string({
|
|
77
|
+
default: 'docs/decompose-sessions',
|
|
78
|
+
description: 'Directory for session outputs',
|
|
79
|
+
}),
|
|
80
|
+
'per-file': Flags.boolean({
|
|
81
|
+
default: false,
|
|
82
|
+
description: 'Create one task per file for file-heavy operations',
|
|
83
|
+
}),
|
|
84
|
+
'plan-only': Flags.boolean({
|
|
85
|
+
default: false,
|
|
86
|
+
description: 'Generate task graph without executing',
|
|
87
|
+
}),
|
|
88
|
+
provider: Flags.string({
|
|
89
|
+
default: 'claude',
|
|
90
|
+
description: 'AI provider to use (claude or gemini)',
|
|
91
|
+
options: ['claude', 'gemini'],
|
|
92
|
+
}),
|
|
93
|
+
'story-format': Flags.boolean({
|
|
94
|
+
default: false,
|
|
95
|
+
description: 'Format tasks as BMAD stories with acceptance criteria and proper structure',
|
|
96
|
+
}),
|
|
97
|
+
'story-prefix': Flags.string({
|
|
98
|
+
description: 'Project prefix for story IDs (e.g., "MIGRATE", "REFACTOR") - used with --story-format',
|
|
99
|
+
}),
|
|
100
|
+
'task-timeout': Flags.integer({
|
|
101
|
+
default: 1_800_000, // 30 minutes
|
|
102
|
+
description: 'Timeout per task in milliseconds',
|
|
103
|
+
}),
|
|
104
|
+
verbose: Flags.boolean({
|
|
105
|
+
char: 'v',
|
|
106
|
+
default: false,
|
|
107
|
+
description: 'Detailed output mode',
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
cancelled = false;
|
|
111
|
+
fileManager;
|
|
112
|
+
logger;
|
|
113
|
+
scaffolder;
|
|
114
|
+
/**
|
|
115
|
+
* Main command execution
|
|
116
|
+
*/
|
|
117
|
+
async run() {
|
|
118
|
+
const { args, flags } = await this.parse(Decompose);
|
|
119
|
+
try {
|
|
120
|
+
// Validate provider
|
|
121
|
+
if (!isProviderSupported(flags.provider)) {
|
|
122
|
+
this.error(`Unsupported provider: ${flags.provider}. Use 'claude' or 'gemini'.`, { exit: 1 });
|
|
123
|
+
}
|
|
124
|
+
// Validate per-file mode
|
|
125
|
+
if (flags['per-file'] && !flags['file-pattern']) {
|
|
126
|
+
this.error('--file-pattern is required when using --per-file mode', { exit: 1 });
|
|
127
|
+
}
|
|
128
|
+
// Validate story format mode
|
|
129
|
+
if (flags['story-format'] && !flags['story-prefix']) {
|
|
130
|
+
this.error('--story-prefix is required when using --story-format mode', { exit: 1 });
|
|
131
|
+
}
|
|
132
|
+
// Initialize services
|
|
133
|
+
await this.initializeServices(flags.provider);
|
|
134
|
+
// Register signal handlers
|
|
135
|
+
this.registerSignalHandlers();
|
|
136
|
+
// Show header
|
|
137
|
+
this.displayHeader(args.goal);
|
|
138
|
+
// Build options
|
|
139
|
+
const options = {
|
|
140
|
+
agent: flags.agent,
|
|
141
|
+
contextFiles: flags.context || [],
|
|
142
|
+
cwd: flags.cwd,
|
|
143
|
+
execute: flags.execute,
|
|
144
|
+
filePattern: flags['file-pattern'],
|
|
145
|
+
goal: args.goal,
|
|
146
|
+
maxParallel: flags['max-parallel'],
|
|
147
|
+
perFile: flags['per-file'],
|
|
148
|
+
planOnly: flags['plan-only'],
|
|
149
|
+
storyFormat: flags['story-format'],
|
|
150
|
+
storyPrefix: flags['story-prefix'],
|
|
151
|
+
taskTimeout: flags['task-timeout'],
|
|
152
|
+
};
|
|
153
|
+
// Log configuration if verbose
|
|
154
|
+
if (flags.verbose) {
|
|
155
|
+
this.logger.info({ options }, 'Decompose configuration');
|
|
156
|
+
}
|
|
157
|
+
// Create session directory
|
|
158
|
+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('.')[0];
|
|
159
|
+
const sessionDir = join(flags['output-dir'], `session-${timestamp}`);
|
|
160
|
+
await this.fileManager.createDirectory(sessionDir);
|
|
161
|
+
this.log(colors.info(`📁 Session directory: ${sessionDir}\n`));
|
|
162
|
+
// Phase 1: Decompose goal into task graph
|
|
163
|
+
this.log(colors.bold('⚙️ Phase 1: Decomposing goal into task graph...\n'));
|
|
164
|
+
const decompositionService = new TaskDecompositionService(createAgentRunner(flags.provider, this.logger), this.fileManager, new GlobMatcher(this.fileManager, this.logger), this.logger);
|
|
165
|
+
const taskGraph = await decompositionService.decomposeGoal(options, sessionDir);
|
|
166
|
+
this.log(colors.success('✓ Task graph generated!\n'));
|
|
167
|
+
this.displayTaskGraphSummary(taskGraph);
|
|
168
|
+
// Phase 2: Scaffold session structure
|
|
169
|
+
this.log(colors.bold('\n⚙️ Phase 2: Creating session structure...\n'));
|
|
170
|
+
await this.scaffolder.createSessionStructure(sessionDir, options.storyFormat);
|
|
171
|
+
const sessionConfig = {
|
|
172
|
+
createdAt: new Date(),
|
|
173
|
+
goal: args.goal,
|
|
174
|
+
options,
|
|
175
|
+
outputDir: sessionDir,
|
|
176
|
+
sessionId: taskGraph.session.id,
|
|
177
|
+
};
|
|
178
|
+
await this.scaffolder.writeGoalFile(sessionDir, sessionConfig);
|
|
179
|
+
await this.scaffolder.writeTaskGraphFile(sessionDir, taskGraph);
|
|
180
|
+
await this.scaffolder.writeMasterPromptFile(sessionDir, taskGraph);
|
|
181
|
+
await this.scaffolder.writeTaskPromptFiles(sessionDir, taskGraph);
|
|
182
|
+
await this.scaffolder.writeSessionReadme(sessionDir, taskGraph, options.storyFormat);
|
|
183
|
+
this.log(colors.success('✓ Session structure created!\n'));
|
|
184
|
+
this.log(colors.info(`📄 Task graph saved to: ${sessionDir}/task-graph.yaml`));
|
|
185
|
+
this.log(colors.info(`📄 Session README: ${sessionDir}/SESSION_README.md\n`));
|
|
186
|
+
// Phase 3: Execute (if not plan-only)
|
|
187
|
+
if (flags['plan-only']) {
|
|
188
|
+
this.log(colors.info('\n💡 Plan-only mode: Task graph generated but not executed.'));
|
|
189
|
+
this.log(colors.info(`To execute, run: bmad-cli decompose "${args.goal}" --execute\n`));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Ask for confirmation unless --execute flag is set
|
|
193
|
+
if (!flags.execute) {
|
|
194
|
+
const confirmed = await this.confirmExecution(taskGraph);
|
|
195
|
+
if (!confirmed) {
|
|
196
|
+
this.log(colors.warning('\n⏸️ Execution cancelled. Task graph saved for later use.'));
|
|
197
|
+
this.log(colors.info(`To execute later, review: ${sessionDir}/task-graph.yaml\n`));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.log(colors.bold('\n⚙️ Phase 3: Executing task graph...\n'));
|
|
202
|
+
const executor = new DependencyGraphExecutor(taskGraph, createAgentRunner(flags.provider, this.logger), new BatchProcessor(flags['max-parallel'], 0, this.logger), this.fileManager, this.logger, flags.cwd);
|
|
203
|
+
const executionResult = await executor.execute((layerIndex, totalLayers, layerSize) => {
|
|
204
|
+
this.log(colors.info(`\n🔄 Starting Layer ${layerIndex + 1}/${totalLayers} (${layerSize} task${layerSize > 1 ? 's' : ''} in parallel)`));
|
|
205
|
+
}, (taskId, _layerIndex, _taskIndex, _totalTasks) => {
|
|
206
|
+
this.log(colors.dim(` → Executing ${taskId}...`));
|
|
207
|
+
});
|
|
208
|
+
// Write execution report
|
|
209
|
+
await this.scaffolder.writeExecutionReport(sessionDir, taskGraph, executionResult);
|
|
210
|
+
// Display results
|
|
211
|
+
this.displayExecutionResults(executionResult, sessionDir);
|
|
212
|
+
// Check for cancellation
|
|
213
|
+
if (this.cancelled) {
|
|
214
|
+
this.log('\n' + colors.warning('⚠️ Execution cancelled by user'));
|
|
215
|
+
process.exit(130);
|
|
216
|
+
}
|
|
217
|
+
// Exit with appropriate code
|
|
218
|
+
if (!executionResult.success) {
|
|
219
|
+
this.error('Task graph execution completed with failures', { exit: 1 });
|
|
220
|
+
}
|
|
221
|
+
this.log('\n' + colors.success('✅ All tasks completed successfully!'));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.logger.error({ error: error.message }, 'Decompose command failed');
|
|
226
|
+
this.log('\n' + colors.error('✗ Decompose failed:'));
|
|
227
|
+
this.log(colors.error(` ${error.message}`));
|
|
228
|
+
this.error('Execution failed', { exit: 1 });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Confirm execution with user
|
|
233
|
+
*/
|
|
234
|
+
async confirmExecution(taskGraph) {
|
|
235
|
+
this.log(colors.warning('\n⚠️ Ready to execute task graph'));
|
|
236
|
+
this.log(colors.dim(` This will run ${taskGraph.metadata.totalTasks} tasks across ${taskGraph.metadata.executionLayers.length} layers.`));
|
|
237
|
+
this.log(colors.dim(` Estimated duration: ${taskGraph.metadata.estimatedDuration} minutes\n`));
|
|
238
|
+
// For now, default to yes in yolo mode
|
|
239
|
+
// In production, you'd use a proper prompt library
|
|
240
|
+
this.log(colors.info(' Press Ctrl+C to cancel, or Enter to continue...'));
|
|
241
|
+
// Simple confirmation
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Display execution results
|
|
246
|
+
*/
|
|
247
|
+
displayExecutionResults(result, sessionDir) {
|
|
248
|
+
this.log('\n' + colors.bold('╔════════════════════════════════════════════════════════╗'));
|
|
249
|
+
this.log(colors.bold('║ Execution Summary ║'));
|
|
250
|
+
this.log(colors.bold('╚════════════════════════════════════════════════════════╝') + '\n');
|
|
251
|
+
const headers = ['Metric', 'Value'];
|
|
252
|
+
const rows = [
|
|
253
|
+
['Total Tasks', String(result.totalTasks)],
|
|
254
|
+
[colors.success('✓ Completed'), colors.success(String(result.completedTasks))],
|
|
255
|
+
[colors.error('✗ Failed'), result.failedTasks > 0 ? colors.error(String(result.failedTasks)) : '0'],
|
|
256
|
+
[colors.warning('⊘ Skipped'), result.skippedTasks > 0 ? colors.warning(String(result.skippedTasks)) : '0'],
|
|
257
|
+
['Duration', `${Math.round(result.totalDuration / 1000)}s`],
|
|
258
|
+
['Success Rate', `${Math.round((result.completedTasks / result.totalTasks) * 100)}%`],
|
|
259
|
+
];
|
|
260
|
+
this.log(formatTable(headers, rows));
|
|
261
|
+
this.log(colors.info(`\n📊 Full execution report: ${sessionDir}/execution-report.yaml`));
|
|
262
|
+
this.log(colors.info(`📁 Task outputs: ${sessionDir}/outputs/\n`));
|
|
263
|
+
// Show failed tasks if any
|
|
264
|
+
if (result.failedTasks > 0) {
|
|
265
|
+
this.log(colors.error('\n❌ Failed Tasks:'));
|
|
266
|
+
for (const taskResult of result.taskResults) {
|
|
267
|
+
if (!taskResult.success) {
|
|
268
|
+
this.log(colors.error(` • ${taskResult.taskId}: ${taskResult.errors}`));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Display command header
|
|
275
|
+
*/
|
|
276
|
+
displayHeader(goal) {
|
|
277
|
+
const banner = formatBox('DECOMPOSE: Break Big Goals into Executable Tasks', `Goal: ${goal}`);
|
|
278
|
+
this.log('\n' + colors.bold(banner) + '\n');
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Display task graph summary
|
|
282
|
+
*/
|
|
283
|
+
displayTaskGraphSummary(taskGraph) {
|
|
284
|
+
this.log(colors.bold('📊 Task Graph Summary:'));
|
|
285
|
+
this.log(colors.dim(' ├─ Total Tasks: ') + colors.info(String(taskGraph.metadata.totalTasks)));
|
|
286
|
+
this.log(colors.dim(' ├─ Estimated Duration: ') + colors.info(`${taskGraph.metadata.estimatedDuration} minutes`));
|
|
287
|
+
this.log(colors.dim(' ├─ Execution Layers: ') + colors.info(String(taskGraph.metadata.executionLayers.length)));
|
|
288
|
+
this.log(colors.dim(' ├─ Max Parallelism: ') + colors.info(String(taskGraph.metadata.maxParallelism)));
|
|
289
|
+
// Check if we have any story-formatted tasks (check if task IDs don't start with "task-")
|
|
290
|
+
const isStoryFormat = taskGraph.tasks.some((t) => !t.id.startsWith('task-'));
|
|
291
|
+
if (isStoryFormat) {
|
|
292
|
+
this.log(colors.dim(' ├─ Format Mode: ') + colors.info('BMAD Stories'));
|
|
293
|
+
}
|
|
294
|
+
if (taskGraph.metadata.perFileMode) {
|
|
295
|
+
this.log(colors.dim(' └─ Per-File Mode: ') +
|
|
296
|
+
colors.info(`Enabled (${taskGraph.metadata.totalFiles ?? 0} files)`));
|
|
297
|
+
}
|
|
298
|
+
// Show execution layers
|
|
299
|
+
this.log(colors.bold('\n📋 Execution Plan:'));
|
|
300
|
+
for (let i = 0; i < taskGraph.metadata.executionLayers.length; i++) {
|
|
301
|
+
const layer = taskGraph.metadata.executionLayers[i];
|
|
302
|
+
const symbol = i === taskGraph.metadata.executionLayers.length - 1 ? '└─' : '├─';
|
|
303
|
+
const taskWord = isStoryFormat ? 'story/stories' : 'task(s)';
|
|
304
|
+
this.log(colors.dim(` ${symbol} Layer ${i + 1}: `) + colors.info(`${layer.length} ${taskWord} in parallel`));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Initialize services
|
|
309
|
+
*/
|
|
310
|
+
async initializeServices(provider) {
|
|
311
|
+
this.logger = createLogger({ namespace: 'commands:decompose' });
|
|
312
|
+
this.logger.info({ provider }, 'Initializing decompose services');
|
|
313
|
+
this.fileManager = new FileManager(this.logger);
|
|
314
|
+
this.scaffolder = new DecomposeSessionScaffolder(this.fileManager, this.logger);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Register signal handlers for graceful cancellation
|
|
318
|
+
*/
|
|
319
|
+
registerSignalHandlers() {
|
|
320
|
+
process.on('SIGINT', () => {
|
|
321
|
+
if (!this.cancelled) {
|
|
322
|
+
this.cancelled = true;
|
|
323
|
+
this.log('\n' + colors.warning('⚠️ Cancellation requested. Finishing current tasks...'));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* Demo command that showcases all terminal output utilities
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates colors, progress indicators, formatters, and countdown timers.
|
|
6
|
+
* Useful for verifying that all output utilities work correctly.
|
|
7
|
+
*/
|
|
8
|
+
export default class Demo extends Command {
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Helper method to delay execution
|
|
14
|
+
*
|
|
15
|
+
* @param ms - Milliseconds to delay
|
|
16
|
+
*/
|
|
17
|
+
private delay;
|
|
18
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import * as colors from '../utils/colors.js';
|
|
3
|
+
import { formatBox, formatCountdown, formatList, formatTable } from '../utils/formatters.js';
|
|
4
|
+
import { createLogger, generateCorrelationId } from '../utils/logger.js';
|
|
5
|
+
import { createSpinner, MultiStepProgress } from '../utils/progress.js';
|
|
6
|
+
/**
|
|
7
|
+
* Demo command that showcases all terminal output utilities
|
|
8
|
+
*
|
|
9
|
+
* Demonstrates colors, progress indicators, formatters, and countdown timers.
|
|
10
|
+
* Useful for verifying that all output utilities work correctly.
|
|
11
|
+
*/
|
|
12
|
+
export default class Demo extends Command {
|
|
13
|
+
static description = 'Demonstrate terminal output utilities (colors, progress, formatters)';
|
|
14
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
15
|
+
async run() {
|
|
16
|
+
const logger = createLogger({ namespace: 'commands:demo' });
|
|
17
|
+
const correlationId = generateCorrelationId();
|
|
18
|
+
logger.info({ command: 'demo', correlationId }, 'Starting demo command');
|
|
19
|
+
try {
|
|
20
|
+
// Section 1: Color utilities
|
|
21
|
+
this.log('\n' + colors.bold('═══ Section 1: Color Utilities ═══\n'));
|
|
22
|
+
this.log(colors.success('This is a success message'));
|
|
23
|
+
this.log(colors.error('This is an error message'));
|
|
24
|
+
this.log(colors.warning('This is a warning message'));
|
|
25
|
+
this.log(colors.info('This is an info message'));
|
|
26
|
+
this.log(colors.highlight('This is highlighted text'));
|
|
27
|
+
this.log(colors.dim('This is dimmed text'));
|
|
28
|
+
// Section 2: Simple spinner
|
|
29
|
+
this.log('\n' + colors.bold('═══ Section 2: Simple Spinner ═══\n'));
|
|
30
|
+
const spinner = createSpinner('Loading data...');
|
|
31
|
+
spinner.start();
|
|
32
|
+
await this.delay(2000);
|
|
33
|
+
spinner.succeed('Data loaded successfully');
|
|
34
|
+
// Section 3: Multi-step progress
|
|
35
|
+
this.log('\n' + colors.bold('═══ Section 3: Multi-Step Progress ═══\n'));
|
|
36
|
+
const steps = ['Parse configuration', 'Validate inputs', 'Execute workflow'];
|
|
37
|
+
const progress = new MultiStepProgress(steps);
|
|
38
|
+
// Sequential execution with await is intentional for demo
|
|
39
|
+
for (let i = 0; i < steps.length; i++) {
|
|
40
|
+
progress.start(i);
|
|
41
|
+
// eslint-disable-next-line no-await-in-loop
|
|
42
|
+
await this.delay(1500);
|
|
43
|
+
}
|
|
44
|
+
progress.succeed('All workflow steps completed');
|
|
45
|
+
// Section 4: Table formatting
|
|
46
|
+
this.log('\n' + colors.bold('═══ Section 4: Table Formatting ═══\n'));
|
|
47
|
+
const table = formatTable(['Epic', 'Status', 'Stories'], [
|
|
48
|
+
['Epic 1', 'Done', '5'],
|
|
49
|
+
['Epic 2', 'In Progress', '3'],
|
|
50
|
+
['Epic 3', 'Pending', '0'],
|
|
51
|
+
]);
|
|
52
|
+
this.log(table);
|
|
53
|
+
// Section 5: List formatting
|
|
54
|
+
this.log('\n' + colors.bold('═══ Section 5: List Formatting ═══\n'));
|
|
55
|
+
this.log(colors.info('Unordered list:'));
|
|
56
|
+
const unorderedList = formatList(['First item', 'Second item', 'Third item'], false);
|
|
57
|
+
this.log(unorderedList);
|
|
58
|
+
this.log('\n' + colors.info('Ordered list:'));
|
|
59
|
+
const orderedList = formatList(['Initialize project', 'Install dependencies', 'Run tests'], true);
|
|
60
|
+
this.log(orderedList);
|
|
61
|
+
// Section 6: Box formatting
|
|
62
|
+
this.log('\n' + colors.bold('═══ Section 6: Box Formatting ═══\n'));
|
|
63
|
+
const box = formatBox('Summary', `Total Epics: 3
|
|
64
|
+
Success: 2
|
|
65
|
+
Failed: 0
|
|
66
|
+
Pending: 1`);
|
|
67
|
+
this.log(box);
|
|
68
|
+
// Section 7: Countdown timer
|
|
69
|
+
this.log('\n' + colors.bold('═══ Section 7: Countdown Timer ═══\n'));
|
|
70
|
+
this.log(colors.info('Countdown examples:'));
|
|
71
|
+
this.log(` 0 seconds: ${formatCountdown(0)}`);
|
|
72
|
+
this.log(` 45 seconds: ${formatCountdown(45)}`);
|
|
73
|
+
this.log(` 90 seconds: ${formatCountdown(90)}`);
|
|
74
|
+
this.log(` 3600 seconds: ${formatCountdown(3600)}`);
|
|
75
|
+
this.log(` 3665 seconds: ${formatCountdown(3665)}`);
|
|
76
|
+
// Live countdown demonstration
|
|
77
|
+
this.log('\n' + colors.info('Live countdown (5 seconds):'));
|
|
78
|
+
const countdownSpinner = createSpinner('Next operation in...');
|
|
79
|
+
countdownSpinner.start();
|
|
80
|
+
// Sequential execution with await is intentional for demo
|
|
81
|
+
for (let i = 5; i > 0; i--) {
|
|
82
|
+
countdownSpinner.text = `Next operation in ${formatCountdown(i)}...`;
|
|
83
|
+
// eslint-disable-next-line no-await-in-loop
|
|
84
|
+
await this.delay(1000);
|
|
85
|
+
}
|
|
86
|
+
countdownSpinner.succeed('Countdown complete!');
|
|
87
|
+
// Final message
|
|
88
|
+
this.log('\n' + colors.success('Demo completed successfully!'));
|
|
89
|
+
logger.info({ correlationId }, 'Demo command completed');
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
logger.error({ correlationId, error }, 'Demo command failed');
|
|
93
|
+
this.log('\n' + colors.error('Demo encountered an error'));
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Helper method to delay execution
|
|
99
|
+
*
|
|
100
|
+
* @param ms - Milliseconds to delay
|
|
101
|
+
*/
|
|
102
|
+
async delay(ms) {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
setTimeout(resolve, ms);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Epics Create Command
|
|
3
|
+
*
|
|
4
|
+
* Creates epic files from a PRD document using Claude AI agents.
|
|
5
|
+
* Supports batch processing with progress indicators and idempotent behavior.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```bash
|
|
9
|
+
* bmad-workflow epics create docs/prd.md
|
|
10
|
+
* bmad-workflow epics create docs/prd.md --start 2 --count 3
|
|
11
|
+
* bmad-workflow epics create docs/prd.md --reference docs/architecture.md
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import { Command } from '@oclif/core';
|
|
15
|
+
/**
|
|
16
|
+
* Epics Create Command
|
|
17
|
+
*
|
|
18
|
+
* Creates epic markdown files from a PRD document by:
|
|
19
|
+
* 1. Parsing epics from PRD
|
|
20
|
+
* 2. Filtering based on flags (start, count)
|
|
21
|
+
* 3. Skipping existing epic files (idempotent)
|
|
22
|
+
* 4. Creating placeholder files
|
|
23
|
+
* 5. Running Claude AI agents to populate content
|
|
24
|
+
* 6. Displaying progress and summary
|
|
25
|
+
*/
|
|
26
|
+
export default class EpicsCreate extends Command {
|
|
27
|
+
static args: {
|
|
28
|
+
'prd-path': import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
29
|
+
};
|
|
30
|
+
static description: string;
|
|
31
|
+
static examples: string[];
|
|
32
|
+
static flags: {
|
|
33
|
+
count: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
|
+
interval: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
35
|
+
prefix: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
36
|
+
reference: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
37
|
+
start: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
38
|
+
agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
39
|
+
cwd: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
40
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* AI agent runner service (Claude or Gemini)
|
|
45
|
+
*/
|
|
46
|
+
private agentRunner;
|
|
47
|
+
/**
|
|
48
|
+
* File manager service
|
|
49
|
+
*/
|
|
50
|
+
private fileManager;
|
|
51
|
+
/**
|
|
52
|
+
* File scaffolder service
|
|
53
|
+
*/
|
|
54
|
+
private fileScaffolder;
|
|
55
|
+
/**
|
|
56
|
+
* Logger instance
|
|
57
|
+
*/
|
|
58
|
+
private logger;
|
|
59
|
+
/**
|
|
60
|
+
* Path resolver service
|
|
61
|
+
*/
|
|
62
|
+
private pathResolver;
|
|
63
|
+
/**
|
|
64
|
+
* PRD parser service
|
|
65
|
+
*/
|
|
66
|
+
private prdParser;
|
|
67
|
+
/**
|
|
68
|
+
* Execute the command
|
|
69
|
+
*/
|
|
70
|
+
run(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Build Claude CLI prompt for epic population
|
|
73
|
+
*/
|
|
74
|
+
private buildClaudePrompt;
|
|
75
|
+
/**
|
|
76
|
+
* Check for existing epic files to support idempotent behavior
|
|
77
|
+
*/
|
|
78
|
+
private checkExistingEpics;
|
|
79
|
+
/**
|
|
80
|
+
* Create epic files using Claude AI agents
|
|
81
|
+
*/
|
|
82
|
+
private createEpics;
|
|
83
|
+
/**
|
|
84
|
+
* Create a single epic file
|
|
85
|
+
*/
|
|
86
|
+
private createSingleEpic;
|
|
87
|
+
/**
|
|
88
|
+
* Display initial status before creating epics
|
|
89
|
+
*/
|
|
90
|
+
private displayInitialStatus;
|
|
91
|
+
/**
|
|
92
|
+
* Display summary report after creating epics
|
|
93
|
+
*/
|
|
94
|
+
private displaySummary;
|
|
95
|
+
/**
|
|
96
|
+
* Filter epics based on start and count flags
|
|
97
|
+
*/
|
|
98
|
+
private filterEpics;
|
|
99
|
+
/**
|
|
100
|
+
* Generate epic filename from epic data
|
|
101
|
+
*/
|
|
102
|
+
private generateEpicFileName;
|
|
103
|
+
/**
|
|
104
|
+
* Generate scaffolded content for epic file
|
|
105
|
+
*/
|
|
106
|
+
private generateScaffoldedContent;
|
|
107
|
+
/**
|
|
108
|
+
* Initialize service dependencies
|
|
109
|
+
*/
|
|
110
|
+
private initializeServices;
|
|
111
|
+
/**
|
|
112
|
+
* Parse PRD file and extract epics
|
|
113
|
+
*/
|
|
114
|
+
private parsePrd;
|
|
115
|
+
/**
|
|
116
|
+
* Validate PRD path exists
|
|
117
|
+
*/
|
|
118
|
+
private validatePrdPath;
|
|
119
|
+
/**
|
|
120
|
+
* Wait for specified interval with countdown
|
|
121
|
+
*/
|
|
122
|
+
private waitInterval;
|
|
123
|
+
}
|