agileflow 2.85.0 → 2.87.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.
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft-07/schema#",
3
3
  "description": "AgileFlow session state tracking",
4
- "schema_version": 2,
4
+ "schema_version": 3,
5
5
  "current_session": {
6
6
  "id": "sess-20251206-100000",
7
7
  "started_at": "2025-12-06T10:00:00Z",
8
8
  "baseline_verified": false,
9
9
  "initial_test_status": "not_run",
10
10
  "current_story": null,
11
- "active_agent": null
11
+ "active_agent": null,
12
+ "thread_type": "base",
13
+ "thread_complexity": "routine",
14
+ "expected_duration_minutes": null
12
15
  },
16
+ "_thread_type_enum": ["base", "parallel", "chained", "fusion", "big", "long"],
17
+ "_thread_complexity_enum": ["trivial", "routine", "complex", "exploratory"],
13
18
  "active_command": {
14
19
  "_comment": "Tracks which command (babysit, etc.) is currently active for context preservation across compacts",
15
20
  "name": null,
@@ -23,5 +28,29 @@
23
28
  "final_test_status": "not_run",
24
29
  "commits": []
25
30
  },
26
- "session_history": []
31
+ "session_history": [],
32
+ "batch_loop": {
33
+ "_comment": "State for batch pmap loop mode - iterative processing with quality gates",
34
+ "enabled": false,
35
+ "batch_id": null,
36
+ "pattern": null,
37
+ "action": null,
38
+ "quality_gate": "tests",
39
+ "current_item": null,
40
+ "items": {},
41
+ "summary": {
42
+ "total": 0,
43
+ "completed": 0,
44
+ "in_progress": 0,
45
+ "pending": 0,
46
+ "failed": 0
47
+ },
48
+ "iteration": 0,
49
+ "max_iterations": 50,
50
+ "started_at": null,
51
+ "completed_at": null,
52
+ "stopped_reason": null,
53
+ "last_failure": null
54
+ },
55
+ "_batch_item_status_enum": ["pending", "in_progress", "completed", "failed", "skipped"]
27
56
  }
@@ -11,6 +11,7 @@ const yaml = require('js-yaml');
11
11
  const { Installer } = require('../installers/core/installer');
12
12
  const { IdeManager } = require('../installers/ide/manager');
13
13
  const { displayLogo, displaySection, success, warning, error, info } = require('../lib/ui');
14
+ const { ErrorHandler } = require('../lib/error-handler');
14
15
 
15
16
  const installer = new Installer();
16
17
  const ideManager = new IdeManager();
@@ -33,9 +34,12 @@ module.exports = {
33
34
 
34
35
  if (!status.installed) {
35
36
  displayLogo();
36
- warning('No AgileFlow installation found');
37
- console.log(chalk.dim(`\nRun 'npx agileflow setup' to set up AgileFlow\n`));
38
- process.exit(1);
37
+ const handler = new ErrorHandler('config');
38
+ handler.warning(
39
+ 'No AgileFlow installation found',
40
+ 'Initialize AgileFlow first',
41
+ 'npx agileflow setup'
42
+ );
39
43
  }
40
44
 
41
45
  const manifestPath = path.join(status.path, '_cfg', 'manifest.yaml');
@@ -76,11 +80,13 @@ module.exports = {
76
80
 
77
81
  process.exit(0);
78
82
  } catch (err) {
79
- console.error(chalk.red('Error:'), err.message);
80
- if (process.env.DEBUG) {
81
- console.error(err.stack);
82
- }
83
- process.exit(1);
83
+ const handler = new ErrorHandler('config');
84
+ handler.critical(
85
+ 'Configuration operation failed',
86
+ 'Check manifest file integrity',
87
+ 'npx agileflow doctor --fix',
88
+ err
89
+ );
84
90
  }
85
91
  },
86
92
  };
@@ -121,17 +127,24 @@ async function handleList(status) {
121
127
  * Handle get subcommand
122
128
  */
123
129
  async function handleGet(status, key) {
130
+ const handler = new ErrorHandler('config');
131
+
124
132
  if (!key) {
125
- error('Missing key. Usage: npx agileflow config get <key>');
126
- process.exit(1);
133
+ handler.warning(
134
+ 'Missing key',
135
+ 'Provide a config key to get',
136
+ 'npx agileflow config get <key>'
137
+ );
127
138
  }
128
139
 
129
140
  const validKeys = ['userName', 'ides', 'agileflowFolder', 'docsFolder', 'version'];
130
141
 
131
142
  if (!validKeys.includes(key)) {
132
- error(`Invalid key: ${key}`);
133
- console.log(chalk.dim(`Valid keys: ${validKeys.join(', ')}\n`));
134
- process.exit(1);
143
+ handler.warning(
144
+ `Invalid key: ${key}`,
145
+ `Valid keys: ${validKeys.join(', ')}`,
146
+ 'npx agileflow config list'
147
+ );
135
148
  }
136
149
 
137
150
  let value;
@@ -160,17 +173,24 @@ async function handleGet(status, key) {
160
173
  * Handle set subcommand
161
174
  */
162
175
  async function handleSet(directory, status, manifestPath, key, value) {
176
+ const handler = new ErrorHandler('config');
177
+
163
178
  if (!key || value === undefined) {
164
- error('Missing arguments. Usage: npx agileflow config set <key> <value>');
165
- process.exit(1);
179
+ handler.warning(
180
+ 'Missing arguments',
181
+ 'Provide both key and value',
182
+ 'npx agileflow config set <key> <value>'
183
+ );
166
184
  }
167
185
 
168
186
  const validKeys = ['userName', 'ides', 'agileflowFolder', 'docsFolder'];
169
187
 
170
188
  if (!validKeys.includes(key)) {
171
- error(`Invalid key: ${key}`);
172
- console.log(chalk.dim(`Valid keys: ${validKeys.join(', ')}\n`));
173
- process.exit(1);
189
+ handler.warning(
190
+ `Invalid key: ${key}`,
191
+ `Valid keys: ${validKeys.join(', ')}`,
192
+ 'npx agileflow config list'
193
+ );
174
194
  }
175
195
 
176
196
  displayLogo();
@@ -198,9 +218,11 @@ async function handleSet(directory, status, manifestPath, key, value) {
198
218
  // Validate IDEs
199
219
  for (const ide of newIdes) {
200
220
  if (!validIdes.includes(ide)) {
201
- error(`Invalid IDE: ${ide}`);
202
- console.log(chalk.dim(`Valid IDEs: ${validIdes.join(', ')}\n`));
203
- process.exit(1);
221
+ handler.warning(
222
+ `Invalid IDE: ${ide}`,
223
+ `Valid IDEs: ${validIdes.join(', ')}`,
224
+ 'npx agileflow config set ides "claude-code,cursor"'
225
+ );
204
226
  }
205
227
  }
206
228
 
@@ -20,6 +20,7 @@ const {
20
20
  } = require('../lib/ui');
21
21
  const { IdeManager } = require('../installers/ide/manager');
22
22
  const { getCurrentVersion } = require('../lib/version-checker');
23
+ const { ErrorHandler } = require('../lib/error-handler');
23
24
 
24
25
  const installer = new Installer();
25
26
 
@@ -306,11 +307,13 @@ module.exports = {
306
307
 
307
308
  process.exit(issues > 0 ? 1 : 0);
308
309
  } catch (err) {
309
- console.error(chalk.red('Error:'), err.message);
310
- if (process.env.DEBUG) {
311
- console.error(err.stack);
312
- }
313
- process.exit(1);
310
+ const handler = new ErrorHandler('doctor');
311
+ handler.critical(
312
+ 'Diagnostics failed',
313
+ 'Check permissions and installation',
314
+ 'npx agileflow setup',
315
+ err
316
+ );
314
317
  }
315
318
  },
316
319
  };
@@ -22,6 +22,7 @@ const {
22
22
  } = require('../lib/ui');
23
23
  const { createDocsStructure } = require('../lib/docs-setup');
24
24
  const { getLatestVersion } = require('../lib/npm-utils');
25
+ const { ErrorHandler } = require('../lib/error-handler');
25
26
 
26
27
  const installer = new Installer();
27
28
  const ideManager = new IdeManager();
@@ -101,8 +102,12 @@ module.exports = {
101
102
  const coreResult = await installer.install(config);
102
103
 
103
104
  if (!coreResult.success) {
104
- error('Core setup failed');
105
- process.exit(1);
105
+ const handler = new ErrorHandler('setup');
106
+ handler.warning(
107
+ 'Core setup failed',
108
+ 'Check directory permissions',
109
+ 'npx agileflow doctor'
110
+ );
106
111
  }
107
112
 
108
113
  success(`Installed ${coreResult.counts.agents} agents`);
@@ -144,11 +149,13 @@ module.exports = {
144
149
 
145
150
  process.exit(0);
146
151
  } catch (err) {
147
- console.error(chalk.red('\nSetup failed:'), err.message);
148
- if (process.env.DEBUG) {
149
- console.error(err.stack);
150
- }
151
- process.exit(1);
152
+ const handler = new ErrorHandler('setup');
153
+ handler.critical(
154
+ 'Setup failed',
155
+ 'Check directory exists and has write permissions',
156
+ 'npx agileflow doctor',
157
+ err
158
+ );
152
159
  }
153
160
  },
154
161
  };
@@ -10,6 +10,7 @@ const fs = require('fs-extra');
10
10
  const { Installer } = require('../installers/core/installer');
11
11
  const { IdeManager } = require('../installers/ide/manager');
12
12
  const { displayLogo, displaySection, success, warning, error, confirm } = require('../lib/ui');
13
+ const { ErrorHandler } = require('../lib/error-handler');
13
14
 
14
15
  const installer = new Installer();
15
16
  const ideManager = new IdeManager();
@@ -138,11 +139,13 @@ module.exports = {
138
139
 
139
140
  process.exit(0);
140
141
  } catch (err) {
141
- console.error(chalk.red('Uninstall failed:'), err.message);
142
- if (process.env.DEBUG) {
143
- console.error(err.stack);
144
- }
145
- process.exit(1);
142
+ const handler = new ErrorHandler('uninstall');
143
+ handler.critical(
144
+ 'Uninstall failed',
145
+ 'Check file permissions',
146
+ 'sudo npx agileflow uninstall --force',
147
+ err
148
+ );
146
149
  }
147
150
  },
148
151
  };
@@ -22,6 +22,7 @@ const {
22
22
  } = require('../lib/ui');
23
23
  const { createDocsStructure, getDocsFolderName } = require('../lib/docs-setup');
24
24
  const { getLatestVersion } = require('../lib/npm-utils');
25
+ const { ErrorHandler } = require('../lib/error-handler');
25
26
 
26
27
  const installer = new Installer();
27
28
  const ideManager = new IdeManager();
@@ -76,9 +77,12 @@ module.exports = {
76
77
  const status = await installer.getStatus(directory);
77
78
 
78
79
  if (!status.installed) {
79
- warning('No AgileFlow installation found');
80
- console.log(chalk.dim(`\nRun 'npx agileflow setup' to set up AgileFlow\n`));
81
- process.exit(1);
80
+ const handler = new ErrorHandler('update');
81
+ handler.warning(
82
+ 'No AgileFlow installation found',
83
+ 'Initialize AgileFlow first',
84
+ 'npx agileflow setup'
85
+ );
82
86
  }
83
87
 
84
88
  displaySection('Updating AgileFlow', `Current version: ${status.version}`);
@@ -179,8 +183,12 @@ module.exports = {
179
183
  const coreResult = await installer.install(config, { force: options.force });
180
184
 
181
185
  if (!coreResult.success) {
182
- error('Update failed');
183
- process.exit(1);
186
+ const handler = new ErrorHandler('update');
187
+ handler.warning(
188
+ 'Update failed',
189
+ 'Try running doctor to diagnose issues',
190
+ 'npx agileflow doctor --fix'
191
+ );
184
192
  }
185
193
 
186
194
  success('Updated core content');
@@ -237,11 +245,13 @@ module.exports = {
237
245
 
238
246
  process.exit(0);
239
247
  } catch (err) {
240
- console.error(chalk.red('Update failed:'), err.message);
241
- if (process.env.DEBUG) {
242
- console.error(err.stack);
243
- }
244
- process.exit(1);
248
+ const handler = new ErrorHandler('update');
249
+ handler.critical(
250
+ 'Update failed',
251
+ 'Check network connection and disk space',
252
+ 'npx agileflow doctor',
253
+ err
254
+ );
245
255
  }
246
256
  },
247
257
  };
@@ -202,6 +202,80 @@ function injectContent(content, context = {}) {
202
202
  return result;
203
203
  }
204
204
 
205
+ // =============================================================================
206
+ // Section Processing Functions (Progressive Disclosure)
207
+ // =============================================================================
208
+
209
+ /**
210
+ * Extract section names from content
211
+ * @param {string} content - Content with section markers
212
+ * @returns {string[]} Array of section names
213
+ */
214
+ function extractSectionNames(content) {
215
+ const sectionPattern = /<!-- SECTION: (\w+[-\w]*) -->/g;
216
+ const sections = [];
217
+ let match;
218
+ while ((match = sectionPattern.exec(content)) !== null) {
219
+ sections.push(match[1]);
220
+ }
221
+ return sections;
222
+ }
223
+
224
+ /**
225
+ * Filter content to only include specified sections
226
+ * Sections are marked with: <!-- SECTION: name --> ... <!-- END_SECTION -->
227
+ *
228
+ * @param {string} content - Content with section markers
229
+ * @param {string[]} activeSections - Sections to include (empty = include all)
230
+ * @returns {string} Content with only active sections
231
+ */
232
+ function filterSections(content, activeSections = []) {
233
+ // If no active sections specified, include all content
234
+ if (!activeSections || activeSections.length === 0) {
235
+ return content;
236
+ }
237
+
238
+ // Pattern matches: <!-- SECTION: name --> content <!-- END_SECTION -->
239
+ const sectionPattern = /<!-- SECTION: (\w+[-\w]*) -->([\s\S]*?)<!-- END_SECTION -->/g;
240
+
241
+ return content.replace(sectionPattern, (match, sectionName, sectionContent) => {
242
+ if (activeSections.includes(sectionName)) {
243
+ // Keep the section content, remove the markers
244
+ return sectionContent;
245
+ }
246
+ // Remove the entire section
247
+ return '';
248
+ });
249
+ }
250
+
251
+ /**
252
+ * Remove all section markers but keep content
253
+ * Used when no filtering is needed but markers should be cleaned
254
+ *
255
+ * @param {string} content - Content with section markers
256
+ * @returns {string} Content without section markers
257
+ */
258
+ function stripSectionMarkers(content) {
259
+ // Remove section start markers
260
+ let result = content.replace(/<!-- SECTION: \w+[-\w]* -->\n?/g, '');
261
+ // Remove section end markers
262
+ result = result.replace(/<!-- END_SECTION -->\n?/g, '');
263
+ return result;
264
+ }
265
+
266
+ /**
267
+ * Check if content has section markers
268
+ * @param {string} content - Content to check
269
+ * @returns {boolean} True if content has sections
270
+ */
271
+ function hasSections(content) {
272
+ return /<!-- SECTION: \w+[-\w]* -->/.test(content);
273
+ }
274
+
275
+ // =============================================================================
276
+ // Utility Functions
277
+ // =============================================================================
278
+
205
279
  /**
206
280
  * Check if content has any template variables
207
281
  * @param {string} content - Content to check
@@ -263,4 +337,10 @@ module.exports = {
263
337
  injectContent,
264
338
  hasPlaceholders,
265
339
  getPlaceholderDocs,
340
+
341
+ // Section processing (progressive disclosure)
342
+ extractSectionNames,
343
+ filterSections,
344
+ stripSectionMarkers,
345
+ hasSections,
266
346
  };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * error-handler.js - Centralized Error Handling with Actionable Guidance
3
+ *
4
+ * Provides three-tier error handling:
5
+ * - INFO: Non-blocking issues (does not exit)
6
+ * - WARNING: Issues that should be addressed (exit 1)
7
+ * - CRITICAL: Severe errors (exit 1 + stack trace if DEBUG=1)
8
+ *
9
+ * Error output format: "X <problem> | Action: <what to do> | Run: <command>"
10
+ */
11
+
12
+ const { c } = require('../../../lib/colors');
13
+
14
+ class ErrorHandler {
15
+ /**
16
+ * Create an ErrorHandler instance
17
+ * @param {string} commandName - Name of the command using this handler
18
+ */
19
+ constructor(commandName = 'agileflow') {
20
+ this.commandName = commandName;
21
+ }
22
+
23
+ /**
24
+ * Format error with actionable guidance
25
+ * Output: "X <problem> | Action: <what to do> | Run: <command>"
26
+ *
27
+ * @param {string} message - The error message describing the problem
28
+ * @param {string} [actionText] - What the user should do to fix it
29
+ * @param {string} [commandHint] - Command to run to fix it
30
+ * @returns {string} Formatted error string
31
+ */
32
+ formatError(message, actionText, commandHint) {
33
+ let output = `${c.red}\u2716${c.reset} ${message}`;
34
+ if (actionText) {
35
+ output += ` ${c.dim}|${c.reset} ${c.cyan}Action:${c.reset} ${actionText}`;
36
+ }
37
+ if (commandHint) {
38
+ output += ` ${c.dim}|${c.reset} ${c.green}Run:${c.reset} ${c.bold}${commandHint}${c.reset}`;
39
+ }
40
+ return output;
41
+ }
42
+
43
+ /**
44
+ * Format warning message (yellow indicator)
45
+ * @param {string} message - The warning message
46
+ * @param {string} [actionText] - What the user should do
47
+ * @param {string} [commandHint] - Command to run
48
+ * @returns {string} Formatted warning string
49
+ */
50
+ formatWarning(message, actionText, commandHint) {
51
+ let output = `${c.yellow}\u26A0${c.reset} ${message}`;
52
+ if (actionText) {
53
+ output += ` ${c.dim}|${c.reset} ${c.cyan}Action:${c.reset} ${actionText}`;
54
+ }
55
+ if (commandHint) {
56
+ output += ` ${c.dim}|${c.reset} ${c.green}Run:${c.reset} ${c.bold}${commandHint}${c.reset}`;
57
+ }
58
+ return output;
59
+ }
60
+
61
+ /**
62
+ * INFO: Non-blocking issue, continue execution (does NOT exit)
63
+ * Use for informational messages about issues that don't prevent operation.
64
+ *
65
+ * @param {string} message - The info message
66
+ * @param {string} [actionText] - Optional action hint
67
+ * @param {string} [commandHint] - Optional command hint
68
+ */
69
+ info(message, actionText, commandHint) {
70
+ console.log(this.formatWarning(message, actionText, commandHint));
71
+ // Does NOT exit - allows continuation
72
+ }
73
+
74
+ /**
75
+ * WARNING: Issue that should be addressed (exit 1)
76
+ * Use for problems that prevent the operation from completing properly.
77
+ *
78
+ * @param {string} message - The warning message
79
+ * @param {string} [actionText] - What the user should do
80
+ * @param {string} [commandHint] - Command to run to fix it
81
+ */
82
+ warning(message, actionText, commandHint) {
83
+ console.error(this.formatError(message, actionText, commandHint));
84
+ process.exit(1);
85
+ }
86
+
87
+ /**
88
+ * CRITICAL: Severe error (exit 1 + stack trace if DEBUG=1)
89
+ * Use for unexpected errors, crashes, or severe failures.
90
+ *
91
+ * @param {string} message - The error message
92
+ * @param {string} [actionText] - What the user should do
93
+ * @param {string} [commandHint] - Command to run
94
+ * @param {Error} [error] - Original error object for stack trace
95
+ */
96
+ critical(message, actionText, commandHint, error) {
97
+ console.error(this.formatError(message, actionText, commandHint));
98
+ if (process.env.DEBUG === '1' && error?.stack) {
99
+ console.error(`\n${c.dim}Stack trace:${c.reset}`);
100
+ console.error(c.dim + error.stack + c.reset);
101
+ }
102
+ process.exit(1);
103
+ }
104
+
105
+ /**
106
+ * Convenience method: Get formatted error string without exiting
107
+ * Useful for collecting multiple errors before displaying.
108
+ *
109
+ * @param {string} message - The error message
110
+ * @param {string} [actionText] - What the user should do
111
+ * @param {string} [commandHint] - Command to run
112
+ * @returns {string} Formatted error string
113
+ */
114
+ errorWithAction(message, actionText, commandHint) {
115
+ return this.formatError(message, actionText, commandHint);
116
+ }
117
+
118
+ /**
119
+ * Convenience method: Get formatted warning string without exiting
120
+ *
121
+ * @param {string} message - The warning message
122
+ * @param {string} [actionText] - What the user should do
123
+ * @param {string} [commandHint] - Command to run
124
+ * @returns {string} Formatted warning string
125
+ */
126
+ warningWithAction(message, actionText, commandHint) {
127
+ return this.formatWarning(message, actionText, commandHint);
128
+ }
129
+
130
+ /**
131
+ * Display multiple issues at once, then exit with appropriate code
132
+ * Useful for validation that collects all errors before reporting.
133
+ *
134
+ * @param {Array<{type: 'info'|'warning'|'error', message: string, action?: string, command?: string}>} issues
135
+ * @returns {boolean} True if there were errors/warnings (would normally exit)
136
+ */
137
+ reportIssues(issues) {
138
+ let hasErrors = false;
139
+ let hasWarnings = false;
140
+
141
+ for (const issue of issues) {
142
+ if (issue.type === 'error') {
143
+ console.error(this.formatError(issue.message, issue.action, issue.command));
144
+ hasErrors = true;
145
+ } else if (issue.type === 'warning') {
146
+ console.error(this.formatWarning(issue.message, issue.action, issue.command));
147
+ hasWarnings = true;
148
+ } else {
149
+ console.log(this.formatWarning(issue.message, issue.action, issue.command));
150
+ }
151
+ }
152
+
153
+ if (hasErrors || hasWarnings) {
154
+ process.exit(1);
155
+ }
156
+
157
+ return hasErrors || hasWarnings;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Create a pre-configured ErrorHandler instance
163
+ * @param {string} commandName - Name of the command
164
+ * @returns {ErrorHandler} New ErrorHandler instance
165
+ */
166
+ function createErrorHandler(commandName) {
167
+ return new ErrorHandler(commandName);
168
+ }
169
+
170
+ module.exports = {
171
+ ErrorHandler,
172
+ createErrorHandler,
173
+ };
@@ -9,6 +9,7 @@ const inquirer = require('inquirer');
9
9
  const path = require('node:path');
10
10
  const fs = require('node:fs');
11
11
  const { IdeManager } = require('../installers/ide/manager');
12
+ const { BRAND_HEX } = require('../../../lib/colors');
12
13
 
13
14
  // Load package.json for version
14
15
  const packageJsonPath = path.join(__dirname, '..', '..', '..', 'package.json');
@@ -25,7 +26,7 @@ function displayLogo() {
25
26
  ██╔══██║██║ ██║██║██║ ██╔══╝ ██╔══╝ ██║ ██║ ██║██║███╗██║
26
27
  ██║ ██║╚██████╔╝██║███████╗███████╗██║ ███████╗╚██████╔╝╚███╔███╔╝
27
28
  ╚═╝ ╚═╝ ╚═════╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ `;
28
- console.log(chalk.hex('#e8683a')(logo));
29
+ console.log(chalk.hex(BRAND_HEX)(logo));
29
30
  console.log(chalk.dim(` AgileFlow v${packageJson.version} - AI-Driven Agile Development\n`));
30
31
  }
31
32
 
@@ -35,7 +36,7 @@ function displayLogo() {
35
36
  * @param {string} subtitle - Optional subtitle
36
37
  */
37
38
  function displaySection(title, subtitle = null) {
38
- console.log(chalk.bold.hex('#e8683a')(`\n${title}`));
39
+ console.log(chalk.bold.hex(BRAND_HEX)(`\n${title}`));
39
40
  if (subtitle) {
40
41
  console.log(chalk.dim(subtitle));
41
42
  }