@hyperdrive.bot/bmad-workflow 1.0.17 → 1.0.19
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/dist/commands/config/show.js +8 -2
- package/dist/commands/decompose.js +26 -5
- package/dist/commands/epics/create.d.ts +1 -0
- package/dist/commands/mcp/add.d.ts +16 -0
- package/dist/commands/mcp/add.js +77 -0
- package/dist/commands/mcp/credential/get.d.ts +14 -0
- package/dist/commands/mcp/credential/get.js +35 -0
- package/dist/commands/mcp/credential/list.d.ts +17 -0
- package/dist/commands/mcp/credential/list.js +67 -0
- package/dist/commands/mcp/credential/remove.d.ts +18 -0
- package/dist/commands/mcp/credential/remove.js +84 -0
- package/dist/commands/mcp/credential/set.d.ts +16 -0
- package/dist/commands/mcp/credential/set.js +41 -0
- package/dist/commands/mcp/credential/validate.d.ts +12 -0
- package/dist/commands/mcp/credential/validate.js +150 -0
- package/dist/commands/mcp/list.d.ts +17 -0
- package/dist/commands/mcp/list.js +80 -0
- package/dist/commands/mcp/logs.d.ts +15 -0
- package/dist/commands/mcp/logs.js +64 -0
- package/dist/commands/mcp/preset.d.ts +15 -0
- package/dist/commands/mcp/preset.js +84 -0
- package/dist/commands/mcp/remove.d.ts +14 -0
- package/dist/commands/mcp/remove.js +36 -0
- package/dist/commands/mcp/start.d.ts +12 -0
- package/dist/commands/mcp/start.js +80 -0
- package/dist/commands/mcp/status.d.ts +30 -0
- package/dist/commands/mcp/status.js +180 -0
- package/dist/commands/mcp/stop.d.ts +12 -0
- package/dist/commands/mcp/stop.js +47 -0
- package/dist/commands/stories/create.d.ts +1 -0
- package/dist/commands/stories/develop.d.ts +1 -0
- package/dist/commands/stories/qa.js +34 -75
- package/dist/commands/stories/review.d.ts +124 -0
- package/dist/commands/stories/review.js +516 -0
- package/dist/commands/workflow.d.ts +89 -0
- package/dist/commands/workflow.js +487 -14
- package/dist/mcp/types.d.ts +99 -0
- package/dist/mcp/types.js +7 -0
- package/dist/mcp/utils/docker-utils.d.ts +56 -0
- package/dist/mcp/utils/docker-utils.js +108 -0
- package/dist/mcp/utils/template-loader.d.ts +21 -0
- package/dist/mcp/utils/template-loader.js +60 -0
- package/dist/models/agent-options.d.ts +10 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/workflow-callbacks.d.ts +251 -0
- package/dist/models/workflow-callbacks.js +10 -0
- package/dist/models/workflow-config.d.ts +77 -0
- package/dist/models/workflow-result.d.ts +7 -0
- package/dist/services/WorkflowReporter.d.ts +165 -0
- package/dist/services/WorkflowReporter.js +691 -0
- package/dist/services/agents/claude-agent-runner.js +25 -4
- package/dist/services/file-system/path-resolver.d.ts +10 -0
- package/dist/services/file-system/path-resolver.js +12 -0
- package/dist/services/mcp/mcp-config-manager.d.ts +54 -0
- package/dist/services/mcp/mcp-config-manager.js +146 -0
- package/dist/services/mcp/mcp-context-injector.d.ts +92 -0
- package/dist/services/mcp/mcp-context-injector.js +168 -0
- package/dist/services/mcp/mcp-credential-manager.d.ts +48 -0
- package/dist/services/mcp/mcp-credential-manager.js +124 -0
- package/dist/services/mcp/mcp-health-checker.d.ts +56 -0
- package/dist/services/mcp/mcp-health-checker.js +162 -0
- package/dist/services/mcp/types/health-types.d.ts +31 -0
- package/dist/services/mcp/types/health-types.js +7 -0
- package/dist/services/orchestration/dependency-graph-executor.js +1 -1
- package/dist/services/orchestration/task-decomposition-service.d.ts +2 -1
- package/dist/services/orchestration/task-decomposition-service.js +90 -36
- package/dist/services/orchestration/workflow-orchestrator.d.ts +87 -3
- package/dist/services/orchestration/workflow-orchestrator.js +1169 -289
- package/dist/services/review/ai-review-scanner.d.ts +66 -0
- package/dist/services/review/ai-review-scanner.js +142 -0
- package/dist/services/review/coderabbit-scanner.d.ts +25 -0
- package/dist/services/review/coderabbit-scanner.js +31 -0
- package/dist/services/review/index.d.ts +20 -0
- package/dist/services/review/index.js +15 -0
- package/dist/services/review/lint-scanner.d.ts +46 -0
- package/dist/services/review/lint-scanner.js +172 -0
- package/dist/services/review/review-config.d.ts +62 -0
- package/dist/services/review/review-config.js +91 -0
- package/dist/services/review/review-phase-executor.d.ts +69 -0
- package/dist/services/review/review-phase-executor.js +152 -0
- package/dist/services/review/review-queue.d.ts +98 -0
- package/dist/services/review/review-queue.js +174 -0
- package/dist/services/review/review-reporter.d.ts +94 -0
- package/dist/services/review/review-reporter.js +386 -0
- package/dist/services/review/scanner-factory.d.ts +42 -0
- package/dist/services/review/scanner-factory.js +60 -0
- package/dist/services/review/self-heal-loop.d.ts +58 -0
- package/dist/services/review/self-heal-loop.js +132 -0
- package/dist/services/review/severity-classifier.d.ts +17 -0
- package/dist/services/review/severity-classifier.js +314 -0
- package/dist/services/review/tech-debt-tracker.d.ts +52 -0
- package/dist/services/review/tech-debt-tracker.js +245 -0
- package/dist/services/review/types.d.ts +93 -0
- package/dist/services/review/types.js +23 -0
- package/dist/services/scaffolding/workflow-session-scaffolder.d.ts +182 -0
- package/dist/services/scaffolding/workflow-session-scaffolder.js +236 -0
- package/dist/services/validation/config-validator.d.ts +84 -0
- package/dist/services/validation/config-validator.js +78 -0
- package/dist/utils/colors.d.ts +10 -10
- package/dist/utils/colors.js +15 -15
- package/dist/utils/credential-utils.d.ts +14 -0
- package/dist/utils/credential-utils.js +19 -0
- package/dist/utils/duration.d.ts +41 -0
- package/dist/utils/duration.js +89 -0
- package/dist/utils/listr2-helpers.d.ts +216 -0
- package/dist/utils/listr2-helpers.js +334 -0
- package/dist/utils/shared-flags.d.ts +1 -0
- package/dist/utils/shared-flags.js +11 -2
- package/package.json +6 -3
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* listr2 Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Utility functions for creating and managing listr2 task structures
|
|
5
|
+
* used by the WorkflowReporter for visualizing workflow progress.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { formatDuration } from './formatters.js';
|
|
9
|
+
/**
|
|
10
|
+
* Default terminal width constraint for output formatting
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_TERMINAL_WIDTH = 80;
|
|
13
|
+
/**
|
|
14
|
+
* Regex pattern for detecting ANSI escape codes
|
|
15
|
+
*/
|
|
16
|
+
export const ANSI_ESCAPE_PATTERN = /\u001B\[[0-9;]*[a-zA-Z]/g;
|
|
17
|
+
/**
|
|
18
|
+
* Strip ANSI escape codes from text
|
|
19
|
+
*
|
|
20
|
+
* @param text - Text potentially containing ANSI codes
|
|
21
|
+
* @returns Clean text without ANSI escape sequences
|
|
22
|
+
*/
|
|
23
|
+
export const stripAnsi = (text) => text.replaceAll(ANSI_ESCAPE_PATTERN, '');
|
|
24
|
+
/**
|
|
25
|
+
* Check if the current environment is a TTY
|
|
26
|
+
*
|
|
27
|
+
* @returns true if stdout is a TTY, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
export const isTTY = () => process.stdout.isTTY === true;
|
|
30
|
+
/**
|
|
31
|
+
* Plain-text status indicators (no ANSI codes)
|
|
32
|
+
*/
|
|
33
|
+
export const PLAIN_STATUS_INDICATORS = {
|
|
34
|
+
completed: '[+]',
|
|
35
|
+
failed: '[X]',
|
|
36
|
+
queued: '[.]',
|
|
37
|
+
running: '[*]',
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Format a plain-text phase header for non-TTY output
|
|
41
|
+
*
|
|
42
|
+
* @param phaseName - Phase name (epic, story, dev, qa)
|
|
43
|
+
* @returns Plain-text header string (e.g., "=== Phase: Epic Generation ===")
|
|
44
|
+
*/
|
|
45
|
+
export const formatPlainPhaseHeader = (phaseName) => {
|
|
46
|
+
const title = PHASE_TITLES[phaseName.toLowerCase()] || `${phaseName} Phase`;
|
|
47
|
+
return `=== Phase: ${title} ===`;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Format a plain-text spawn marker for non-TTY output
|
|
51
|
+
*
|
|
52
|
+
* @param itemId - Item identifier
|
|
53
|
+
* @param status - Spawn status (STARTED, COMPLETED, FAILED)
|
|
54
|
+
* @param durationMs - Optional duration in milliseconds
|
|
55
|
+
* @param error - Optional error message for failed spawns
|
|
56
|
+
* @returns Plain-text marker string (e.g., "[SPAWN] story-1.001 STARTED")
|
|
57
|
+
*/
|
|
58
|
+
export const formatPlainSpawnMarker = (itemId, status, durationMs, error) => {
|
|
59
|
+
const duration = durationMs === undefined ? '' : ` (${formatDuration(durationMs)})`;
|
|
60
|
+
const errorSuffix = error ? `: ${error}` : '';
|
|
61
|
+
return `[SPAWN] ${itemId} ${status}${duration}${errorSuffix}`;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Format a plain-text summary for non-TTY output
|
|
65
|
+
*
|
|
66
|
+
* @param totalSpawns - Total number of spawns
|
|
67
|
+
* @param passedCount - Number of passed spawns
|
|
68
|
+
* @param failedCount - Number of failed spawns
|
|
69
|
+
* @param durationMs - Total duration in milliseconds
|
|
70
|
+
* @returns Plain-text summary string
|
|
71
|
+
*/
|
|
72
|
+
export const formatPlainSummary = (totalSpawns, passedCount, failedCount, durationMs) => {
|
|
73
|
+
const lines = [
|
|
74
|
+
'--- Summary ---',
|
|
75
|
+
`Total: ${totalSpawns} spawns`,
|
|
76
|
+
`Passed: ${passedCount}`,
|
|
77
|
+
`Failed: ${failedCount}`,
|
|
78
|
+
`Duration: ${formatDuration(durationMs)}`,
|
|
79
|
+
];
|
|
80
|
+
return lines.join('\n');
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Format spawn output with visual delimiters for verbose mode
|
|
84
|
+
*
|
|
85
|
+
* @param spawnId - Spawn identifier
|
|
86
|
+
* @param output - Output content from the spawn
|
|
87
|
+
* @param maxWidth - Maximum line width (default: 80)
|
|
88
|
+
* @returns Formatted output with header/footer delimiters
|
|
89
|
+
*/
|
|
90
|
+
export const formatVerboseSpawnOutput = (spawnId, output, maxWidth = DEFAULT_TERMINAL_WIDTH) => {
|
|
91
|
+
// Create delimiter line that fits within maxWidth
|
|
92
|
+
const headerText = `── ${spawnId} output ──`;
|
|
93
|
+
const footerText = `── end ${spawnId} ──`;
|
|
94
|
+
// Pad the delimiter to maxWidth with dashes
|
|
95
|
+
const headerPadding = Math.max(0, maxWidth - headerText.length);
|
|
96
|
+
const footerPadding = Math.max(0, maxWidth - footerText.length);
|
|
97
|
+
const header = headerText + '─'.repeat(headerPadding);
|
|
98
|
+
const footer = footerText + '─'.repeat(footerPadding);
|
|
99
|
+
// Wrap output lines to respect width constraint
|
|
100
|
+
const wrappedLines = wrapLines(output, maxWidth);
|
|
101
|
+
return `${header}\n${wrappedLines}\n${footer}`;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Wrap text lines to fit within a maximum width
|
|
105
|
+
*
|
|
106
|
+
* @param text - Text to wrap
|
|
107
|
+
* @param maxWidth - Maximum line width
|
|
108
|
+
* @returns Wrapped text
|
|
109
|
+
*/
|
|
110
|
+
export const wrapLines = (text, maxWidth) => {
|
|
111
|
+
const lines = text.split('\n');
|
|
112
|
+
const wrappedLines = [];
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
if (line.length <= maxWidth) {
|
|
115
|
+
wrappedLines.push(line);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Simple word-based wrapping
|
|
119
|
+
let currentLine = '';
|
|
120
|
+
const words = line.split(' ');
|
|
121
|
+
for (const word of words) {
|
|
122
|
+
if (currentLine.length === 0) {
|
|
123
|
+
currentLine = word;
|
|
124
|
+
}
|
|
125
|
+
else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
126
|
+
currentLine += ' ' + word;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
wrappedLines.push(currentLine);
|
|
130
|
+
currentLine = word;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (currentLine.length > 0) {
|
|
134
|
+
wrappedLines.push(currentLine);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return wrappedLines.join('\n');
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Phase emoji mapping for visual consistency
|
|
142
|
+
*/
|
|
143
|
+
export const PHASE_EMOJI = {
|
|
144
|
+
dev: '🛠️',
|
|
145
|
+
epic: '📋',
|
|
146
|
+
qa: '✅',
|
|
147
|
+
story: '📝',
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Phase display names for titles
|
|
151
|
+
*/
|
|
152
|
+
export const PHASE_TITLES = {
|
|
153
|
+
dev: 'Dev',
|
|
154
|
+
epic: 'Epics',
|
|
155
|
+
qa: 'QA',
|
|
156
|
+
story: 'Stories',
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Action verbs for spawn labels — maps phase to what the spawn is DOING (lowercase)
|
|
160
|
+
*/
|
|
161
|
+
export const PHASE_ACTION_VERBS = {
|
|
162
|
+
dev: 'developing',
|
|
163
|
+
epic: 'generating',
|
|
164
|
+
qa: 'reviewing',
|
|
165
|
+
story: 'creating',
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Get the action verb for a phase (e.g., "Creating" for story phase)
|
|
169
|
+
*/
|
|
170
|
+
export const getPhaseActionVerb = (phaseName) => PHASE_ACTION_VERBS[phaseName.toLowerCase()] || 'Processing';
|
|
171
|
+
/**
|
|
172
|
+
* Status indicators for spawn states
|
|
173
|
+
*/
|
|
174
|
+
export const STATUS_INDICATORS = {
|
|
175
|
+
completed: chalk.bold('✓'),
|
|
176
|
+
failed: chalk.bold('✗'),
|
|
177
|
+
queued: chalk.dim('◯'),
|
|
178
|
+
running: chalk.bold('◉'),
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Get emoji for a phase name
|
|
182
|
+
*
|
|
183
|
+
* @param phaseName - Phase name (epic, story, dev, qa)
|
|
184
|
+
* @returns Emoji string for the phase
|
|
185
|
+
*/
|
|
186
|
+
export const getPhaseEmoji = (phaseName) => PHASE_EMOJI[phaseName.toLowerCase()] || '⚙️';
|
|
187
|
+
/**
|
|
188
|
+
* Create a formatted phase title with emoji
|
|
189
|
+
*
|
|
190
|
+
* @param phaseName - Phase name (epic, story, dev, qa)
|
|
191
|
+
* @returns Formatted title string with emoji (e.g., "📋 Epic Phase")
|
|
192
|
+
*/
|
|
193
|
+
export const createPhaseTitle = (phaseName) => {
|
|
194
|
+
const emoji = getPhaseEmoji(phaseName);
|
|
195
|
+
const title = PHASE_TITLES[phaseName.toLowerCase()] || `${phaseName} Phase`;
|
|
196
|
+
return `${emoji} ${title}`;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Create a phase task group configuration for listr2
|
|
200
|
+
*
|
|
201
|
+
* @param phaseName - Phase name (epic, story, dev, qa)
|
|
202
|
+
* @returns Object with title and phase metadata
|
|
203
|
+
*/
|
|
204
|
+
export const createPhaseTaskGroup = (phaseName) => ({
|
|
205
|
+
emoji: getPhaseEmoji(phaseName),
|
|
206
|
+
phaseName: phaseName.toLowerCase(),
|
|
207
|
+
title: createPhaseTitle(phaseName),
|
|
208
|
+
});
|
|
209
|
+
/**
|
|
210
|
+
* Create a layer task group configuration for listr2
|
|
211
|
+
*
|
|
212
|
+
* @param layerIndex - Zero-based layer index
|
|
213
|
+
* @param spawnCount - Number of spawns in the layer
|
|
214
|
+
* @param totalLayers - Total number of layers in the phase
|
|
215
|
+
* @returns Object with title and layer metadata
|
|
216
|
+
*/
|
|
217
|
+
export const createLayerTaskGroup = (layerIndex, spawnCount, totalLayers) => {
|
|
218
|
+
const layerNumber = layerIndex + 1;
|
|
219
|
+
const title = `Layer ${layerNumber}/${totalLayers} (${spawnCount} ${spawnCount === 1 ? 'spawn' : 'spawns'})`;
|
|
220
|
+
return {
|
|
221
|
+
layerIndex,
|
|
222
|
+
spawnCount,
|
|
223
|
+
title,
|
|
224
|
+
totalLayers,
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Format spawn status for display
|
|
229
|
+
*
|
|
230
|
+
* @param status - Current spawn status
|
|
231
|
+
* @param durationMs - Duration in milliseconds (optional)
|
|
232
|
+
* @param itemCount - Number of items processed (optional, for completed status)
|
|
233
|
+
* @param errorMessage - Error message (optional, for failed status)
|
|
234
|
+
* @returns Formatted status string
|
|
235
|
+
*/
|
|
236
|
+
export const formatSpawnStatus = (status, durationMs, itemCount, errorMessage) => {
|
|
237
|
+
const indicator = STATUS_INDICATORS[status];
|
|
238
|
+
const duration = durationMs === undefined ? '' : formatDuration(durationMs);
|
|
239
|
+
switch (status) {
|
|
240
|
+
case 'completed': {
|
|
241
|
+
if (itemCount !== undefined) {
|
|
242
|
+
return duration
|
|
243
|
+
? `${indicator} completed (${duration}) → ${itemCount} ${itemCount === 1 ? 'item' : 'items'}`
|
|
244
|
+
: `${indicator} completed → ${itemCount} ${itemCount === 1 ? 'item' : 'items'}`;
|
|
245
|
+
}
|
|
246
|
+
return duration ? `${indicator} completed (${duration})` : `${indicator} completed`;
|
|
247
|
+
}
|
|
248
|
+
case 'failed': {
|
|
249
|
+
return errorMessage
|
|
250
|
+
? `${indicator} failed: ${errorMessage}`
|
|
251
|
+
: `${indicator} failed`;
|
|
252
|
+
}
|
|
253
|
+
case 'queued': {
|
|
254
|
+
return `${indicator} queued`;
|
|
255
|
+
}
|
|
256
|
+
case 'running': {
|
|
257
|
+
return duration ? `${indicator} running (${duration})` : `${indicator} running`;
|
|
258
|
+
}
|
|
259
|
+
default: {
|
|
260
|
+
return `${indicator} ${status}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Format a layer summary line for collapsed display
|
|
266
|
+
*
|
|
267
|
+
* @param result - Layer execution results
|
|
268
|
+
* @returns Formatted summary string
|
|
269
|
+
*/
|
|
270
|
+
export const formatLayerSummary = (result) => {
|
|
271
|
+
const layerNumber = result.layerIndex + 1;
|
|
272
|
+
const duration = formatDuration(result.durationMs);
|
|
273
|
+
const passedText = `${result.successCount}/${result.spawnCount} passed`;
|
|
274
|
+
const statusColor = result.failureCount > 0
|
|
275
|
+
? chalk.bold
|
|
276
|
+
: result.successCount === result.spawnCount
|
|
277
|
+
? chalk.dim
|
|
278
|
+
: chalk.underline;
|
|
279
|
+
return `Layer ${layerNumber} (${result.spawnCount} ${result.spawnCount === 1 ? 'spawn' : 'spawns'}) — completed in ${duration} [${statusColor(passedText)}]`;
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Format a phase summary for completion display
|
|
283
|
+
*
|
|
284
|
+
* @param result - Phase execution results
|
|
285
|
+
* @returns Formatted summary string
|
|
286
|
+
*/
|
|
287
|
+
export const formatPhaseSummary = (result) => {
|
|
288
|
+
const emoji = getPhaseEmoji(result.phaseName);
|
|
289
|
+
const title = PHASE_TITLES[result.phaseName.toLowerCase()] || `${result.phaseName} Phase`;
|
|
290
|
+
const duration = formatDuration(result.durationMs);
|
|
291
|
+
const passedText = `${result.successCount}/${result.itemCount}`;
|
|
292
|
+
const statusIcon = result.failureCount > 0
|
|
293
|
+
? chalk.bold('✗')
|
|
294
|
+
: result.successCount === result.itemCount
|
|
295
|
+
? chalk.bold('✓')
|
|
296
|
+
: chalk.bold('⚠');
|
|
297
|
+
return `${emoji} ${title} ${statusIcon} — ${passedText} in ${duration}`;
|
|
298
|
+
};
|
|
299
|
+
/**
|
|
300
|
+
* Create a spawn task title
|
|
301
|
+
*
|
|
302
|
+
* @param itemId - Item identifier (epic number, story number, etc.)
|
|
303
|
+
* @param itemTitle - Optional item title/description
|
|
304
|
+
* @param agentType - Type of agent being spawned
|
|
305
|
+
* @returns Formatted spawn task title
|
|
306
|
+
*/
|
|
307
|
+
export const createSpawnTitle = (itemId, itemTitle, agentType) => {
|
|
308
|
+
const prefix = agentType ? chalk.dim(`[${agentType}]`) : '';
|
|
309
|
+
const title = itemTitle ? `${itemId}: ${itemTitle}` : itemId;
|
|
310
|
+
return prefix ? `${prefix} ${title}` : title;
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* Truncate a string to a maximum length with ellipsis
|
|
314
|
+
*
|
|
315
|
+
* @param text - Text to truncate
|
|
316
|
+
* @param maxLength - Maximum length (default: 60)
|
|
317
|
+
* @returns Truncated string with ellipsis if needed
|
|
318
|
+
*/
|
|
319
|
+
export const truncateText = (text, maxLength = 60) => {
|
|
320
|
+
if (text.length <= maxLength) {
|
|
321
|
+
return text;
|
|
322
|
+
}
|
|
323
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Format elapsed time for running tasks (updates every second)
|
|
327
|
+
*
|
|
328
|
+
* @param startTime - Start timestamp in milliseconds
|
|
329
|
+
* @returns Formatted elapsed time string
|
|
330
|
+
*/
|
|
331
|
+
export const formatElapsedTime = (startTime) => {
|
|
332
|
+
const elapsed = Date.now() - startTime;
|
|
333
|
+
return formatDuration(elapsed);
|
|
334
|
+
};
|
|
@@ -27,6 +27,7 @@ export declare const agentFlags: {
|
|
|
27
27
|
provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
28
|
task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
29
|
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
'review-timeout': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
31
|
'max-retries': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
32
|
'retry-backoff': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
33
|
};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
import { Flags } from '@oclif/core';
|
|
18
|
+
import { parseDuration } from './duration.js';
|
|
18
19
|
/**
|
|
19
20
|
* Agent, Task, and Provider flags for customizing command behavior
|
|
20
21
|
*
|
|
@@ -44,9 +45,17 @@ export const agentFlags = {
|
|
|
44
45
|
description: 'Override which task command to execute (e.g., develop-story, draft, review-implementation). Defaults to command-appropriate task.',
|
|
45
46
|
helpGroup: 'Agent Customization',
|
|
46
47
|
}),
|
|
47
|
-
timeout: Flags.
|
|
48
|
+
timeout: Flags.custom({
|
|
49
|
+
parse: async (input) => parseDuration(input),
|
|
50
|
+
})({
|
|
48
51
|
default: 2_700_000,
|
|
49
|
-
description: 'Agent execution timeout
|
|
52
|
+
description: 'Agent execution timeout — accepts durations like 30s, 5m, 1h, 90m, or raw milliseconds (default: 45m)',
|
|
53
|
+
helpGroup: 'Resilience',
|
|
54
|
+
}),
|
|
55
|
+
'review-timeout': Flags.custom({
|
|
56
|
+
parse: async (input) => parseDuration(input),
|
|
57
|
+
})({
|
|
58
|
+
description: 'AI review scanner timeout — overrides --timeout for review phase only (default: 5m)',
|
|
50
59
|
helpGroup: 'Resilience',
|
|
51
60
|
}),
|
|
52
61
|
'max-retries': Flags.integer({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperdrive.bot/bmad-workflow",
|
|
3
3
|
"description": "AI-driven development workflow orchestration CLI for BMAD projects",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.19",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DevSquad",
|
|
7
7
|
"email": "marcelo@devsquad.email",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"url": "https://gitlab.com/dev_squad/repo/cli/bmad-orchestrator/issues"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@hyperdrive.bot/plugin-telemetry": "file:../telemetry-plugin",
|
|
17
18
|
"@oclif/core": "^4",
|
|
18
19
|
"@oclif/plugin-help": "^6",
|
|
19
20
|
"bcrypt": "^6.0.0",
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"fs-extra": "^11.3.2",
|
|
24
25
|
"js-yaml": "^4.1.0",
|
|
25
26
|
"jsonwebtoken": "^9.0.2",
|
|
27
|
+
"listr2": "^10.1.0",
|
|
26
28
|
"nodemailer": "^7.0.9",
|
|
27
29
|
"ora": "^9.0.0",
|
|
28
30
|
"pino": "^10.0.0",
|
|
@@ -52,10 +54,10 @@
|
|
|
52
54
|
"eslint": "^9",
|
|
53
55
|
"eslint-config-oclif": "^6",
|
|
54
56
|
"eslint-config-prettier": "^10",
|
|
57
|
+
"esmock": "^2.7.3",
|
|
55
58
|
"mocha": "^10.8.2",
|
|
56
59
|
"oclif": "^4",
|
|
57
60
|
"prettier": "^3.6.2",
|
|
58
|
-
"esmock": "^2.7.3",
|
|
59
61
|
"proxyquire": "^2.1.3",
|
|
60
62
|
"sinon": "^17.0.1",
|
|
61
63
|
"ts-node": "^10.9.2",
|
|
@@ -88,7 +90,8 @@
|
|
|
88
90
|
"dirname": "bmad-workflow",
|
|
89
91
|
"commands": "./dist/commands",
|
|
90
92
|
"plugins": [
|
|
91
|
-
"@oclif/plugin-help"
|
|
93
|
+
"@oclif/plugin-help",
|
|
94
|
+
"@hyperdrive.bot/plugin-telemetry"
|
|
92
95
|
],
|
|
93
96
|
"topicSeparator": " "
|
|
94
97
|
},
|