agileflow 2.91.0 → 2.92.0

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 (99) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -3
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +31 -23
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate.js +116 -52
  22. package/package.json +1 -1
  23. package/scripts/af +34 -0
  24. package/scripts/agent-loop.js +63 -9
  25. package/scripts/agileflow-configure.js +2 -2
  26. package/scripts/agileflow-welcome.js +435 -23
  27. package/scripts/archive-completed-stories.sh +57 -11
  28. package/scripts/claude-tmux.sh +102 -0
  29. package/scripts/damage-control-bash.js +3 -70
  30. package/scripts/damage-control-edit.js +3 -20
  31. package/scripts/damage-control-write.js +3 -20
  32. package/scripts/dependency-check.js +310 -0
  33. package/scripts/get-env.js +11 -4
  34. package/scripts/lib/configure-detect.js +23 -1
  35. package/scripts/lib/configure-features.js +43 -2
  36. package/scripts/lib/context-formatter.js +771 -0
  37. package/scripts/lib/context-loader.js +699 -0
  38. package/scripts/lib/damage-control-utils.js +107 -0
  39. package/scripts/lib/json-utils.sh +162 -0
  40. package/scripts/lib/state-migrator.js +353 -0
  41. package/scripts/lib/story-state-machine.js +437 -0
  42. package/scripts/obtain-context.js +80 -1248
  43. package/scripts/pre-push-check.sh +46 -0
  44. package/scripts/precompact-context.sh +23 -10
  45. package/scripts/query-codebase.js +122 -14
  46. package/scripts/ralph-loop.js +5 -5
  47. package/scripts/session-manager.js +220 -42
  48. package/scripts/spawn-parallel.js +651 -0
  49. package/scripts/tui/blessed/data/watcher.js +20 -15
  50. package/scripts/tui/blessed/index.js +2 -2
  51. package/scripts/tui/blessed/panels/output.js +14 -8
  52. package/scripts/tui/blessed/panels/sessions.js +22 -15
  53. package/scripts/tui/blessed/panels/trace.js +14 -8
  54. package/scripts/tui/blessed/ui/help.js +3 -3
  55. package/scripts/tui/blessed/ui/screen.js +4 -4
  56. package/scripts/tui/blessed/ui/statusbar.js +5 -9
  57. package/scripts/tui/blessed/ui/tabbar.js +11 -11
  58. package/scripts/validators/component-validator.js +41 -14
  59. package/scripts/validators/json-schema-validator.js +11 -4
  60. package/scripts/validators/markdown-validator.js +1 -2
  61. package/scripts/validators/migration-validator.js +17 -5
  62. package/scripts/validators/security-validator.js +137 -33
  63. package/scripts/validators/story-format-validator.js +31 -10
  64. package/scripts/validators/test-result-validator.js +19 -4
  65. package/scripts/validators/workflow-validator.js +12 -5
  66. package/src/core/agents/codebase-query.md +24 -0
  67. package/src/core/commands/adr.md +114 -0
  68. package/src/core/commands/agent.md +120 -0
  69. package/src/core/commands/assign.md +145 -0
  70. package/src/core/commands/babysit.md +32 -5
  71. package/src/core/commands/changelog.md +118 -0
  72. package/src/core/commands/configure.md +42 -6
  73. package/src/core/commands/diagnose.md +114 -0
  74. package/src/core/commands/epic.md +113 -0
  75. package/src/core/commands/handoff.md +128 -0
  76. package/src/core/commands/help.md +75 -0
  77. package/src/core/commands/pr.md +96 -0
  78. package/src/core/commands/roadmap/analyze.md +400 -0
  79. package/src/core/commands/session/new.md +113 -6
  80. package/src/core/commands/session/spawn.md +197 -0
  81. package/src/core/commands/sprint.md +22 -0
  82. package/src/core/commands/status.md +74 -0
  83. package/src/core/commands/story.md +143 -4
  84. package/src/core/templates/agileflow-metadata.json +55 -2
  85. package/src/core/templates/plan-template.md +125 -0
  86. package/src/core/templates/story-lifecycle.md +213 -0
  87. package/src/core/templates/story-template.md +4 -0
  88. package/src/core/templates/tdd-test-template.js +241 -0
  89. package/tools/cli/commands/setup.js +86 -0
  90. package/tools/cli/installers/core/installer.js +94 -0
  91. package/tools/cli/installers/ide/_base-ide.js +20 -11
  92. package/tools/cli/installers/ide/codex.js +29 -47
  93. package/tools/cli/lib/config-manager.js +17 -2
  94. package/tools/cli/lib/content-transformer.js +271 -0
  95. package/tools/cli/lib/error-handler.js +14 -22
  96. package/tools/cli/lib/ide-error-factory.js +421 -0
  97. package/tools/cli/lib/ide-health-monitor.js +364 -0
  98. package/tools/cli/lib/ide-registry.js +114 -1
  99. package/tools/cli/lib/ui.js +14 -25
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Registry Dependency Injection Module
3
+ *
4
+ * Provides dependency injection patterns for registry classes to enable:
5
+ * - Easier testing with mock dependencies
6
+ * - Configuration without modifying code
7
+ * - Clear separation of concerns
8
+ *
9
+ * Usage:
10
+ * const { createContainer, createPlaceholderRegistry } = require('./registry-di');
11
+ *
12
+ * // Create a container with custom dependencies
13
+ * const container = createContainer({
14
+ * sanitizer: mockSanitizer,
15
+ * fs: mockFs,
16
+ * });
17
+ *
18
+ * // Create registry using container
19
+ * const registry = createPlaceholderRegistry(container);
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ // Default dependencies
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ /**
29
+ * @typedef {Object} IDependencyContainer
30
+ * @property {Object} fs - File system module
31
+ * @property {Object} path - Path module
32
+ * @property {Object} sanitizer - Content sanitizer module
33
+ * @property {Object} [logger] - Optional logger interface
34
+ */
35
+
36
+ /**
37
+ * @typedef {Object} ISanitizer
38
+ * @property {Function} sanitize.count - Sanitize count values
39
+ * @property {Function} sanitize.description - Sanitize description strings
40
+ * @property {Function} sanitize.date - Sanitize date values
41
+ * @property {Function} sanitize.version - Sanitize version strings
42
+ * @property {Function} sanitize.folderName - Sanitize folder names
43
+ * @property {Function} validatePlaceholderValue - Validate placeholder values
44
+ * @property {Function} detectInjectionAttempt - Detect injection attempts
45
+ * @property {Function} escapeMarkdown - Escape markdown characters
46
+ */
47
+
48
+ /**
49
+ * @typedef {Object} ILogger
50
+ * @property {Function} debug - Debug level logging
51
+ * @property {Function} info - Info level logging
52
+ * @property {Function} warn - Warning level logging
53
+ * @property {Function} error - Error level logging
54
+ */
55
+
56
+ /**
57
+ * Default no-op logger
58
+ */
59
+ const noopLogger = {
60
+ debug: () => {},
61
+ info: () => {},
62
+ warn: () => {},
63
+ error: () => {},
64
+ };
65
+
66
+ /**
67
+ * Console logger adapter
68
+ */
69
+ const consoleLogger = {
70
+ debug: (...args) => console.debug('[Registry]', ...args),
71
+ info: (...args) => console.log('[Registry]', ...args),
72
+ warn: (...args) => console.warn('[Registry]', ...args),
73
+ error: (...args) => console.error('[Registry]', ...args),
74
+ };
75
+
76
+ /**
77
+ * Load default sanitizer module
78
+ * @returns {ISanitizer}
79
+ */
80
+ function loadDefaultSanitizer() {
81
+ try {
82
+ return require('./content-sanitizer');
83
+ } catch (e) {
84
+ // Return stub sanitizer if module not found
85
+ return {
86
+ sanitize: {
87
+ count: v => (typeof v === 'number' ? Math.max(0, Math.floor(v)) : 0),
88
+ description: v => String(v || ''),
89
+ date: v => (v instanceof Date ? v.toISOString().split('T')[0] : String(v || '')),
90
+ version: v => String(v || 'unknown'),
91
+ folderName: v => String(v || '.agileflow'),
92
+ },
93
+ validatePlaceholderValue: () => ({ valid: true }),
94
+ detectInjectionAttempt: () => ({ safe: true }),
95
+ escapeMarkdown: v => String(v || ''),
96
+ };
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Create a dependency container with defaults
102
+ * @param {Partial<IDependencyContainer>} [overrides={}] - Override specific dependencies
103
+ * @returns {IDependencyContainer}
104
+ */
105
+ function createContainer(overrides = {}) {
106
+ const defaultSanitizer = loadDefaultSanitizer();
107
+
108
+ return {
109
+ fs: overrides.fs || fs,
110
+ path: overrides.path || path,
111
+ sanitizer: overrides.sanitizer || defaultSanitizer,
112
+ logger: overrides.logger || noopLogger,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Create a test container with mock-friendly defaults
118
+ * @param {Partial<IDependencyContainer>} [overrides={}] - Override specific dependencies
119
+ * @returns {IDependencyContainer}
120
+ */
121
+ function createTestContainer(overrides = {}) {
122
+ const mockFs = {
123
+ readdirSync: () => [],
124
+ readFileSync: () => '',
125
+ existsSync: () => false,
126
+ writeFileSync: () => {},
127
+ mkdirSync: () => {},
128
+ ...overrides.fs,
129
+ };
130
+
131
+ const mockSanitizer = {
132
+ sanitize: {
133
+ count: v => v,
134
+ description: v => v,
135
+ date: v => v,
136
+ version: v => v,
137
+ folderName: v => v,
138
+ },
139
+ validatePlaceholderValue: () => ({ valid: true }),
140
+ detectInjectionAttempt: () => ({ safe: true }),
141
+ escapeMarkdown: v => v,
142
+ ...overrides.sanitizer,
143
+ };
144
+
145
+ return createContainer({
146
+ fs: mockFs,
147
+ sanitizer: mockSanitizer,
148
+ logger: overrides.logger || noopLogger,
149
+ ...overrides,
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Registry factory configuration
155
+ * @typedef {Object} RegistryFactoryConfig
156
+ * @property {Object} [options] - Registry options
157
+ * @property {IDependencyContainer} [container] - Dependency container
158
+ */
159
+
160
+ /**
161
+ * Create PlaceholderRegistry with injected dependencies
162
+ * @param {IDependencyContainer} container - Dependency container
163
+ * @param {Object} [options={}] - Registry options
164
+ * @returns {PlaceholderRegistry}
165
+ */
166
+ function createPlaceholderRegistry(container, options = {}) {
167
+ const { PlaceholderRegistry } = require('./placeholder-registry');
168
+
169
+ // Create registry with injected container
170
+ const registry = new PlaceholderRegistry({
171
+ ...options,
172
+ // Inject dependencies via context
173
+ _container: container,
174
+ });
175
+
176
+ return registry;
177
+ }
178
+
179
+ /**
180
+ * Scan registry factory - creates scanner functions with injected dependencies
181
+ * @param {IDependencyContainer} container - Dependency container
182
+ * @returns {Object} Scanner functions
183
+ */
184
+ function createScannerFactory(container) {
185
+ const { fs: fsModule, path: pathModule, logger } = container;
186
+
187
+ /**
188
+ * Generic file scanner
189
+ * @param {string} directory - Directory to scan
190
+ * @param {string} extension - File extension (e.g., '.md')
191
+ * @param {Function} parser - Frontmatter parser function
192
+ * @returns {Array} Parsed items
193
+ */
194
+ function scanDirectory(directory, extension, parser) {
195
+ logger.debug(`Scanning directory: ${directory}`);
196
+
197
+ if (!fsModule.existsSync(directory)) {
198
+ logger.warn(`Directory not found: ${directory}`);
199
+ return [];
200
+ }
201
+
202
+ let files;
203
+ try {
204
+ files = fsModule.readdirSync(directory);
205
+ } catch (err) {
206
+ logger.error(`Failed to read directory: ${err.message}`);
207
+ return [];
208
+ }
209
+
210
+ const items = [];
211
+
212
+ for (const file of files) {
213
+ if (!file.endsWith(extension)) {
214
+ continue;
215
+ }
216
+
217
+ const filePath = pathModule.join(directory, file);
218
+
219
+ try {
220
+ const content = fsModule.readFileSync(filePath, 'utf8');
221
+ const parsed = parser(content, file, filePath);
222
+
223
+ if (parsed) {
224
+ items.push(parsed);
225
+ }
226
+ } catch (err) {
227
+ logger.warn(`Failed to parse ${file}: ${err.message}`);
228
+ }
229
+ }
230
+
231
+ logger.debug(`Found ${items.length} items`);
232
+ return items;
233
+ }
234
+
235
+ return {
236
+ scanDirectory,
237
+
238
+ /**
239
+ * Scan for command files
240
+ * @param {string} commandsDir - Commands directory path
241
+ * @param {Function} extractFrontmatter - Frontmatter extractor
242
+ * @returns {Array} Command metadata
243
+ */
244
+ scanCommands(commandsDir, extractFrontmatter) {
245
+ return scanDirectory(commandsDir, '.md', (content, file, filePath) => {
246
+ const frontmatter = extractFrontmatter(filePath);
247
+ if (Object.keys(frontmatter).length === 0) {
248
+ return null;
249
+ }
250
+
251
+ const name = file.replace('.md', '');
252
+ return {
253
+ name,
254
+ file,
255
+ path: filePath,
256
+ description: frontmatter.description || '',
257
+ argumentHint: frontmatter['argument-hint'] || '',
258
+ };
259
+ });
260
+ },
261
+
262
+ /**
263
+ * Scan for agent files
264
+ * @param {string} agentsDir - Agents directory path
265
+ * @param {Function} extractFrontmatter - Frontmatter extractor
266
+ * @param {Function} normalizeTools - Tools normalizer
267
+ * @returns {Array} Agent metadata
268
+ */
269
+ scanAgents(agentsDir, extractFrontmatter, normalizeTools) {
270
+ return scanDirectory(agentsDir, '.md', (content, file, filePath) => {
271
+ const frontmatter = extractFrontmatter(filePath);
272
+ if (Object.keys(frontmatter).length === 0) {
273
+ return null;
274
+ }
275
+
276
+ const name = file.replace('.md', '');
277
+ const tools = normalizeTools ? normalizeTools(frontmatter.tools) : frontmatter.tools || [];
278
+
279
+ return {
280
+ name,
281
+ file,
282
+ path: filePath,
283
+ displayName: frontmatter.name || name,
284
+ description: frontmatter.description || '',
285
+ tools,
286
+ model: frontmatter.model || 'haiku',
287
+ color: frontmatter.color || 'blue',
288
+ };
289
+ });
290
+ },
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Resolve paths factory - creates path resolver with injected dependencies
296
+ * @param {IDependencyContainer} container - Dependency container
297
+ * @param {string} [baseDir] - Base directory for relative paths
298
+ * @returns {Object} Path resolver functions
299
+ */
300
+ function createPathResolver(container, baseDir) {
301
+ const { path: pathModule, fs: fsModule } = container;
302
+ const resolvedBaseDir = baseDir || process.cwd();
303
+
304
+ return {
305
+ /**
306
+ * Resolve path relative to base directory
307
+ * @param {...string} segments - Path segments
308
+ * @returns {string} Resolved path
309
+ */
310
+ resolve(...segments) {
311
+ return pathModule.resolve(resolvedBaseDir, ...segments);
312
+ },
313
+
314
+ /**
315
+ * Join path segments
316
+ * @param {...string} segments - Path segments
317
+ * @returns {string} Joined path
318
+ */
319
+ join(...segments) {
320
+ return pathModule.join(...segments);
321
+ },
322
+
323
+ /**
324
+ * Check if path exists
325
+ * @param {string} targetPath - Path to check
326
+ * @returns {boolean}
327
+ */
328
+ exists(targetPath) {
329
+ return fsModule.existsSync(targetPath);
330
+ },
331
+
332
+ /**
333
+ * Get base directory
334
+ * @returns {string}
335
+ */
336
+ getBaseDir() {
337
+ return resolvedBaseDir;
338
+ },
339
+ };
340
+ }
341
+
342
+ module.exports = {
343
+ // Container factories
344
+ createContainer,
345
+ createTestContainer,
346
+
347
+ // Registry factories
348
+ createPlaceholderRegistry,
349
+ createScannerFactory,
350
+ createPathResolver,
351
+
352
+ // Loggers
353
+ noopLogger,
354
+ consoleLogger,
355
+
356
+ // Utilities
357
+ loadDefaultSanitizer,
358
+ };
@@ -0,0 +1,363 @@
1
+ /**
2
+ * result-schema.js - Unified Result<T> type for consistent return values
3
+ *
4
+ * Provides standardized Result type with metadata for:
5
+ * - Consistent success/failure handling
6
+ * - Error code integration
7
+ * - Severity and category tracking
8
+ * - Automatic recovery suggestions
9
+ *
10
+ * Usage:
11
+ * const { success, failure, createResult, isSuccess } = require('./result-schema');
12
+ *
13
+ * // Success case
14
+ * return success(data);
15
+ *
16
+ * // Failure case
17
+ * return failure('ENOENT', 'File not found');
18
+ *
19
+ * // Check result
20
+ * if (isSuccess(result)) {
21
+ * console.log(result.data);
22
+ * } else {
23
+ * console.error(result.error);
24
+ * }
25
+ */
26
+
27
+ const { ErrorCodes, Severity, Category, getErrorCode } = require('./error-codes');
28
+
29
+ /**
30
+ * @typedef {Object} ResultSuccess<T>
31
+ * @property {true} ok - Indicates success
32
+ * @property {T} data - The success data
33
+ * @property {undefined} error - No error on success
34
+ * @property {undefined} errorCode - No error code on success
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} ResultFailure
39
+ * @property {false} ok - Indicates failure
40
+ * @property {undefined} data - No data on failure
41
+ * @property {string} error - Human-readable error message
42
+ * @property {string} errorCode - Machine-readable error code
43
+ * @property {string} severity - Error severity (critical, high, medium, low)
44
+ * @property {string} category - Error category (filesystem, permission, etc.)
45
+ * @property {boolean} recoverable - Whether the error can be recovered from
46
+ * @property {string} [suggestedFix] - Suggested fix for the error
47
+ * @property {string} [autoFix] - Auto-fix action name if available
48
+ * @property {Object} [context] - Additional context about the error
49
+ */
50
+
51
+ /**
52
+ * @typedef {ResultSuccess<T> | ResultFailure} Result<T>
53
+ */
54
+
55
+ /**
56
+ * Create a success result
57
+ * @template T
58
+ * @param {T} data - The success data
59
+ * @param {Object} [meta] - Optional metadata
60
+ * @returns {ResultSuccess<T>}
61
+ */
62
+ function success(data, meta = {}) {
63
+ return {
64
+ ok: true,
65
+ data,
66
+ ...meta,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Create a failure result with error code metadata
72
+ * @param {string} errorCode - Error code (e.g., 'ENOENT', 'ECONFIG')
73
+ * @param {string} [message] - Optional custom error message
74
+ * @param {Object} [options] - Additional options
75
+ * @param {Object} [options.context] - Additional context about the error
76
+ * @param {Error} [options.cause] - Original error that caused this failure
77
+ * @returns {ResultFailure}
78
+ */
79
+ function failure(errorCode, message, options = {}) {
80
+ const codeData = getErrorCode(errorCode) || ErrorCodes.EUNKNOWN;
81
+
82
+ return {
83
+ ok: false,
84
+ error: message || codeData.message,
85
+ errorCode: codeData.code,
86
+ severity: codeData.severity,
87
+ category: codeData.category,
88
+ recoverable: codeData.recoverable,
89
+ suggestedFix: codeData.suggestedFix,
90
+ autoFix: codeData.autoFix || null,
91
+ context: options.context,
92
+ cause: options.cause,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Create a failure result from an existing Error object
98
+ * @param {Error} error - The error object
99
+ * @param {string} [defaultCode='EUNKNOWN'] - Default error code if none detected
100
+ * @param {Object} [options] - Additional options
101
+ * @param {Object} [options.context] - Additional context about the error
102
+ * @returns {ResultFailure}
103
+ */
104
+ function failureFromError(error, defaultCode = 'EUNKNOWN', options = {}) {
105
+ const { getErrorCodeFromError } = require('./error-codes');
106
+ const codeData = getErrorCodeFromError(error);
107
+
108
+ // Use detected code or default
109
+ const code = codeData.code !== 'EUNKNOWN' ? codeData.code : defaultCode;
110
+ const finalCodeData = ErrorCodes[code] || ErrorCodes.EUNKNOWN;
111
+
112
+ return {
113
+ ok: false,
114
+ error: error.message || finalCodeData.message,
115
+ errorCode: finalCodeData.code,
116
+ severity: finalCodeData.severity,
117
+ category: finalCodeData.category,
118
+ recoverable: finalCodeData.recoverable,
119
+ suggestedFix: finalCodeData.suggestedFix,
120
+ autoFix: finalCodeData.autoFix || null,
121
+ context: options.context,
122
+ cause: error,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Create a result from a boolean condition
128
+ * @template T
129
+ * @param {boolean} condition - Condition to check
130
+ * @param {T} data - Data to return on success
131
+ * @param {string} errorCode - Error code on failure
132
+ * @param {string} [errorMessage] - Error message on failure
133
+ * @returns {Result<T>}
134
+ */
135
+ function fromCondition(condition, data, errorCode, errorMessage) {
136
+ if (condition) {
137
+ return success(data);
138
+ }
139
+ return failure(errorCode, errorMessage);
140
+ }
141
+
142
+ /**
143
+ * Create a result from a Promise
144
+ * @template T
145
+ * @param {Promise<T>} promise - Promise to wrap
146
+ * @param {string} [defaultCode='EUNKNOWN'] - Default error code on rejection
147
+ * @returns {Promise<Result<T>>}
148
+ */
149
+ async function fromPromise(promise, defaultCode = 'EUNKNOWN') {
150
+ try {
151
+ const data = await promise;
152
+ return success(data);
153
+ } catch (error) {
154
+ return failureFromError(error, defaultCode);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Create a result from a sync function call
160
+ * @template T
161
+ * @param {() => T} fn - Function to execute
162
+ * @param {string} [defaultCode='EUNKNOWN'] - Default error code on throw
163
+ * @returns {Result<T>}
164
+ */
165
+ function fromTry(fn, defaultCode = 'EUNKNOWN') {
166
+ try {
167
+ const data = fn();
168
+ return success(data);
169
+ } catch (error) {
170
+ return failureFromError(error, defaultCode);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Check if a result is successful
176
+ * @template T
177
+ * @param {Result<T>} result - Result to check
178
+ * @returns {boolean}
179
+ */
180
+ function isSuccess(result) {
181
+ return result != null && result.ok === true;
182
+ }
183
+
184
+ /**
185
+ * Check if a result is a failure
186
+ * @template T
187
+ * @param {Result<T>} result - Result to check
188
+ * @returns {boolean}
189
+ */
190
+ function isFailure(result) {
191
+ return result != null && result.ok === false;
192
+ }
193
+
194
+ /**
195
+ * Get data from result or throw if failure
196
+ * @template T
197
+ * @param {Result<T>} result - Result to unwrap
198
+ * @param {string} [context] - Context for error message
199
+ * @returns {T}
200
+ * @throws {Error} If result is a failure
201
+ */
202
+ function unwrap(result, context) {
203
+ if (isSuccess(result)) {
204
+ return result.data;
205
+ }
206
+
207
+ const prefix = context ? `${context}: ` : '';
208
+ const error = new Error(`${prefix}${result.error}`);
209
+ error.errorCode = result.errorCode;
210
+ error.severity = result.severity;
211
+ error.category = result.category;
212
+ error.recoverable = result.recoverable;
213
+ error.suggestedFix = result.suggestedFix;
214
+ throw error;
215
+ }
216
+
217
+ /**
218
+ * Get data from result or return default value on failure
219
+ * @template T
220
+ * @param {Result<T>} result - Result to unwrap
221
+ * @param {T} defaultValue - Default value on failure
222
+ * @returns {T}
223
+ */
224
+ function unwrapOr(result, defaultValue) {
225
+ if (isSuccess(result)) {
226
+ return result.data;
227
+ }
228
+ return defaultValue;
229
+ }
230
+
231
+ /**
232
+ * Transform successful result data
233
+ * @template T, U
234
+ * @param {Result<T>} result - Result to transform
235
+ * @param {(data: T) => U} fn - Transform function
236
+ * @returns {Result<U>}
237
+ */
238
+ function map(result, fn) {
239
+ if (isSuccess(result)) {
240
+ return success(fn(result.data));
241
+ }
242
+ return result;
243
+ }
244
+
245
+ /**
246
+ * Chain result-returning operations
247
+ * @template T, U
248
+ * @param {Result<T>} result - Result to chain from
249
+ * @param {(data: T) => Result<U>} fn - Function returning Result
250
+ * @returns {Result<U>}
251
+ */
252
+ function flatMap(result, fn) {
253
+ if (isSuccess(result)) {
254
+ return fn(result.data);
255
+ }
256
+ return result;
257
+ }
258
+
259
+ /**
260
+ * Combine multiple results into one
261
+ * @template T
262
+ * @param {Result<T>[]} results - Results to combine
263
+ * @returns {Result<T[]>} Combined result with array of data or first failure
264
+ */
265
+ function all(results) {
266
+ const data = [];
267
+
268
+ for (const result of results) {
269
+ if (isFailure(result)) {
270
+ return result;
271
+ }
272
+ data.push(result.data);
273
+ }
274
+
275
+ return success(data);
276
+ }
277
+
278
+ /**
279
+ * Get first successful result or last failure
280
+ * @template T
281
+ * @param {Result<T>[]} results - Results to check
282
+ * @returns {Result<T>} First success or last failure
283
+ */
284
+ function any(results) {
285
+ let lastFailure = null;
286
+
287
+ for (const result of results) {
288
+ if (isSuccess(result)) {
289
+ return result;
290
+ }
291
+ lastFailure = result;
292
+ }
293
+
294
+ return lastFailure || failure('EUNKNOWN', 'No results provided');
295
+ }
296
+
297
+ /**
298
+ * Format a result for logging/display
299
+ * @template T
300
+ * @param {Result<T>} result - Result to format
301
+ * @param {Object} [options] - Format options
302
+ * @param {boolean} [options.includeData=false] - Include data in output
303
+ * @param {boolean} [options.includeSuggestion=true] - Include suggested fix
304
+ * @returns {string}
305
+ */
306
+ function format(result, options = {}) {
307
+ const { includeData = false, includeSuggestion = true } = options;
308
+
309
+ if (isSuccess(result)) {
310
+ if (includeData) {
311
+ return `[OK] ${JSON.stringify(result.data)}`;
312
+ }
313
+ return '[OK]';
314
+ }
315
+
316
+ const lines = [`[${result.errorCode}] ${result.error}`];
317
+ lines.push(` Severity: ${result.severity} | Category: ${result.category}`);
318
+
319
+ if (includeSuggestion && result.suggestedFix) {
320
+ lines.push(` Fix: ${result.suggestedFix}`);
321
+ }
322
+
323
+ if (result.autoFix) {
324
+ lines.push(` Auto-fix available: npx agileflow doctor --fix`);
325
+ }
326
+
327
+ return lines.join('\n');
328
+ }
329
+
330
+ module.exports = {
331
+ // Constructors
332
+ success,
333
+ failure,
334
+ failureFromError,
335
+
336
+ // From helpers
337
+ fromCondition,
338
+ fromPromise,
339
+ fromTry,
340
+
341
+ // Type guards
342
+ isSuccess,
343
+ isFailure,
344
+
345
+ // Extractors
346
+ unwrap,
347
+ unwrapOr,
348
+
349
+ // Transformers
350
+ map,
351
+ flatMap,
352
+
353
+ // Combinators
354
+ all,
355
+ any,
356
+
357
+ // Utilities
358
+ format,
359
+
360
+ // Re-export enums for convenience
361
+ Severity,
362
+ Category,
363
+ };