@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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1017 -0
  3. package/bin/dev +5 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/dev.js +5 -0
  6. package/bin/run +5 -0
  7. package/bin/run.cmd +3 -0
  8. package/bin/run.js +5 -0
  9. package/dist/commands/config/show.d.ts +34 -0
  10. package/dist/commands/config/show.js +108 -0
  11. package/dist/commands/config/validate.d.ts +29 -0
  12. package/dist/commands/config/validate.js +131 -0
  13. package/dist/commands/decompose.d.ts +79 -0
  14. package/dist/commands/decompose.js +327 -0
  15. package/dist/commands/demo.d.ts +18 -0
  16. package/dist/commands/demo.js +107 -0
  17. package/dist/commands/epics/create.d.ts +123 -0
  18. package/dist/commands/epics/create.js +459 -0
  19. package/dist/commands/epics/list.d.ts +120 -0
  20. package/dist/commands/epics/list.js +280 -0
  21. package/dist/commands/hello/index.d.ts +12 -0
  22. package/dist/commands/hello/index.js +34 -0
  23. package/dist/commands/hello/world.d.ts +8 -0
  24. package/dist/commands/hello/world.js +24 -0
  25. package/dist/commands/prd/fix.d.ts +39 -0
  26. package/dist/commands/prd/fix.js +140 -0
  27. package/dist/commands/prd/validate.d.ts +112 -0
  28. package/dist/commands/prd/validate.js +302 -0
  29. package/dist/commands/stories/create.d.ts +95 -0
  30. package/dist/commands/stories/create.js +431 -0
  31. package/dist/commands/stories/develop.d.ts +91 -0
  32. package/dist/commands/stories/develop.js +460 -0
  33. package/dist/commands/stories/list.d.ts +84 -0
  34. package/dist/commands/stories/list.js +291 -0
  35. package/dist/commands/stories/move.d.ts +66 -0
  36. package/dist/commands/stories/move.js +273 -0
  37. package/dist/commands/stories/qa.d.ts +99 -0
  38. package/dist/commands/stories/qa.js +530 -0
  39. package/dist/commands/workflow.d.ts +97 -0
  40. package/dist/commands/workflow.js +390 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +1 -0
  43. package/dist/models/agent-options.d.ts +50 -0
  44. package/dist/models/agent-options.js +1 -0
  45. package/dist/models/agent-result.d.ts +29 -0
  46. package/dist/models/agent-result.js +1 -0
  47. package/dist/models/index.d.ts +10 -0
  48. package/dist/models/index.js +10 -0
  49. package/dist/models/phase-result.d.ts +65 -0
  50. package/dist/models/phase-result.js +7 -0
  51. package/dist/models/provider.d.ts +28 -0
  52. package/dist/models/provider.js +18 -0
  53. package/dist/models/story.d.ts +154 -0
  54. package/dist/models/story.js +18 -0
  55. package/dist/models/workflow-config.d.ts +148 -0
  56. package/dist/models/workflow-config.js +1 -0
  57. package/dist/models/workflow-result.d.ts +164 -0
  58. package/dist/models/workflow-result.js +7 -0
  59. package/dist/services/agents/agent-runner-factory.d.ts +31 -0
  60. package/dist/services/agents/agent-runner-factory.js +44 -0
  61. package/dist/services/agents/agent-runner.d.ts +46 -0
  62. package/dist/services/agents/agent-runner.js +29 -0
  63. package/dist/services/agents/claude-agent-runner.d.ts +81 -0
  64. package/dist/services/agents/claude-agent-runner.js +332 -0
  65. package/dist/services/agents/gemini-agent-runner.d.ts +82 -0
  66. package/dist/services/agents/gemini-agent-runner.js +350 -0
  67. package/dist/services/agents/index.d.ts +7 -0
  68. package/dist/services/agents/index.js +7 -0
  69. package/dist/services/file-system/file-manager.d.ts +110 -0
  70. package/dist/services/file-system/file-manager.js +223 -0
  71. package/dist/services/file-system/glob-matcher.d.ts +75 -0
  72. package/dist/services/file-system/glob-matcher.js +126 -0
  73. package/dist/services/file-system/path-resolver.d.ts +183 -0
  74. package/dist/services/file-system/path-resolver.js +400 -0
  75. package/dist/services/logging/workflow-logger.d.ts +232 -0
  76. package/dist/services/logging/workflow-logger.js +552 -0
  77. package/dist/services/orchestration/batch-processor.d.ts +113 -0
  78. package/dist/services/orchestration/batch-processor.js +187 -0
  79. package/dist/services/orchestration/dependency-graph-executor.d.ts +60 -0
  80. package/dist/services/orchestration/dependency-graph-executor.js +447 -0
  81. package/dist/services/orchestration/index.d.ts +10 -0
  82. package/dist/services/orchestration/index.js +8 -0
  83. package/dist/services/orchestration/input-detector.d.ts +125 -0
  84. package/dist/services/orchestration/input-detector.js +381 -0
  85. package/dist/services/orchestration/story-queue.d.ts +94 -0
  86. package/dist/services/orchestration/story-queue.js +170 -0
  87. package/dist/services/orchestration/story-type-detector.d.ts +80 -0
  88. package/dist/services/orchestration/story-type-detector.js +258 -0
  89. package/dist/services/orchestration/task-decomposition-service.d.ts +67 -0
  90. package/dist/services/orchestration/task-decomposition-service.js +607 -0
  91. package/dist/services/orchestration/workflow-orchestrator.d.ts +659 -0
  92. package/dist/services/orchestration/workflow-orchestrator.js +2201 -0
  93. package/dist/services/parsers/epic-parser.d.ts +117 -0
  94. package/dist/services/parsers/epic-parser.js +264 -0
  95. package/dist/services/parsers/prd-fixer.d.ts +86 -0
  96. package/dist/services/parsers/prd-fixer.js +194 -0
  97. package/dist/services/parsers/prd-parser.d.ts +123 -0
  98. package/dist/services/parsers/prd-parser.js +286 -0
  99. package/dist/services/parsers/standalone-story-parser.d.ts +114 -0
  100. package/dist/services/parsers/standalone-story-parser.js +255 -0
  101. package/dist/services/parsers/story-parser-factory.d.ts +81 -0
  102. package/dist/services/parsers/story-parser-factory.js +108 -0
  103. package/dist/services/parsers/story-parser.d.ts +122 -0
  104. package/dist/services/parsers/story-parser.js +262 -0
  105. package/dist/services/scaffolding/decompose-session-scaffolder.d.ts +74 -0
  106. package/dist/services/scaffolding/decompose-session-scaffolder.js +315 -0
  107. package/dist/services/scaffolding/file-scaffolder.d.ts +94 -0
  108. package/dist/services/scaffolding/file-scaffolder.js +314 -0
  109. package/dist/services/validation/config-validator.d.ts +88 -0
  110. package/dist/services/validation/config-validator.js +167 -0
  111. package/dist/types/task-graph.d.ts +142 -0
  112. package/dist/types/task-graph.js +5 -0
  113. package/dist/utils/colors.d.ts +49 -0
  114. package/dist/utils/colors.js +50 -0
  115. package/dist/utils/error-formatter.d.ts +64 -0
  116. package/dist/utils/error-formatter.js +279 -0
  117. package/dist/utils/errors.d.ts +170 -0
  118. package/dist/utils/errors.js +233 -0
  119. package/dist/utils/formatters.d.ts +84 -0
  120. package/dist/utils/formatters.js +162 -0
  121. package/dist/utils/logger.d.ts +63 -0
  122. package/dist/utils/logger.js +78 -0
  123. package/dist/utils/progress.d.ts +104 -0
  124. package/dist/utils/progress.js +161 -0
  125. package/dist/utils/retry.d.ts +114 -0
  126. package/dist/utils/retry.js +160 -0
  127. package/dist/utils/shared-flags.d.ts +28 -0
  128. package/dist/utils/shared-flags.js +43 -0
  129. package/package.json +119 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Error Handling Utilities
3
+ *
4
+ * Provides custom error classes with standardized error codes and context.
5
+ * All errors extend BaseError for consistent handling across the CLI.
6
+ */
7
+ import type pino from 'pino';
8
+ /**
9
+ * Error code constants for different error categories
10
+ */
11
+ export declare const ERR_VALIDATION = "ERR_VALIDATION";
12
+ export declare const ERR_PARSER = "ERR_PARSER";
13
+ export declare const ERR_FILE_SYSTEM = "ERR_FILE_SYSTEM";
14
+ export declare const ERR_AGENT = "ERR_AGENT";
15
+ export declare const ERR_CONFIG = "ERR_CONFIG";
16
+ /**
17
+ * Type for error codes
18
+ */
19
+ export type ErrorCode = typeof ERR_AGENT | typeof ERR_CONFIG | typeof ERR_FILE_SYSTEM | typeof ERR_PARSER | typeof ERR_VALIDATION;
20
+ /**
21
+ * Context object for additional error information
22
+ */
23
+ export interface ErrorContext {
24
+ [key: string]: unknown;
25
+ }
26
+ /**
27
+ * Base error class with error codes and context
28
+ *
29
+ * All custom errors should extend this class for consistent handling.
30
+ */
31
+ export declare class BaseError extends Error {
32
+ /**
33
+ * Error code for categorization
34
+ */
35
+ readonly code: ErrorCode;
36
+ /**
37
+ * Additional context information
38
+ */
39
+ readonly context?: ErrorContext;
40
+ /**
41
+ * Actionable suggestion for resolving the error
42
+ */
43
+ readonly suggestion?: string;
44
+ /**
45
+ * Create a new BaseError
46
+ *
47
+ * @param message - Human-readable error message
48
+ * @param code - Error code for categorization
49
+ * @param context - Additional context information
50
+ * @param suggestion - Actionable suggestion for resolving the error
51
+ * @example
52
+ * throw new BaseError('Operation failed', 'ERR_VALIDATION', { field: 'email' }, 'Provide a valid email address')
53
+ */
54
+ constructor(message: string, code: ErrorCode, context?: ErrorContext, suggestion?: string);
55
+ /**
56
+ * Convert error to JSON for logging
57
+ *
58
+ * @returns JSON representation of the error
59
+ */
60
+ toJSON(): Record<string, unknown>;
61
+ }
62
+ /**
63
+ * Validation error for input validation failures
64
+ *
65
+ * Used when user input or configuration is invalid.
66
+ */
67
+ export declare class ValidationError extends BaseError {
68
+ /**
69
+ * Create a new ValidationError
70
+ *
71
+ * @param message - Human-readable error message
72
+ * @param context - Additional context (e.g., field name, validation rule)
73
+ * @param suggestion - Actionable suggestion for resolving the error
74
+ * @example
75
+ * throw new ValidationError('Email is required', { field: 'email', value: '' }, 'Provide a valid email address in the format: user@example.com')
76
+ */
77
+ constructor(message: string, context?: ErrorContext, suggestion?: string);
78
+ }
79
+ /**
80
+ * File system error for file operation failures
81
+ *
82
+ * Used when file operations (read, write, move) fail.
83
+ */
84
+ export declare class FileSystemError extends BaseError {
85
+ /**
86
+ * Create a new FileSystemError
87
+ *
88
+ * @param message - Human-readable error message
89
+ * @param context - Additional context (e.g., file path, operation type)
90
+ * @param suggestion - Actionable suggestion for resolving the error
91
+ * @example
92
+ * throw new FileSystemError('File not found', { filePath: '/path/to/file.md' }, "Check that you're in the project root directory and the path is correct.")
93
+ */
94
+ constructor(message: string, context?: ErrorContext, suggestion?: string);
95
+ }
96
+ /**
97
+ * Agent error for Claude CLI execution failures
98
+ *
99
+ * Used when spawning or executing Claude CLI processes fails.
100
+ */
101
+ export declare class AgentError extends BaseError {
102
+ /**
103
+ * Create a new AgentError
104
+ *
105
+ * @param message - Human-readable error message
106
+ * @param context - Additional context (e.g., exit code, stderr, agent type)
107
+ * @param suggestion - Actionable suggestion for resolving the error
108
+ * @example
109
+ * throw new AgentError('Agent execution failed', { exitCode: 1, stderr: 'Error output' }, 'Ensure Claude CLI is installed and accessible in your PATH.')
110
+ */
111
+ constructor(message: string, context?: ErrorContext, suggestion?: string);
112
+ }
113
+ /**
114
+ * Configuration error for config validation failures
115
+ *
116
+ * Used when configuration files are invalid or missing required fields.
117
+ */
118
+ export declare class ConfigurationError extends BaseError {
119
+ /**
120
+ * Create a new ConfigurationError
121
+ *
122
+ * @param message - Human-readable error message
123
+ * @param context - Additional context (e.g., config field, expected value, actual value)
124
+ * @param suggestion - Actionable suggestion for resolving the error
125
+ * @example
126
+ * throw new ConfigurationError('Invalid field "prdFile"', { field: 'prdFile', expectedType: 'string', actualType: 'number' }, "Field 'prdFile' expects string, got number. Example: 'docs/prd.md'")
127
+ */
128
+ constructor(message: string, context?: ErrorContext, suggestion?: string);
129
+ /**
130
+ * Get example value for common config fields
131
+ * @param field - Config field name
132
+ * @param type - Expected type
133
+ * @returns Example value
134
+ */
135
+ private static getExampleValue;
136
+ }
137
+ /**
138
+ * Parser error for markdown parsing failures
139
+ *
140
+ * Used when parsing PRD, epic, or story files fails.
141
+ */
142
+ export declare class ParserError extends BaseError {
143
+ /**
144
+ * Create a new ParserError
145
+ *
146
+ * @param message - Human-readable error message
147
+ * @param context - Additional context (e.g., file path, line number, problematic line content)
148
+ * @param suggestion - Actionable suggestion for resolving the error
149
+ * @example
150
+ * throw new ParserError('Invalid epic format at line 42', { filePath: 'prd.md', line: 42, lineContent: '## Epic A: Title' }, "Expected format: '## Epic 1: Title'")
151
+ */
152
+ constructor(message: string, context?: ErrorContext, suggestion?: string);
153
+ }
154
+ /**
155
+ * Handle fatal errors by logging and exiting the process
156
+ *
157
+ * This function should be used for unrecoverable errors that require
158
+ * the CLI to exit immediately. It logs the error with full context
159
+ * and exits with code 1.
160
+ *
161
+ * @param error - The error to handle
162
+ * @param logger - Logger instance for error logging
163
+ * @example
164
+ * try {
165
+ * // critical operation
166
+ * } catch (error) {
167
+ * handleFatalError(error as Error, logger)
168
+ * }
169
+ */
170
+ export declare function handleFatalError(error: Error, logger: pino.Logger): never;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Error Handling Utilities
3
+ *
4
+ * Provides custom error classes with standardized error codes and context.
5
+ * All errors extend BaseError for consistent handling across the CLI.
6
+ */
7
+ /**
8
+ * Error code constants for different error categories
9
+ */
10
+ export const ERR_VALIDATION = 'ERR_VALIDATION';
11
+ export const ERR_PARSER = 'ERR_PARSER';
12
+ export const ERR_FILE_SYSTEM = 'ERR_FILE_SYSTEM';
13
+ export const ERR_AGENT = 'ERR_AGENT';
14
+ export const ERR_CONFIG = 'ERR_CONFIG';
15
+ /**
16
+ * Base error class with error codes and context
17
+ *
18
+ * All custom errors should extend this class for consistent handling.
19
+ */
20
+ export class BaseError extends Error {
21
+ /**
22
+ * Error code for categorization
23
+ */
24
+ code;
25
+ /**
26
+ * Additional context information
27
+ */
28
+ context;
29
+ /**
30
+ * Actionable suggestion for resolving the error
31
+ */
32
+ suggestion;
33
+ /**
34
+ * Create a new BaseError
35
+ *
36
+ * @param message - Human-readable error message
37
+ * @param code - Error code for categorization
38
+ * @param context - Additional context information
39
+ * @param suggestion - Actionable suggestion for resolving the error
40
+ * @example
41
+ * throw new BaseError('Operation failed', 'ERR_VALIDATION', { field: 'email' }, 'Provide a valid email address')
42
+ */
43
+ constructor(message, code, context, suggestion) {
44
+ super(message);
45
+ this.name = this.constructor.name;
46
+ this.code = code;
47
+ this.context = context;
48
+ this.suggestion = suggestion;
49
+ // Maintain proper stack trace (V8 only)
50
+ if (Error.captureStackTrace) {
51
+ Error.captureStackTrace(this, this.constructor);
52
+ }
53
+ }
54
+ /**
55
+ * Convert error to JSON for logging
56
+ *
57
+ * @returns JSON representation of the error
58
+ */
59
+ toJSON() {
60
+ return {
61
+ code: this.code,
62
+ context: this.context,
63
+ message: this.message,
64
+ name: this.name,
65
+ stack: this.stack,
66
+ suggestion: this.suggestion,
67
+ };
68
+ }
69
+ }
70
+ /**
71
+ * Validation error for input validation failures
72
+ *
73
+ * Used when user input or configuration is invalid.
74
+ */
75
+ export class ValidationError extends BaseError {
76
+ /**
77
+ * Create a new ValidationError
78
+ *
79
+ * @param message - Human-readable error message
80
+ * @param context - Additional context (e.g., field name, validation rule)
81
+ * @param suggestion - Actionable suggestion for resolving the error
82
+ * @example
83
+ * throw new ValidationError('Email is required', { field: 'email', value: '' }, 'Provide a valid email address in the format: user@example.com')
84
+ */
85
+ constructor(message, context, suggestion) {
86
+ super(message, ERR_VALIDATION, context, suggestion || 'Check that all required fields are provided and have valid values.');
87
+ }
88
+ }
89
+ /**
90
+ * File system error for file operation failures
91
+ *
92
+ * Used when file operations (read, write, move) fail.
93
+ */
94
+ export class FileSystemError extends BaseError {
95
+ /**
96
+ * Create a new FileSystemError
97
+ *
98
+ * @param message - Human-readable error message
99
+ * @param context - Additional context (e.g., file path, operation type)
100
+ * @param suggestion - Actionable suggestion for resolving the error
101
+ * @example
102
+ * throw new FileSystemError('File not found', { filePath: '/path/to/file.md' }, "Check that you're in the project root directory and the path is correct.")
103
+ */
104
+ constructor(message, context, suggestion) {
105
+ const defaultSuggestion = context?.filePath
106
+ ? `Check that you're in the project root directory and the path '${context.filePath}' is correct.`
107
+ : "Check that you're in the project root directory and the path is correct.";
108
+ super(message, ERR_FILE_SYSTEM, context, suggestion || defaultSuggestion);
109
+ }
110
+ }
111
+ /**
112
+ * Agent error for Claude CLI execution failures
113
+ *
114
+ * Used when spawning or executing Claude CLI processes fails.
115
+ */
116
+ export class AgentError extends BaseError {
117
+ /**
118
+ * Create a new AgentError
119
+ *
120
+ * @param message - Human-readable error message
121
+ * @param context - Additional context (e.g., exit code, stderr, agent type)
122
+ * @param suggestion - Actionable suggestion for resolving the error
123
+ * @example
124
+ * throw new AgentError('Agent execution failed', { exitCode: 1, stderr: 'Error output' }, 'Ensure Claude CLI is installed and accessible in your PATH.')
125
+ */
126
+ constructor(message, context, suggestion) {
127
+ super(message, ERR_AGENT, context, suggestion || 'Ensure Claude CLI is installed and accessible in your PATH. Run "claude --version" to verify.');
128
+ }
129
+ }
130
+ /**
131
+ * Configuration error for config validation failures
132
+ *
133
+ * Used when configuration files are invalid or missing required fields.
134
+ */
135
+ export class ConfigurationError extends BaseError {
136
+ /**
137
+ * Create a new ConfigurationError
138
+ *
139
+ * @param message - Human-readable error message
140
+ * @param context - Additional context (e.g., config field, expected value, actual value)
141
+ * @param suggestion - Actionable suggestion for resolving the error
142
+ * @example
143
+ * throw new ConfigurationError('Invalid field "prdFile"', { field: 'prdFile', expectedType: 'string', actualType: 'number' }, "Field 'prdFile' expects string, got number. Example: 'docs/prd.md'")
144
+ */
145
+ constructor(message, context, suggestion) {
146
+ let defaultSuggestion = 'Check your .bmad-core/core-config.yaml file for missing or invalid configuration fields.';
147
+ // Build specific suggestion based on context
148
+ if (context?.field && context?.expectedType && context?.actualType) {
149
+ defaultSuggestion = `Field '${context.field}' expects ${context.expectedType}, got ${context.actualType}. Example: ${ConfigurationError.getExampleValue(context.field, context.expectedType)}`;
150
+ }
151
+ else if (context?.field) {
152
+ defaultSuggestion = `Check the '${context.field}' field in your configuration file.`;
153
+ }
154
+ super(message, ERR_CONFIG, context, suggestion || defaultSuggestion);
155
+ }
156
+ /**
157
+ * Get example value for common config fields
158
+ * @param field - Config field name
159
+ * @param type - Expected type
160
+ * @returns Example value
161
+ */
162
+ static getExampleValue(field, type) {
163
+ const examples = {
164
+ epicDir: "'docs/epics'",
165
+ prdFile: "'docs/prd.md'",
166
+ prdSharded: 'true',
167
+ storyDir: "'docs/stories'",
168
+ };
169
+ return examples[field] || `<${type} value>`;
170
+ }
171
+ }
172
+ /**
173
+ * Parser error for markdown parsing failures
174
+ *
175
+ * Used when parsing PRD, epic, or story files fails.
176
+ */
177
+ export class ParserError extends BaseError {
178
+ /**
179
+ * Create a new ParserError
180
+ *
181
+ * @param message - Human-readable error message
182
+ * @param context - Additional context (e.g., file path, line number, problematic line content)
183
+ * @param suggestion - Actionable suggestion for resolving the error
184
+ * @example
185
+ * throw new ParserError('Invalid epic format at line 42', { filePath: 'prd.md', line: 42, lineContent: '## Epic A: Title' }, "Expected format: '## Epic 1: Title'")
186
+ */
187
+ constructor(message, context, suggestion) {
188
+ let defaultSuggestion = 'Check the file format and ensure it follows the expected markdown structure.';
189
+ // Build specific suggestion based on context
190
+ if (context?.lineContent && context?.line) {
191
+ defaultSuggestion = `Problem at line ${context.line}: "${context.lineContent}". Check the expected format for this section.`;
192
+ }
193
+ else if (context?.line) {
194
+ defaultSuggestion = `Problem at line ${context.line}. Check the expected format for this section.`;
195
+ }
196
+ super(message, ERR_PARSER, context, suggestion || defaultSuggestion);
197
+ }
198
+ }
199
+ /**
200
+ * Handle fatal errors by logging and exiting the process
201
+ *
202
+ * This function should be used for unrecoverable errors that require
203
+ * the CLI to exit immediately. It logs the error with full context
204
+ * and exits with code 1.
205
+ *
206
+ * @param error - The error to handle
207
+ * @param logger - Logger instance for error logging
208
+ * @example
209
+ * try {
210
+ * // critical operation
211
+ * } catch (error) {
212
+ * handleFatalError(error as Error, logger)
213
+ * }
214
+ */
215
+ export function handleFatalError(error, logger) {
216
+ // Log the error with full context
217
+ if (error instanceof BaseError) {
218
+ logger.fatal({
219
+ context: error.context,
220
+ errorCode: error.code,
221
+ errorMessage: error.message,
222
+ stack: error.stack,
223
+ }, 'Fatal error occurred');
224
+ }
225
+ else {
226
+ logger.fatal({
227
+ errorMessage: error.message,
228
+ stack: error.stack,
229
+ }, 'Fatal error occurred');
230
+ }
231
+ // Exit with error code
232
+ process.exit(1);
233
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Format data as a table with headers and rows
3
+ *
4
+ * @param headers - Array of column header strings
5
+ * @param rows - Array of row data, where each row is an array of cell values
6
+ * @returns Formatted table string
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const table = formatTable(
11
+ * ['Name', 'Status'],
12
+ * [['Epic 1', 'Done'], ['Epic 2', 'In Progress']]
13
+ * )
14
+ * console.log(table)
15
+ * ```
16
+ */
17
+ export declare const formatTable: (headers: string[], rows: string[][]) => string;
18
+ /**
19
+ * Format an array of items as a list
20
+ *
21
+ * @param items - Array of items to format
22
+ * @param ordered - Whether to use numbered list (default: false)
23
+ * @returns Formatted list string
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * formatList(['Item 1', 'Item 2'], false) // • Item 1\n• Item 2
28
+ * formatList(['Item 1', 'Item 2'], true) // 1. Item 1\n2. Item 2
29
+ * ```
30
+ */
31
+ export declare const formatList: (items: string[], ordered?: boolean) => string;
32
+ /**
33
+ * Format content in a bordered box with optional title
34
+ *
35
+ * @param title - Title to display at top of box
36
+ * @param content - Content to display inside box
37
+ * @returns Formatted box string
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const box = formatBox('Summary', 'Total: 5 items\nSuccess: 4\nFailed: 1')
42
+ * console.log(box)
43
+ * ```
44
+ */
45
+ export declare const formatBox: (title: string, content: string) => string;
46
+ /**
47
+ * Format seconds as a human-readable countdown string
48
+ *
49
+ * @param seconds - Number of seconds to format
50
+ * @returns Formatted time string (e.g., "2m 30s", "45s", "1h 25m")
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * formatCountdown(0) // "0s"
55
+ * formatCountdown(45) // "45s"
56
+ * formatCountdown(90) // "1m 30s"
57
+ * formatCountdown(3600) // "1h 0m"
58
+ * formatCountdown(3665) // "1h 1m"
59
+ * ```
60
+ */
61
+ export declare const formatCountdown: (seconds: number) => string;
62
+ /**
63
+ * Format a duration in milliseconds as a human-readable string
64
+ *
65
+ * @param milliseconds - Duration in milliseconds
66
+ * @returns Formatted duration string
67
+ */
68
+ export declare const formatDuration: (milliseconds: number) => string;
69
+ /**
70
+ * Format bytes as a human-readable string
71
+ *
72
+ * @param bytes - Number of bytes to format
73
+ * @param decimals - Number of decimal places (default: 2)
74
+ * @returns Formatted byte string (e.g., "1.50 MB", "256.00 KB")
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * formatBytes(1024) // "1.00 KB"
79
+ * formatBytes(1048576) // "1.00 MB"
80
+ * formatBytes(1536, 0) // "2 KB"
81
+ * formatBytes(1536000, 2) // "1.46 MB"
82
+ * ```
83
+ */
84
+ export declare const formatBytes: (bytes: number, decimals?: number) => string;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Format data as a table with headers and rows
3
+ *
4
+ * @param headers - Array of column header strings
5
+ * @param rows - Array of row data, where each row is an array of cell values
6
+ * @returns Formatted table string
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const table = formatTable(
11
+ * ['Name', 'Status'],
12
+ * [['Epic 1', 'Done'], ['Epic 2', 'In Progress']]
13
+ * )
14
+ * console.log(table)
15
+ * ```
16
+ */
17
+ export const formatTable = (headers, rows) => {
18
+ if (headers.length === 0) {
19
+ return '';
20
+ }
21
+ // Calculate column widths
22
+ const columnWidths = headers.map((header, i) => {
23
+ const maxRowWidth = Math.max(...rows.map((row) => (row[i] || '').length));
24
+ return Math.max(header.length, maxRowWidth);
25
+ });
26
+ // Create separator line
27
+ const separator = columnWidths.map((width) => '─'.repeat(width + 2)).join('┼');
28
+ // Format header
29
+ const headerRow = headers.map((header, i) => ` ${header.padEnd(columnWidths[i])} `).join('│');
30
+ // Format rows
31
+ const formattedRows = rows.map((row) => row.map((cell, i) => ` ${(cell || '').padEnd(columnWidths[i])} `).join('│'));
32
+ // Assemble table
33
+ return [
34
+ '┌' + separator.replaceAll('┼', '┬') + '┐',
35
+ '│' + headerRow + '│',
36
+ '├' + separator + '┤',
37
+ ...formattedRows.map((row) => '│' + row + '│'),
38
+ '└' + separator.replaceAll('┼', '┴') + '┘',
39
+ ].join('\n');
40
+ };
41
+ /**
42
+ * Format an array of items as a list
43
+ *
44
+ * @param items - Array of items to format
45
+ * @param ordered - Whether to use numbered list (default: false)
46
+ * @returns Formatted list string
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * formatList(['Item 1', 'Item 2'], false) // • Item 1\n• Item 2
51
+ * formatList(['Item 1', 'Item 2'], true) // 1. Item 1\n2. Item 2
52
+ * ```
53
+ */
54
+ export const formatList = (items, ordered = false) => {
55
+ if (items.length === 0) {
56
+ return '';
57
+ }
58
+ return items
59
+ .map((item, index) => {
60
+ const prefix = ordered ? `${index + 1}. ` : '• ';
61
+ return `${prefix}${item}`;
62
+ })
63
+ .join('\n');
64
+ };
65
+ /**
66
+ * Format content in a bordered box with optional title
67
+ *
68
+ * @param title - Title to display at top of box
69
+ * @param content - Content to display inside box
70
+ * @returns Formatted box string
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const box = formatBox('Summary', 'Total: 5 items\nSuccess: 4\nFailed: 1')
75
+ * console.log(box)
76
+ * ```
77
+ */
78
+ export const formatBox = (title, content) => {
79
+ const lines = content.split('\n');
80
+ const maxLength = Math.max(title.length + 2, ...lines.map((line) => line.length));
81
+ const topBorder = '┏' + '━'.repeat(maxLength + 2) + '┓';
82
+ const titleLine = title ? `┃ ${title.padEnd(maxLength)} ┃` : '';
83
+ const separator = title ? '┣' + '━'.repeat(maxLength + 2) + '┫' : '';
84
+ const contentLines = lines.map((line) => `┃ ${line.padEnd(maxLength)} ┃`);
85
+ const bottomBorder = '┗' + '━'.repeat(maxLength + 2) + '┛';
86
+ const parts = [topBorder];
87
+ if (titleLine)
88
+ parts.push(titleLine);
89
+ if (separator)
90
+ parts.push(separator);
91
+ parts.push(...contentLines, bottomBorder);
92
+ return parts.join('\n');
93
+ };
94
+ /**
95
+ * Format seconds as a human-readable countdown string
96
+ *
97
+ * @param seconds - Number of seconds to format
98
+ * @returns Formatted time string (e.g., "2m 30s", "45s", "1h 25m")
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * formatCountdown(0) // "0s"
103
+ * formatCountdown(45) // "45s"
104
+ * formatCountdown(90) // "1m 30s"
105
+ * formatCountdown(3600) // "1h 0m"
106
+ * formatCountdown(3665) // "1h 1m"
107
+ * ```
108
+ */
109
+ export const formatCountdown = (seconds) => {
110
+ if (seconds < 0) {
111
+ return '0s';
112
+ }
113
+ if (seconds === 0) {
114
+ return '0s';
115
+ }
116
+ const hours = Math.floor(seconds / 3600);
117
+ const minutes = Math.floor((seconds % 3600) / 60);
118
+ const secs = seconds % 60;
119
+ if (hours > 0) {
120
+ return `${hours}h ${minutes}m`;
121
+ }
122
+ if (minutes > 0) {
123
+ return `${minutes}m ${secs}s`;
124
+ }
125
+ return `${secs}s`;
126
+ };
127
+ /**
128
+ * Format a duration in milliseconds as a human-readable string
129
+ *
130
+ * @param milliseconds - Duration in milliseconds
131
+ * @returns Formatted duration string
132
+ */
133
+ export const formatDuration = (milliseconds) => {
134
+ const seconds = Math.floor(milliseconds / 1000);
135
+ return formatCountdown(seconds);
136
+ };
137
+ /**
138
+ * Format bytes as a human-readable string
139
+ *
140
+ * @param bytes - Number of bytes to format
141
+ * @param decimals - Number of decimal places (default: 2)
142
+ * @returns Formatted byte string (e.g., "1.50 MB", "256.00 KB")
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * formatBytes(1024) // "1.00 KB"
147
+ * formatBytes(1048576) // "1.00 MB"
148
+ * formatBytes(1536, 0) // "2 KB"
149
+ * formatBytes(1536000, 2) // "1.46 MB"
150
+ * ```
151
+ */
152
+ export const formatBytes = (bytes, decimals = 2) => {
153
+ if (bytes === 0) {
154
+ return '0 Bytes';
155
+ }
156
+ const k = 1024;
157
+ const dm = Math.max(decimals, 0);
158
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
159
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
160
+ const value = bytes / k ** i;
161
+ return `${value.toFixed(dm)} ${sizes[i]}`;
162
+ };