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 +3 -3
- package/lib/colors.js +190 -0
- package/lib/errors.js +279 -0
- package/lib/paths.js +99 -0
- package/lib/validate.js +337 -0
- package/package.json +2 -1
- package/scripts/ralph-loop.js +293 -11
- package/src/core/agents/orchestrator.md +135 -6
- package/src/core/commands/babysit.md +132 -0
- package/src/core/commands/batch.md +362 -0
- package/src/core/commands/choose.md +337 -0
- package/src/core/commands/workflow.md +344 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](docs/04-architecture/commands.md)
|
|
7
7
|
[](docs/04-architecture/subagents.md)
|
|
8
8
|
[](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
|
+
| [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
|
|
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
|
+
};
|