agileflow 2.84.2 → 2.86.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.
@@ -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
  }