agileflow 2.81.0 → 2.82.1

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
- [![Commands](https://img.shields.io/badge/commands-68-blue)](docs/04-architecture/commands.md)
6
+ [![Commands](https://img.shields.io/badge/commands-71-blue)](docs/04-architecture/commands.md)
7
7
  [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-29-orange)](docs/04-architecture/subagents.md)
8
8
  [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](docs/04-architecture/skills.md)
9
9
 
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
65
65
 
66
66
  | Component | Count | Description |
67
67
  |-----------|-------|-------------|
68
- | [Commands](docs/04-architecture/commands.md) | 68 | Slash commands for agile workflows |
68
+ | [Commands](docs/04-architecture/commands.md) | 71 | Slash commands for agile workflows |
69
69
  | [Agents/Experts](docs/04-architecture/subagents.md) | 29 | Specialized agents with self-improving knowledge bases |
70
70
  | [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
71
71
 
@@ -76,7 +76,7 @@ AgileFlow combines three proven methodologies:
76
76
  Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
77
 
78
78
  ### Reference
79
- - [Commands](docs/04-architecture/commands.md) - All 68 slash commands
79
+ - [Commands](docs/04-architecture/commands.md) - All 71 slash commands
80
80
  - [Agents/Experts](docs/04-architecture/subagents.md) - 29 specialized agents with self-improving knowledge
81
81
  - [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
82
82
 
package/lib/colors.js ADDED
@@ -0,0 +1,190 @@
1
+ /**
2
+ * AgileFlow CLI - Shared Color Utilities
3
+ *
4
+ * Centralized ANSI color codes and formatting helpers.
5
+ * Uses 256-color palette for modern terminal support.
6
+ */
7
+
8
+ /**
9
+ * ANSI color codes for terminal output.
10
+ * Includes standard colors, 256-color palette, and brand colors.
11
+ */
12
+ const c = {
13
+ // Reset and modifiers
14
+ reset: '\x1b[0m',
15
+ bold: '\x1b[1m',
16
+ dim: '\x1b[2m',
17
+ italic: '\x1b[3m',
18
+ underline: '\x1b[4m',
19
+
20
+ // Standard ANSI colors (8 colors)
21
+ red: '\x1b[31m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ magenta: '\x1b[35m',
26
+ cyan: '\x1b[36m',
27
+ white: '\x1b[37m',
28
+
29
+ // Bright variants
30
+ brightBlack: '\x1b[90m',
31
+ brightRed: '\x1b[91m',
32
+ brightGreen: '\x1b[92m',
33
+ brightYellow: '\x1b[93m',
34
+ brightBlue: '\x1b[94m',
35
+ brightMagenta: '\x1b[95m',
36
+ brightCyan: '\x1b[96m',
37
+ brightWhite: '\x1b[97m',
38
+
39
+ // 256-color palette (vibrant, modern look)
40
+ mintGreen: '\x1b[38;5;158m', // Healthy/success states
41
+ peach: '\x1b[38;5;215m', // Warning states
42
+ coral: '\x1b[38;5;203m', // Critical/error states
43
+ lightGreen: '\x1b[38;5;194m', // Session healthy
44
+ lightYellow: '\x1b[38;5;228m', // Session warning
45
+ lightPink: '\x1b[38;5;210m', // Session critical
46
+ skyBlue: '\x1b[38;5;117m', // Directories/paths, ready states
47
+ lavender: '\x1b[38;5;147m', // Model info, story IDs
48
+ softGold: '\x1b[38;5;222m', // Cost/money
49
+ teal: '\x1b[38;5;80m', // Pending states
50
+ slate: '\x1b[38;5;103m', // Secondary info
51
+ rose: '\x1b[38;5;211m', // Blocked/critical accent
52
+ amber: '\x1b[38;5;214m', // WIP/in-progress accent
53
+ powder: '\x1b[38;5;153m', // Labels/headers
54
+
55
+ // Brand color (#e8683a - burnt orange/terracotta)
56
+ brand: '\x1b[38;2;232;104;58m',
57
+ orange: '\x1b[38;2;232;104;58m', // Alias for brand color
58
+
59
+ // Background colors
60
+ bgRed: '\x1b[41m',
61
+ bgGreen: '\x1b[42m',
62
+ bgYellow: '\x1b[43m',
63
+ bgBlue: '\x1b[44m',
64
+ };
65
+
66
+ /**
67
+ * Box drawing characters for tables and borders.
68
+ */
69
+ const box = {
70
+ // Corners (rounded)
71
+ tl: '╭', // top-left
72
+ tr: '╮', // top-right
73
+ bl: '╰', // bottom-left
74
+ br: '╯', // bottom-right
75
+
76
+ // Lines
77
+ h: '─', // horizontal
78
+ v: '│', // vertical
79
+
80
+ // T-junctions
81
+ lT: '├', // left T
82
+ rT: '┤', // right T
83
+ tT: '┬', // top T
84
+ bT: '┴', // bottom T
85
+
86
+ // Cross
87
+ cross: '┼',
88
+
89
+ // Double line variants
90
+ dh: '═', // double horizontal
91
+ dv: '║', // double vertical
92
+ };
93
+
94
+ /**
95
+ * Status indicators with colors.
96
+ */
97
+ const status = {
98
+ success: `${c.green}✓${c.reset}`,
99
+ warning: `${c.yellow}⚠️${c.reset}`,
100
+ error: `${c.red}✗${c.reset}`,
101
+ info: `${c.cyan}ℹ${c.reset}`,
102
+ pending: `${c.dim}○${c.reset}`,
103
+ inProgress: `${c.yellow}◐${c.reset}`,
104
+ done: `${c.green}●${c.reset}`,
105
+ blocked: `${c.red}◆${c.reset}`,
106
+ };
107
+
108
+ /**
109
+ * Wrap text with color codes.
110
+ *
111
+ * @param {string} text - Text to colorize
112
+ * @param {string} color - Color code from `c` object
113
+ * @returns {string} Colorized text
114
+ */
115
+ function colorize(text, color) {
116
+ return `${color}${text}${c.reset}`;
117
+ }
118
+
119
+ /**
120
+ * Create a dim text string.
121
+ *
122
+ * @param {string} text - Text to dim
123
+ * @returns {string} Dimmed text
124
+ */
125
+ function dim(text) {
126
+ return colorize(text, c.dim);
127
+ }
128
+
129
+ /**
130
+ * Create a bold text string.
131
+ *
132
+ * @param {string} text - Text to bold
133
+ * @returns {string} Bold text
134
+ */
135
+ function bold(text) {
136
+ return colorize(text, c.bold);
137
+ }
138
+
139
+ /**
140
+ * Create success-colored text.
141
+ *
142
+ * @param {string} text - Text to color
143
+ * @returns {string} Green text
144
+ */
145
+ function success(text) {
146
+ return colorize(text, c.green);
147
+ }
148
+
149
+ /**
150
+ * Create warning-colored text.
151
+ *
152
+ * @param {string} text - Text to color
153
+ * @returns {string} Yellow text
154
+ */
155
+ function warning(text) {
156
+ return colorize(text, c.yellow);
157
+ }
158
+
159
+ /**
160
+ * Create error-colored text.
161
+ *
162
+ * @param {string} text - Text to color
163
+ * @returns {string} Red text
164
+ */
165
+ function error(text) {
166
+ return colorize(text, c.red);
167
+ }
168
+
169
+ /**
170
+ * Create brand-colored text.
171
+ *
172
+ * @param {string} text - Text to color
173
+ * @returns {string} Brand-colored text (#e8683a)
174
+ */
175
+ function brand(text) {
176
+ return colorize(text, c.brand);
177
+ }
178
+
179
+ module.exports = {
180
+ c,
181
+ box,
182
+ status,
183
+ colorize,
184
+ dim,
185
+ bold,
186
+ success,
187
+ warning,
188
+ error,
189
+ brand,
190
+ };
package/lib/errors.js ADDED
@@ -0,0 +1,279 @@
1
+ /**
2
+ * errors.js - Safe error handling wrappers
3
+ *
4
+ * Provides consistent error handling patterns across AgileFlow scripts.
5
+ * All wrappers return Result objects: { ok: boolean, data?: T, error?: string }
6
+ *
7
+ * Usage:
8
+ * const { safeReadJSON, safeWriteJSON, safeExec } = require('../lib/errors');
9
+ *
10
+ * const result = safeReadJSON('/path/to/file.json');
11
+ * if (result.ok) {
12
+ * console.log(result.data);
13
+ * } else {
14
+ * console.error(result.error);
15
+ * }
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const { execSync } = require('child_process');
20
+
21
+ // Debug logging (opt-in via AGILEFLOW_DEBUG=1)
22
+ const DEBUG = process.env.AGILEFLOW_DEBUG === '1';
23
+
24
+ function debugLog(operation, details) {
25
+ if (DEBUG) {
26
+ const timestamp = new Date().toISOString();
27
+ console.error(`[${timestamp}] [errors.js] ${operation}:`, JSON.stringify(details));
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Safely read and parse a JSON file
33
+ * @param {string} filePath - Absolute path to JSON file
34
+ * @param {object} options - Optional settings
35
+ * @param {*} options.defaultValue - Value to return if file doesn't exist (makes missing file not an error)
36
+ * @returns {{ ok: boolean, data?: any, error?: string }}
37
+ */
38
+ function safeReadJSON(filePath, options = {}) {
39
+ const { defaultValue } = options;
40
+
41
+ try {
42
+ if (!fs.existsSync(filePath)) {
43
+ if (defaultValue !== undefined) {
44
+ debugLog('safeReadJSON', { filePath, status: 'missing, using default' });
45
+ return { ok: true, data: defaultValue };
46
+ }
47
+ const error = `File not found: ${filePath}`;
48
+ debugLog('safeReadJSON', { filePath, error });
49
+ return { ok: false, error };
50
+ }
51
+
52
+ const content = fs.readFileSync(filePath, 'utf8');
53
+ const data = JSON.parse(content);
54
+ debugLog('safeReadJSON', { filePath, status: 'success' });
55
+ return { ok: true, data };
56
+ } catch (err) {
57
+ const error = `Failed to read JSON from ${filePath}: ${err.message}`;
58
+ debugLog('safeReadJSON', { filePath, error: err.message });
59
+ return { ok: false, error };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Safely write data as JSON to a file
65
+ * @param {string} filePath - Absolute path to JSON file
66
+ * @param {any} data - Data to serialize as JSON
67
+ * @param {object} options - Optional settings
68
+ * @param {number} options.spaces - JSON indentation (default: 2)
69
+ * @param {boolean} options.createDir - Create parent directories if missing (default: false)
70
+ * @returns {{ ok: boolean, error?: string }}
71
+ */
72
+ function safeWriteJSON(filePath, data, options = {}) {
73
+ const { spaces = 2, createDir = false } = options;
74
+
75
+ try {
76
+ if (createDir) {
77
+ const dir = require('path').dirname(filePath);
78
+ if (!fs.existsSync(dir)) {
79
+ fs.mkdirSync(dir, { recursive: true });
80
+ debugLog('safeWriteJSON', { filePath, status: 'created directory', dir });
81
+ }
82
+ }
83
+
84
+ const content = JSON.stringify(data, null, spaces) + '\n';
85
+ fs.writeFileSync(filePath, content);
86
+ debugLog('safeWriteJSON', { filePath, status: 'success' });
87
+ return { ok: true };
88
+ } catch (err) {
89
+ const error = `Failed to write JSON to ${filePath}: ${err.message}`;
90
+ debugLog('safeWriteJSON', { filePath, error: err.message });
91
+ return { ok: false, error };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Safely read a text file
97
+ * @param {string} filePath - Absolute path to file
98
+ * @param {object} options - Optional settings
99
+ * @param {string} options.defaultValue - Value to return if file doesn't exist
100
+ * @returns {{ ok: boolean, data?: string, error?: string }}
101
+ */
102
+ function safeReadFile(filePath, options = {}) {
103
+ const { defaultValue } = options;
104
+
105
+ try {
106
+ if (!fs.existsSync(filePath)) {
107
+ if (defaultValue !== undefined) {
108
+ debugLog('safeReadFile', { filePath, status: 'missing, using default' });
109
+ return { ok: true, data: defaultValue };
110
+ }
111
+ const error = `File not found: ${filePath}`;
112
+ debugLog('safeReadFile', { filePath, error });
113
+ return { ok: false, error };
114
+ }
115
+
116
+ const data = fs.readFileSync(filePath, 'utf8');
117
+ debugLog('safeReadFile', { filePath, status: 'success' });
118
+ return { ok: true, data };
119
+ } catch (err) {
120
+ const error = `Failed to read file ${filePath}: ${err.message}`;
121
+ debugLog('safeReadFile', { filePath, error: err.message });
122
+ return { ok: false, error };
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Safely write text to a file
128
+ * @param {string} filePath - Absolute path to file
129
+ * @param {string} content - Content to write
130
+ * @param {object} options - Optional settings
131
+ * @param {boolean} options.createDir - Create parent directories if missing (default: false)
132
+ * @returns {{ ok: boolean, error?: string }}
133
+ */
134
+ function safeWriteFile(filePath, content, options = {}) {
135
+ const { createDir = false } = options;
136
+
137
+ try {
138
+ if (createDir) {
139
+ const dir = require('path').dirname(filePath);
140
+ if (!fs.existsSync(dir)) {
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ debugLog('safeWriteFile', { filePath, status: 'created directory', dir });
143
+ }
144
+ }
145
+
146
+ fs.writeFileSync(filePath, content);
147
+ debugLog('safeWriteFile', { filePath, status: 'success' });
148
+ return { ok: true };
149
+ } catch (err) {
150
+ const error = `Failed to write file ${filePath}: ${err.message}`;
151
+ debugLog('safeWriteFile', { filePath, error: err.message });
152
+ return { ok: false, error };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Safely execute a shell command
158
+ * @param {string} command - Shell command to execute
159
+ * @param {object} options - Optional settings
160
+ * @param {string} options.cwd - Working directory
161
+ * @param {number} options.timeout - Timeout in ms (default: 30000)
162
+ * @param {boolean} options.silent - Suppress stderr (default: false)
163
+ * @returns {{ ok: boolean, data?: string, error?: string, exitCode?: number }}
164
+ */
165
+ function safeExec(command, options = {}) {
166
+ const { cwd = process.cwd(), timeout = 30000, silent = false } = options;
167
+
168
+ try {
169
+ const output = execSync(command, {
170
+ cwd,
171
+ encoding: 'utf8',
172
+ timeout,
173
+ stdio: silent ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'inherit'],
174
+ });
175
+ debugLog('safeExec', { command: command.slice(0, 50), status: 'success' });
176
+ return { ok: true, data: output.trim() };
177
+ } catch (err) {
178
+ const exitCode = err.status || 1;
179
+ const error = `Command failed (exit ${exitCode}): ${command.slice(0, 100)}${err.message ? ` - ${err.message}` : ''}`;
180
+ debugLog('safeExec', { command: command.slice(0, 50), error: err.message, exitCode });
181
+ return { ok: false, error, exitCode };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Safely check if a file or directory exists
187
+ * @param {string} path - Path to check
188
+ * @returns {{ ok: boolean, exists: boolean }}
189
+ */
190
+ function safeExists(path) {
191
+ try {
192
+ const exists = fs.existsSync(path);
193
+ return { ok: true, exists };
194
+ } catch (err) {
195
+ debugLog('safeExists', { path, error: err.message });
196
+ return { ok: true, exists: false };
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Safely create a directory
202
+ * @param {string} dirPath - Directory path to create
203
+ * @param {object} options - Optional settings
204
+ * @param {boolean} options.recursive - Create parent directories (default: true)
205
+ * @returns {{ ok: boolean, error?: string }}
206
+ */
207
+ function safeMkdir(dirPath, options = {}) {
208
+ const { recursive = true } = options;
209
+
210
+ try {
211
+ if (!fs.existsSync(dirPath)) {
212
+ fs.mkdirSync(dirPath, { recursive });
213
+ debugLog('safeMkdir', { dirPath, status: 'created' });
214
+ } else {
215
+ debugLog('safeMkdir', { dirPath, status: 'already exists' });
216
+ }
217
+ return { ok: true };
218
+ } catch (err) {
219
+ const error = `Failed to create directory ${dirPath}: ${err.message}`;
220
+ debugLog('safeMkdir', { dirPath, error: err.message });
221
+ return { ok: false, error };
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Wrap any function to catch errors and return Result
227
+ * @param {Function} fn - Function to wrap
228
+ * @param {string} operationName - Name for error messages
229
+ * @returns {Function} - Wrapped function returning { ok, data?, error? }
230
+ */
231
+ function wrapSafe(fn, operationName = 'operation') {
232
+ return function (...args) {
233
+ try {
234
+ const result = fn.apply(this, args);
235
+ return { ok: true, data: result };
236
+ } catch (err) {
237
+ const error = `${operationName} failed: ${err.message}`;
238
+ debugLog('wrapSafe', { operationName, error: err.message });
239
+ return { ok: false, error };
240
+ }
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Wrap an async function to catch errors and return Result
246
+ * @param {Function} fn - Async function to wrap
247
+ * @param {string} operationName - Name for error messages
248
+ * @returns {Function} - Wrapped async function returning { ok, data?, error? }
249
+ */
250
+ function wrapSafeAsync(fn, operationName = 'operation') {
251
+ return async function (...args) {
252
+ try {
253
+ const result = await fn.apply(this, args);
254
+ return { ok: true, data: result };
255
+ } catch (err) {
256
+ const error = `${operationName} failed: ${err.message}`;
257
+ debugLog('wrapSafeAsync', { operationName, error: err.message });
258
+ return { ok: false, error };
259
+ }
260
+ };
261
+ }
262
+
263
+ module.exports = {
264
+ // Core safe operations
265
+ safeReadJSON,
266
+ safeWriteJSON,
267
+ safeReadFile,
268
+ safeWriteFile,
269
+ safeExec,
270
+ safeExists,
271
+ safeMkdir,
272
+
273
+ // Utility wrappers
274
+ wrapSafe,
275
+ wrapSafeAsync,
276
+
277
+ // Debug helper
278
+ debugLog,
279
+ };
package/lib/paths.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * AgileFlow CLI - Shared Path Utilities
3
+ *
4
+ * Centralized path resolution functions used across scripts.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ /**
11
+ * Find the project root by looking for .agileflow directory.
12
+ * Walks up from current directory until .agileflow is found.
13
+ *
14
+ * @param {string} [startDir=process.cwd()] - Directory to start searching from
15
+ * @returns {string} Project root path, or startDir if not found
16
+ */
17
+ function getProjectRoot(startDir = process.cwd()) {
18
+ let dir = startDir;
19
+ while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
20
+ dir = path.dirname(dir);
21
+ }
22
+ return dir !== '/' ? dir : startDir;
23
+ }
24
+
25
+ /**
26
+ * Get the .agileflow directory path.
27
+ *
28
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
29
+ * @returns {string} Path to .agileflow directory
30
+ */
31
+ function getAgileflowDir(rootDir) {
32
+ const root = rootDir || getProjectRoot();
33
+ return path.join(root, '.agileflow');
34
+ }
35
+
36
+ /**
37
+ * Get the .claude directory path.
38
+ *
39
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
40
+ * @returns {string} Path to .claude directory
41
+ */
42
+ function getClaudeDir(rootDir) {
43
+ const root = rootDir || getProjectRoot();
44
+ return path.join(root, '.claude');
45
+ }
46
+
47
+ /**
48
+ * Get the docs directory path.
49
+ *
50
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
51
+ * @returns {string} Path to docs directory
52
+ */
53
+ function getDocsDir(rootDir) {
54
+ const root = rootDir || getProjectRoot();
55
+ return path.join(root, 'docs');
56
+ }
57
+
58
+ /**
59
+ * Get the status.json path.
60
+ *
61
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
62
+ * @returns {string} Path to status.json
63
+ */
64
+ function getStatusPath(rootDir) {
65
+ const root = rootDir || getProjectRoot();
66
+ return path.join(root, 'docs', '09-agents', 'status.json');
67
+ }
68
+
69
+ /**
70
+ * Get the session-state.json path.
71
+ *
72
+ * @param {string} [rootDir] - Project root (auto-detected if not provided)
73
+ * @returns {string} Path to session-state.json
74
+ */
75
+ function getSessionStatePath(rootDir) {
76
+ const root = rootDir || getProjectRoot();
77
+ return path.join(root, 'docs', '09-agents', 'session-state.json');
78
+ }
79
+
80
+ /**
81
+ * Check if we're in an AgileFlow project.
82
+ *
83
+ * @param {string} [dir=process.cwd()] - Directory to check
84
+ * @returns {boolean} True if .agileflow directory exists
85
+ */
86
+ function isAgileflowProject(dir = process.cwd()) {
87
+ const root = getProjectRoot(dir);
88
+ return fs.existsSync(path.join(root, '.agileflow'));
89
+ }
90
+
91
+ module.exports = {
92
+ getProjectRoot,
93
+ getAgileflowDir,
94
+ getClaudeDir,
95
+ getDocsDir,
96
+ getStatusPath,
97
+ getSessionStatePath,
98
+ isAgileflowProject,
99
+ };