@hyperdrive.bot/bmad-workflow 1.0.16 → 1.0.18

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.
@@ -0,0 +1,182 @@
1
+ /**
2
+ * WorkflowSessionScaffolder
3
+ *
4
+ * Creates structured session directories with spawn output files and summary reports.
5
+ * Provides persistent, browsable artifacts for troubleshooting and audit trails
6
+ * after each workflow run.
7
+ */
8
+ import type pino from 'pino';
9
+ import type { FileManager } from '../file-system/file-manager.js';
10
+ import type { WorkflowLogFile } from '../logging/workflow-logger.js';
11
+ /**
12
+ * Configuration for creating a workflow session
13
+ */
14
+ export interface WorkflowSessionConfig {
15
+ /** Base directory for workflow sessions (e.g., 'docs/workflow-sessions') */
16
+ baseDir: string;
17
+ /** Session prefix (e.g., 'workflow', 'bmad-run') */
18
+ prefix: string;
19
+ }
20
+ /**
21
+ * Spawn output payload structure
22
+ */
23
+ export interface SpawnOutputPayload {
24
+ /** Timestamp when spawn completed */
25
+ completedAt?: string;
26
+ /** Spawn output content */
27
+ content: string;
28
+ /** Spawn execution duration in milliseconds */
29
+ duration?: number;
30
+ /** Any errors from the spawn */
31
+ errors?: string;
32
+ /** Unique spawn identifier */
33
+ spawnId: string;
34
+ /** Whether the spawn succeeded */
35
+ success?: boolean;
36
+ /** Type of spawn: epic, story, or dev */
37
+ type: 'dev' | 'epic' | 'story';
38
+ }
39
+ /**
40
+ * Session report summary structure
41
+ */
42
+ export interface SessionReportSummary {
43
+ /** Total duration in milliseconds */
44
+ duration: number;
45
+ /** Session end timestamp */
46
+ endedAt: string;
47
+ /** List of errors encountered */
48
+ errors: Array<{
49
+ error: string;
50
+ identifier: string;
51
+ phase: string;
52
+ timestamp: string;
53
+ }>;
54
+ /** Overall success status */
55
+ overallSuccess: boolean;
56
+ /** Phase execution counts */
57
+ phases: {
58
+ developments: {
59
+ completed: number;
60
+ failed: number;
61
+ total: number;
62
+ };
63
+ epics: {
64
+ completed: number;
65
+ failed: number;
66
+ total: number;
67
+ };
68
+ stories: {
69
+ completed: number;
70
+ failed: number;
71
+ total: number;
72
+ };
73
+ };
74
+ /** Session identifier */
75
+ sessionId: string;
76
+ /** List of spawn files written */
77
+ spawnFiles: string[];
78
+ /** Session start timestamp */
79
+ startedAt: string;
80
+ }
81
+ /**
82
+ * Scaffolder for workflow session directory structure
83
+ */
84
+ export declare class WorkflowSessionScaffolder {
85
+ private readonly fileManager;
86
+ private readonly logger;
87
+ private sessionDir;
88
+ constructor(fileManager: FileManager, logger: pino.Logger);
89
+ /**
90
+ * Create the session directory structure
91
+ *
92
+ * Creates:
93
+ * - Session root directory at `{baseDir}/{prefix}-{timestamp}/`
94
+ * - `spawns/` subdirectory for individual spawn output files
95
+ *
96
+ * @param config - Session configuration
97
+ * @returns Session directory path
98
+ */
99
+ createSessionStructure(config: WorkflowSessionConfig): Promise<string>;
100
+ /**
101
+ * Get the current session directory path
102
+ *
103
+ * @returns Session directory path or null if not created
104
+ */
105
+ getSessionDir(): null | string;
106
+ /**
107
+ * Write session report markdown file
108
+ *
109
+ * Generates SESSION_REPORT.md with execution summary, pass/fail counts,
110
+ * durations, and error listings.
111
+ *
112
+ * @param summary - Session execution summary
113
+ */
114
+ writeSessionReport(summary: SessionReportSummary): Promise<void>;
115
+ /**
116
+ * Write individual spawn output file
117
+ *
118
+ * Writes immediately (no buffering) to ensure partial results survive crashes.
119
+ *
120
+ * @param spawnId - Unique spawn identifier
121
+ * @param type - Type of spawn (epic, story, dev)
122
+ * @param content - Spawn output content
123
+ */
124
+ writeSpawnOutput(spawnId: string, type: 'dev' | 'epic' | 'story', content: string): Promise<void>;
125
+ /**
126
+ * Write individual spawn output file with full payload
127
+ *
128
+ * Writes immediately (no buffering) to ensure partial results survive crashes.
129
+ *
130
+ * @param payload - Full spawn output payload
131
+ */
132
+ writeSpawnOutput(payload: SpawnOutputPayload): Promise<void>;
133
+ /**
134
+ * Write workflow log YAML file
135
+ *
136
+ * Generates workflow-log.yaml machine-parseable audit trail file.
137
+ *
138
+ * @param logData - Full workflow log data (WorkflowLogFile interface)
139
+ */
140
+ writeWorkflowLog(logData: WorkflowLogFile): Promise<void>;
141
+ /**
142
+ * Build markdown content for session report
143
+ *
144
+ * @param summary - Session report summary
145
+ * @returns Formatted markdown content
146
+ * @private
147
+ */
148
+ private buildSessionReportMarkdown;
149
+ /**
150
+ * Build markdown content for spawn output file
151
+ *
152
+ * @param payload - Spawn output payload
153
+ * @returns Formatted markdown content
154
+ * @private
155
+ */
156
+ private buildSpawnMarkdown;
157
+ /**
158
+ * Capitalize first letter of a string
159
+ *
160
+ * @param str - String to capitalize
161
+ * @returns Capitalized string
162
+ * @private
163
+ */
164
+ private capitalizeFirst;
165
+ /**
166
+ * Format duration in human-readable format
167
+ *
168
+ * @param ms - Duration in milliseconds
169
+ * @returns Formatted duration string
170
+ * @private
171
+ */
172
+ private formatDuration;
173
+ /**
174
+ * Generate timestamp string for session directory name
175
+ *
176
+ * Format: YYYYMMDD-HHmmss
177
+ *
178
+ * @returns Formatted timestamp
179
+ * @private
180
+ */
181
+ private generateTimestamp;
182
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * WorkflowSessionScaffolder
3
+ *
4
+ * Creates structured session directories with spawn output files and summary reports.
5
+ * Provides persistent, browsable artifacts for troubleshooting and audit trails
6
+ * after each workflow run.
7
+ */
8
+ import * as yaml from 'js-yaml';
9
+ import { join } from 'node:path';
10
+ /**
11
+ * Scaffolder for workflow session directory structure
12
+ */
13
+ export class WorkflowSessionScaffolder {
14
+ fileManager;
15
+ logger;
16
+ sessionDir = null;
17
+ constructor(fileManager, logger) {
18
+ this.fileManager = fileManager;
19
+ this.logger = logger;
20
+ }
21
+ /**
22
+ * Create the session directory structure
23
+ *
24
+ * Creates:
25
+ * - Session root directory at `{baseDir}/{prefix}-{timestamp}/`
26
+ * - `spawns/` subdirectory for individual spawn output files
27
+ *
28
+ * @param config - Session configuration
29
+ * @returns Session directory path
30
+ */
31
+ async createSessionStructure(config) {
32
+ const timestamp = this.generateTimestamp();
33
+ const sessionDirName = `${config.prefix}-${timestamp}`;
34
+ const sessionDir = join(config.baseDir, sessionDirName);
35
+ this.logger.info({ prefix: config.prefix, sessionDir }, 'Creating workflow session structure');
36
+ // Create root session directory
37
+ await this.fileManager.createDirectory(sessionDir);
38
+ // Create spawns subdirectory
39
+ await this.fileManager.createDirectory(join(sessionDir, 'spawns'));
40
+ this.sessionDir = sessionDir;
41
+ this.logger.info({ sessionDir }, 'Workflow session structure created');
42
+ return sessionDir;
43
+ }
44
+ /**
45
+ * Get the current session directory path
46
+ *
47
+ * @returns Session directory path or null if not created
48
+ */
49
+ getSessionDir() {
50
+ return this.sessionDir;
51
+ }
52
+ /**
53
+ * Write session report markdown file
54
+ *
55
+ * Generates SESSION_REPORT.md with execution summary, pass/fail counts,
56
+ * durations, and error listings.
57
+ *
58
+ * @param summary - Session execution summary
59
+ */
60
+ async writeSessionReport(summary) {
61
+ if (!this.sessionDir) {
62
+ throw new Error('Session structure must be created before writing session report');
63
+ }
64
+ const reportPath = join(this.sessionDir, 'SESSION_REPORT.md');
65
+ this.logger.info({ reportPath, sessionId: summary.sessionId }, 'Writing session report');
66
+ const markdownContent = this.buildSessionReportMarkdown(summary);
67
+ await this.fileManager.writeFile(reportPath, markdownContent);
68
+ this.logger.info({ reportPath }, 'Session report written');
69
+ }
70
+ async writeSpawnOutput(spawnIdOrPayload, type, content) {
71
+ if (!this.sessionDir) {
72
+ throw new Error('Session structure must be created before writing spawn output');
73
+ }
74
+ // Normalize to payload
75
+ const payload = typeof spawnIdOrPayload === 'string'
76
+ ? {
77
+ content: content,
78
+ spawnId: spawnIdOrPayload,
79
+ type: type,
80
+ }
81
+ : spawnIdOrPayload;
82
+ const fileName = `${payload.type}-${payload.spawnId}.md`;
83
+ const filePath = join(this.sessionDir, 'spawns', fileName);
84
+ this.logger.info({ filePath, spawnId: payload.spawnId, type: payload.type }, 'Writing spawn output');
85
+ // Build markdown content with metadata header
86
+ const markdownContent = this.buildSpawnMarkdown(payload);
87
+ // Immediate write for crash resilience
88
+ await this.fileManager.writeFile(filePath, markdownContent);
89
+ this.logger.info({ filePath, spawnId: payload.spawnId }, 'Spawn output written');
90
+ }
91
+ /**
92
+ * Write workflow log YAML file
93
+ *
94
+ * Generates workflow-log.yaml machine-parseable audit trail file.
95
+ *
96
+ * @param logData - Full workflow log data (WorkflowLogFile interface)
97
+ */
98
+ async writeWorkflowLog(logData) {
99
+ if (!this.sessionDir) {
100
+ throw new Error('Session structure must be created before writing workflow log');
101
+ }
102
+ const logPath = join(this.sessionDir, 'workflow-log.yaml');
103
+ this.logger.info({ logPath, workflowId: logData.metadata.workflowId }, 'Writing workflow log');
104
+ const yamlContent = yaml.dump(logData, {
105
+ indent: 2,
106
+ lineWidth: 120,
107
+ noRefs: true,
108
+ });
109
+ await this.fileManager.writeFile(logPath, yamlContent);
110
+ this.logger.info({ logPath }, 'Workflow log written');
111
+ }
112
+ /**
113
+ * Build markdown content for session report
114
+ *
115
+ * @param summary - Session report summary
116
+ * @returns Formatted markdown content
117
+ * @private
118
+ */
119
+ buildSessionReportMarkdown(summary) {
120
+ const statusIcon = summary.overallSuccess ? '✅' : '❌';
121
+ const durationStr = this.formatDuration(summary.duration);
122
+ let md = `# Workflow Session Report\n\n`;
123
+ // Metadata section
124
+ md += `## Metadata\n\n`;
125
+ md += `- **Session ID:** ${summary.sessionId}\n`;
126
+ md += `- **Status:** ${statusIcon} ${summary.overallSuccess ? 'Completed Successfully' : 'Completed with Failures'}\n`;
127
+ md += `- **Started:** ${summary.startedAt}\n`;
128
+ md += `- **Ended:** ${summary.endedAt}\n`;
129
+ md += `- **Duration:** ${durationStr}\n\n`;
130
+ // Execution summary table
131
+ md += `## Execution Summary\n\n`;
132
+ md += `| Phase | Total | Completed | Failed | Pass Rate |\n`;
133
+ md += `|-------|-------|-----------|--------|------------|\n`;
134
+ for (const [phase, counts] of Object.entries(summary.phases)) {
135
+ const passRate = counts.total > 0 ? ((counts.completed / counts.total) * 100).toFixed(1) : '0.0';
136
+ md += `| ${this.capitalizeFirst(phase)} | ${counts.total} | ${counts.completed} | ${counts.failed} | ${passRate}% |\n`;
137
+ }
138
+ md += `\n`;
139
+ // Error listings (if any)
140
+ if (summary.errors.length > 0) {
141
+ md += `## Errors (${summary.errors.length})\n\n`;
142
+ for (const error of summary.errors) {
143
+ md += `### ${error.phase.toUpperCase()} - ${error.identifier}\n\n`;
144
+ md += `- **Timestamp:** ${error.timestamp}\n`;
145
+ md += `- **Error:** ${error.error}\n\n`;
146
+ }
147
+ }
148
+ // Spawn file inventory
149
+ md += `## Spawn Files\n\n`;
150
+ if (summary.spawnFiles.length > 0) {
151
+ for (const file of summary.spawnFiles) {
152
+ md += `- \`${file}\`\n`;
153
+ }
154
+ }
155
+ else {
156
+ md += `_No spawn files generated_\n`;
157
+ }
158
+ md += `\n---\n\n`;
159
+ md += `<!-- Powered by BMAD™ Core -->\n`;
160
+ return md;
161
+ }
162
+ /**
163
+ * Build markdown content for spawn output file
164
+ *
165
+ * @param payload - Spawn output payload
166
+ * @returns Formatted markdown content
167
+ * @private
168
+ */
169
+ buildSpawnMarkdown(payload) {
170
+ const statusIcon = payload.success === false ? '❌' : payload.success === true ? '✅' : '⏳';
171
+ const durationStr = payload.duration ? this.formatDuration(payload.duration) : 'N/A';
172
+ let md = `# ${payload.type.toUpperCase()} Spawn: ${payload.spawnId}\n\n`;
173
+ md += `## Metadata\n\n`;
174
+ md += `- **Spawn ID:** ${payload.spawnId}\n`;
175
+ md += `- **Type:** ${payload.type}\n`;
176
+ md += `- **Status:** ${statusIcon} ${payload.success === false ? 'Failed' : payload.success === true ? 'Success' : 'Unknown'}\n`;
177
+ md += `- **Duration:** ${durationStr}\n`;
178
+ if (payload.completedAt) {
179
+ md += `- **Completed At:** ${payload.completedAt}\n`;
180
+ }
181
+ md += `\n`;
182
+ if (payload.errors) {
183
+ md += `## Errors\n\n`;
184
+ md += `\`\`\`\n${payload.errors}\n\`\`\`\n\n`;
185
+ }
186
+ md += `## Output\n\n`;
187
+ md += `${payload.content}\n\n`;
188
+ md += `---\n\n`;
189
+ md += `<!-- Powered by BMAD™ Core -->\n`;
190
+ return md;
191
+ }
192
+ /**
193
+ * Capitalize first letter of a string
194
+ *
195
+ * @param str - String to capitalize
196
+ * @returns Capitalized string
197
+ * @private
198
+ */
199
+ capitalizeFirst(str) {
200
+ return str.charAt(0).toUpperCase() + str.slice(1);
201
+ }
202
+ /**
203
+ * Format duration in human-readable format
204
+ *
205
+ * @param ms - Duration in milliseconds
206
+ * @returns Formatted duration string
207
+ * @private
208
+ */
209
+ formatDuration(ms) {
210
+ if (ms < 1000)
211
+ return `${ms}ms`;
212
+ if (ms < 60_000)
213
+ return `${(ms / 1000).toFixed(1)}s`;
214
+ if (ms < 3_600_000)
215
+ return `${(ms / 60_000).toFixed(1)}m`;
216
+ return `${(ms / 3_600_000).toFixed(1)}h`;
217
+ }
218
+ /**
219
+ * Generate timestamp string for session directory name
220
+ *
221
+ * Format: YYYYMMDD-HHmmss
222
+ *
223
+ * @returns Formatted timestamp
224
+ * @private
225
+ */
226
+ generateTimestamp() {
227
+ const now = new Date();
228
+ const year = now.getFullYear();
229
+ const month = String(now.getMonth() + 1).padStart(2, '0');
230
+ const day = String(now.getDate()).padStart(2, '0');
231
+ const hours = String(now.getHours()).padStart(2, '0');
232
+ const minutes = String(now.getMinutes()).padStart(2, '0');
233
+ const seconds = String(now.getSeconds()).padStart(2, '0');
234
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
235
+ }
236
+ }
@@ -1,36 +1,36 @@
1
1
  /**
2
- * Format a success message with green color and checkmark icon
2
+ * Format a success message with checkmark icon
3
3
  *
4
4
  * @param text - Message text to format
5
- * @returns Formatted string with ANSI color codes and ✓ icon
5
+ * @returns Formatted string with ✓ icon
6
6
  */
7
7
  export declare const success: (text: string) => string;
8
8
  /**
9
- * Format an error message with red color and X icon
9
+ * Format an error message with X icon
10
10
  *
11
11
  * @param text - Message text to format
12
- * @returns Formatted string with ANSI color codes and ✗ icon
12
+ * @returns Formatted string with ✗ icon
13
13
  */
14
14
  export declare const error: (text: string) => string;
15
15
  /**
16
- * Format a warning message with yellow color and warning icon
16
+ * Format a warning message with warning icon
17
17
  *
18
18
  * @param text - Message text to format
19
- * @returns Formatted string with ANSI color codes and ⚠ icon
19
+ * @returns Formatted string with ⚠ icon
20
20
  */
21
21
  export declare const warning: (text: string) => string;
22
22
  /**
23
- * Format an info message with blue color and info icon
23
+ * Format an info message with info icon
24
24
  *
25
25
  * @param text - Message text to format
26
- * @returns Formatted string with ANSI color codes and ℹ icon
26
+ * @returns Formatted string with ℹ icon
27
27
  */
28
28
  export declare const info: (text: string) => string;
29
29
  /**
30
- * Highlight text with cyan color for emphasis
30
+ * Highlight text for emphasis
31
31
  *
32
32
  * @param text - Text to highlight
33
- * @returns Formatted string with ANSI color codes
33
+ * @returns Formatted string with ANSI bold codes
34
34
  */
35
35
  export declare const highlight: (text: string) => string;
36
36
  /**
@@ -1,39 +1,39 @@
1
1
  import chalk from 'chalk';
2
2
  /**
3
- * Format a success message with green color and checkmark icon
3
+ * Format a success message with checkmark icon
4
4
  *
5
5
  * @param text - Message text to format
6
- * @returns Formatted string with ANSI color codes and ✓ icon
6
+ * @returns Formatted string with ✓ icon
7
7
  */
8
- export const success = (text) => chalk.green(`✓ ${text}`);
8
+ export const success = (text) => chalk.bold(`✓ ${text}`);
9
9
  /**
10
- * Format an error message with red color and X icon
10
+ * Format an error message with X icon
11
11
  *
12
12
  * @param text - Message text to format
13
- * @returns Formatted string with ANSI color codes and ✗ icon
13
+ * @returns Formatted string with ✗ icon
14
14
  */
15
- export const error = (text) => chalk.red(`✗ ${text}`);
15
+ export const error = (text) => chalk.bold(`✗ ${text}`);
16
16
  /**
17
- * Format a warning message with yellow color and warning icon
17
+ * Format a warning message with warning icon
18
18
  *
19
19
  * @param text - Message text to format
20
- * @returns Formatted string with ANSI color codes and ⚠ icon
20
+ * @returns Formatted string with ⚠ icon
21
21
  */
22
- export const warning = (text) => chalk.yellow(`⚠ ${text}`);
22
+ export const warning = (text) => chalk.bold(`⚠ ${text}`);
23
23
  /**
24
- * Format an info message with blue color and info icon
24
+ * Format an info message with info icon
25
25
  *
26
26
  * @param text - Message text to format
27
- * @returns Formatted string with ANSI color codes and ℹ icon
27
+ * @returns Formatted string with ℹ icon
28
28
  */
29
- export const info = (text) => chalk.blue(`ℹ ${text}`);
29
+ export const info = (text) => `ℹ ${text}`;
30
30
  /**
31
- * Highlight text with cyan color for emphasis
31
+ * Highlight text for emphasis
32
32
  *
33
33
  * @param text - Text to highlight
34
- * @returns Formatted string with ANSI color codes
34
+ * @returns Formatted string with ANSI bold codes
35
35
  */
36
- export const highlight = (text) => chalk.cyan(text);
36
+ export const highlight = (text) => chalk.bold(text);
37
37
  /**
38
38
  * Format text in bold style
39
39
  *