agileflow 2.51.0 → 2.56.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 (90) hide show
  1. package/README.md +80 -460
  2. package/package.json +18 -3
  3. package/scripts/agileflow-configure.js +134 -63
  4. package/scripts/agileflow-welcome.js +161 -31
  5. package/scripts/generators/agent-registry.js +45 -57
  6. package/scripts/generators/command-registry.js +48 -32
  7. package/scripts/generators/index.js +2 -6
  8. package/scripts/generators/inject-babysit.js +9 -2
  9. package/scripts/generators/inject-help.js +3 -1
  10. package/scripts/generators/inject-readme.js +7 -3
  11. package/scripts/generators/skill-registry.js +60 -33
  12. package/scripts/get-env.js +13 -12
  13. package/scripts/lib/frontmatter-parser.js +82 -0
  14. package/scripts/obtain-context.js +79 -26
  15. package/scripts/session-coordinator.sh +232 -0
  16. package/scripts/session-manager.js +512 -0
  17. package/src/core/agents/orchestrator.md +275 -0
  18. package/src/core/commands/adr.md +38 -16
  19. package/src/core/commands/agent.md +39 -22
  20. package/src/core/commands/assign.md +17 -0
  21. package/src/core/commands/auto.md +60 -46
  22. package/src/core/commands/babysit.md +302 -637
  23. package/src/core/commands/baseline.md +20 -0
  24. package/src/core/commands/blockers.md +33 -48
  25. package/src/core/commands/board.md +19 -0
  26. package/src/core/commands/changelog.md +20 -0
  27. package/src/core/commands/ci.md +17 -0
  28. package/src/core/commands/context.md +43 -40
  29. package/src/core/commands/debt.md +76 -45
  30. package/src/core/commands/deploy.md +20 -0
  31. package/src/core/commands/deps.md +40 -46
  32. package/src/core/commands/diagnose.md +24 -18
  33. package/src/core/commands/docs.md +18 -0
  34. package/src/core/commands/epic.md +31 -0
  35. package/src/core/commands/feedback.md +33 -21
  36. package/src/core/commands/handoff.md +29 -0
  37. package/src/core/commands/help.md +16 -7
  38. package/src/core/commands/impact.md +31 -61
  39. package/src/core/commands/metrics.md +17 -35
  40. package/src/core/commands/packages.md +21 -0
  41. package/src/core/commands/pr.md +15 -0
  42. package/src/core/commands/readme-sync.md +42 -9
  43. package/src/core/commands/research.md +58 -11
  44. package/src/core/commands/retro.md +42 -50
  45. package/src/core/commands/review.md +22 -27
  46. package/src/core/commands/session/end.md +53 -297
  47. package/src/core/commands/session/history.md +38 -257
  48. package/src/core/commands/session/init.md +44 -446
  49. package/src/core/commands/session/new.md +152 -0
  50. package/src/core/commands/session/resume.md +51 -447
  51. package/src/core/commands/session/status.md +32 -244
  52. package/src/core/commands/sprint.md +33 -0
  53. package/src/core/commands/status.md +18 -0
  54. package/src/core/commands/story-validate.md +32 -0
  55. package/src/core/commands/story.md +21 -6
  56. package/src/core/commands/template.md +18 -0
  57. package/src/core/commands/tests.md +22 -0
  58. package/src/core/commands/update.md +72 -58
  59. package/src/core/commands/validate-expertise.md +25 -37
  60. package/src/core/commands/velocity.md +33 -74
  61. package/src/core/commands/verify.md +16 -0
  62. package/src/core/experts/documentation/expertise.yaml +16 -2
  63. package/src/core/skills/agileflow-retro-facilitator/SKILL.md +57 -219
  64. package/src/core/skills/agileflow-retro-facilitator/cookbook/4ls.md +86 -0
  65. package/src/core/skills/agileflow-retro-facilitator/cookbook/glad-sad-mad.md +79 -0
  66. package/src/core/skills/agileflow-retro-facilitator/cookbook/start-stop-continue.md +142 -0
  67. package/src/core/skills/agileflow-retro-facilitator/prompts/action-items.md +83 -0
  68. package/src/core/skills/writing-skills/SKILL.md +352 -0
  69. package/src/core/skills/writing-skills/testing-skills-with-subagents.md +232 -0
  70. package/tools/cli/agileflow-cli.js +4 -2
  71. package/tools/cli/commands/config.js +20 -13
  72. package/tools/cli/commands/doctor.js +25 -9
  73. package/tools/cli/commands/list.js +10 -6
  74. package/tools/cli/commands/setup.js +54 -3
  75. package/tools/cli/commands/status.js +6 -8
  76. package/tools/cli/commands/uninstall.js +5 -5
  77. package/tools/cli/commands/update.js +51 -7
  78. package/tools/cli/installers/core/installer.js +8 -4
  79. package/tools/cli/installers/ide/_base-ide.js +58 -1
  80. package/tools/cli/installers/ide/claude-code.js +3 -61
  81. package/tools/cli/installers/ide/codex.js +440 -0
  82. package/tools/cli/installers/ide/cursor.js +21 -51
  83. package/tools/cli/installers/ide/manager.js +2 -6
  84. package/tools/cli/installers/ide/windsurf.js +20 -50
  85. package/tools/cli/lib/content-injector.js +26 -49
  86. package/tools/cli/lib/docs-setup.js +3 -2
  87. package/tools/cli/lib/npm-utils.js +39 -12
  88. package/tools/cli/lib/ui.js +31 -10
  89. package/tools/cli/lib/version-checker.js +3 -3
  90. package/tools/postinstall.js +2 -3
@@ -7,7 +7,7 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
- const yaml = require('js-yaml');
10
+ const { parseFrontmatter, normalizeTools } = require('../../../scripts/lib/frontmatter-parser');
11
11
 
12
12
  /**
13
13
  * Scan agents directory and generate formatted agent list
@@ -22,34 +22,20 @@ function generateAgentList(agentsDir) {
22
22
  const filePath = path.join(agentsDir, file);
23
23
  const content = fs.readFileSync(filePath, 'utf8');
24
24
 
25
- // Extract YAML frontmatter
26
- const match = content.match(/^---\n([\s\S]*?)\n---/);
27
- if (!match) continue;
28
-
29
- try {
30
- const frontmatter = yaml.load(match[1]);
31
-
32
- // Skip if frontmatter is null or not an object
33
- if (!frontmatter || typeof frontmatter !== 'object') {
34
- continue;
35
- }
36
-
37
- // Handle tools field - can be array or string
38
- let tools = frontmatter.tools || [];
39
- if (typeof tools === 'string') {
40
- tools = tools.split(',').map(t => t.trim());
41
- }
42
-
43
- agents.push({
44
- name: frontmatter.name || path.basename(file, '.md'),
45
- description: frontmatter.description || '',
46
- tools: tools,
47
- model: frontmatter.model || 'haiku'
48
- });
49
- } catch (err) {
50
- // Silently skip files with parsing errors
25
+ // Parse frontmatter using shared parser
26
+ const frontmatter = parseFrontmatter(content);
27
+
28
+ // Skip if no frontmatter found
29
+ if (!frontmatter || Object.keys(frontmatter).length === 0) {
51
30
  continue;
52
31
  }
32
+
33
+ agents.push({
34
+ name: frontmatter.name || path.basename(file, '.md'),
35
+ description: frontmatter.description || '',
36
+ tools: normalizeTools(frontmatter.tools),
37
+ model: frontmatter.model || 'haiku',
38
+ });
53
39
  }
54
40
 
55
41
  // Sort alphabetically by name
@@ -82,29 +68,20 @@ function generateCommandList(commandsDir) {
82
68
  const filePath = path.join(commandsDir, file);
83
69
  const content = fs.readFileSync(filePath, 'utf8');
84
70
 
85
- // Extract YAML frontmatter
86
- const match = content.match(/^---\n([\s\S]*?)\n---/);
87
- if (!match) continue;
88
-
89
- try {
90
- const frontmatter = yaml.load(match[1]);
91
- const cmdName = path.basename(file, '.md');
92
-
93
- // Skip if frontmatter is null or not an object
94
- if (!frontmatter || typeof frontmatter !== 'object') {
95
- continue;
96
- }
97
-
98
- commands.push({
99
- name: cmdName,
100
- description: frontmatter.description || '',
101
- argumentHint: frontmatter['argument-hint'] || ''
102
- });
103
- } catch (err) {
104
- // Silently skip files with parsing errors - they might be generated files
105
- // with content that looks like frontmatter but isn't
71
+ // Parse frontmatter using shared parser
72
+ const frontmatter = parseFrontmatter(content);
73
+ const cmdName = path.basename(file, '.md');
74
+
75
+ // Skip if no frontmatter found
76
+ if (!frontmatter || Object.keys(frontmatter).length === 0) {
106
77
  continue;
107
78
  }
79
+
80
+ commands.push({
81
+ name: cmdName,
82
+ description: frontmatter.description || '',
83
+ argumentHint: frontmatter['argument-hint'] || '',
84
+ });
108
85
  }
109
86
 
110
87
  // Sort alphabetically by name
@@ -149,5 +126,5 @@ function injectContent(templateContent, agentsDir, commandsDir) {
149
126
  module.exports = {
150
127
  generateAgentList,
151
128
  generateCommandList,
152
- injectContent
129
+ injectContent,
153
130
  };
@@ -25,7 +25,8 @@ try {
25
25
  );
26
26
  } catch (e) {
27
27
  // Fallback if README not found
28
- packageReadme = '# AgileFlow\n\nSee https://github.com/projectquestorg/AgileFlow for documentation.';
28
+ packageReadme =
29
+ '# AgileFlow\n\nSee https://github.com/projectquestorg/AgileFlow for documentation.';
29
30
  }
30
31
 
31
32
  /**
@@ -386,7 +387,7 @@ Document your CI/CD workflows and configuration here.
386
387
 
387
388
  if (fs.existsSync(gitignorePath)) {
388
389
  const existingGitignore = await fs.readFile(gitignorePath, 'utf8');
389
- const newEntries = gitignoreEntries.filter((entry) => !existingGitignore.includes(entry));
390
+ const newEntries = gitignoreEntries.filter(entry => !existingGitignore.includes(entry));
390
391
  if (newEntries.length > 0) {
391
392
  await fs.appendFile(gitignorePath, '\n' + newEntries.join('\n') + '\n', 'utf8');
392
393
  console.log(chalk.yellow(` ↻ Updated .gitignore with ${newEntries.length} entries`));
@@ -2,17 +2,36 @@
2
2
  * AgileFlow CLI - npm Registry Utilities
3
3
  *
4
4
  * Utilities for interacting with the npm registry.
5
+ * Set DEBUG_NPM=1 environment variable for verbose error logging.
5
6
  */
6
7
 
7
8
  const https = require('https');
8
9
 
10
+ // Debug mode: set DEBUG_NPM=1 to see error details
11
+ const DEBUG = process.env.DEBUG_NPM === '1';
12
+
13
+ /**
14
+ * Log debug messages when DEBUG_NPM=1
15
+ * @param {string} message - Message to log
16
+ * @param {*} data - Optional data to include
17
+ */
18
+ function debugLog(message, data = null) {
19
+ if (DEBUG) {
20
+ console.error(`[npm-utils] ${message}`, data ? JSON.stringify(data) : '');
21
+ }
22
+ }
23
+
9
24
  /**
10
25
  * Get the latest version of a package from npm registry
11
- * @param {string} packageName - Name of the package
12
- * @returns {Promise<string|null>} Latest version or null if error
26
+ *
27
+ * Returns null on any error (network, timeout, invalid response) since
28
+ * version checking should not block the CLI. Set DEBUG_NPM=1 to see errors.
29
+ *
30
+ * @param {string} packageName - Name of the package (e.g., 'agileflow' or '@scope/pkg')
31
+ * @returns {Promise<string|null>} Latest version string or null if unavailable
13
32
  */
14
33
  async function getLatestVersion(packageName) {
15
- return new Promise((resolve) => {
34
+ return new Promise(resolve => {
16
35
  const options = {
17
36
  hostname: 'registry.npmjs.org',
18
37
  port: 443,
@@ -23,32 +42,40 @@ async function getLatestVersion(packageName) {
23
42
  },
24
43
  };
25
44
 
26
- const req = https.request(options, (res) => {
45
+ debugLog('Fetching version', { package: packageName, path: options.path });
46
+
47
+ const req = https.request(options, res => {
27
48
  let data = '';
28
49
 
29
- res.on('data', (chunk) => {
50
+ res.on('data', chunk => {
30
51
  data += chunk;
31
52
  });
32
53
 
33
54
  res.on('end', () => {
55
+ if (res.statusCode !== 200) {
56
+ debugLog('Non-200 status', { statusCode: res.statusCode });
57
+ return resolve(null);
58
+ }
59
+
34
60
  try {
35
- if (res.statusCode === 200) {
36
- const json = JSON.parse(data);
37
- resolve(json.version || null);
38
- } else {
39
- resolve(null);
40
- }
61
+ const json = JSON.parse(data);
62
+ const version = json.version || null;
63
+ debugLog('Version found', { version });
64
+ resolve(version);
41
65
  } catch (err) {
66
+ debugLog('JSON parse error', { error: err.message });
42
67
  resolve(null);
43
68
  }
44
69
  });
45
70
  });
46
71
 
47
- req.on('error', () => {
72
+ req.on('error', err => {
73
+ debugLog('Network error', { error: err.message });
48
74
  resolve(null);
49
75
  });
50
76
 
51
77
  req.setTimeout(5000, () => {
78
+ debugLog('Request timeout');
52
79
  req.destroy();
53
80
  resolve(null);
54
81
  });
@@ -8,6 +8,7 @@ const chalk = require('chalk');
8
8
  const inquirer = require('inquirer');
9
9
  const path = require('node:path');
10
10
  const fs = require('node:fs');
11
+ const { IdeManager } = require('../installers/ide/manager');
11
12
 
12
13
  // Load package.json for version
13
14
  const packageJsonPath = path.join(__dirname, '..', '..', '..', 'package.json');
@@ -73,8 +74,27 @@ function info(message) {
73
74
  console.log(chalk.dim(` ${message}`));
74
75
  }
75
76
 
77
+ // IDE Manager instance for dynamic IDE discovery
78
+ const ideManager = new IdeManager();
79
+
80
+ /**
81
+ * Get available IDE choices dynamically from installed handlers
82
+ * @returns {Array} IDE choices formatted for inquirer
83
+ */
84
+ function getIdeChoices() {
85
+ const ides = ideManager.getAvailableIdes();
86
+
87
+ return ides.map((ide, index) => ({
88
+ name: ide.name,
89
+ value: ide.value,
90
+ // First IDE (preferred) is checked by default
91
+ checked: ide.preferred || index === 0,
92
+ }));
93
+ }
94
+
76
95
  /**
77
- * Available IDE configurations
96
+ * @deprecated Use getIdeChoices() instead - dynamically loaded from IDE handlers
97
+ * Legacy hardcoded IDE choices kept for backward compatibility
78
98
  */
79
99
  const IDE_CHOICES = [
80
100
  {
@@ -82,7 +102,7 @@ const IDE_CHOICES = [
82
102
  value: 'claude-code',
83
103
  checked: true,
84
104
  configDir: '.claude/commands',
85
- description: 'Anthropic\'s Claude Code IDE',
105
+ description: "Anthropic's Claude Code IDE",
86
106
  },
87
107
  {
88
108
  name: 'Cursor',
@@ -96,7 +116,7 @@ const IDE_CHOICES = [
96
116
  value: 'windsurf',
97
117
  checked: false,
98
118
  configDir: '.windsurf/workflows',
99
- description: 'Codeium\'s AI IDE',
119
+ description: "Codeium's AI IDE",
100
120
  },
101
121
  ];
102
122
 
@@ -113,7 +133,7 @@ async function promptInstall() {
113
133
  name: 'directory',
114
134
  message: 'Where would you like to install AgileFlow?',
115
135
  default: '.',
116
- validate: (input) => {
136
+ validate: input => {
117
137
  const resolved = path.resolve(input);
118
138
  const parent = path.dirname(resolved);
119
139
  if (!fs.existsSync(parent)) {
@@ -126,8 +146,8 @@ async function promptInstall() {
126
146
  type: 'checkbox',
127
147
  name: 'ides',
128
148
  message: 'Select your IDE(s):',
129
- choices: IDE_CHOICES,
130
- validate: (input) => {
149
+ choices: getIdeChoices(),
150
+ validate: input => {
131
151
  if (input.length === 0) {
132
152
  return 'Please select at least one IDE';
133
153
  }
@@ -145,7 +165,7 @@ async function promptInstall() {
145
165
  name: 'agileflowFolder',
146
166
  message: 'AgileFlow installation folder name:',
147
167
  default: '.agileflow',
148
- validate: (input) => {
168
+ validate: input => {
149
169
  if (!/^[a-zA-Z0-9._-]+$/.test(input)) {
150
170
  return 'Folder name can only contain letters, numbers, dots, underscores, and hyphens';
151
171
  }
@@ -157,7 +177,7 @@ async function promptInstall() {
157
177
  name: 'docsFolder',
158
178
  message: 'Documentation folder name:',
159
179
  default: 'docs',
160
- validate: (input) => {
180
+ validate: input => {
161
181
  if (!/^[a-zA-Z0-9._-]+$/.test(input)) {
162
182
  return 'Folder name can only contain letters, numbers, dots, underscores, and hyphens';
163
183
  }
@@ -206,7 +226,7 @@ async function confirm(message, defaultValue = true) {
206
226
  * @returns {Object|null}
207
227
  */
208
228
  function getIdeConfig(ideName) {
209
- return IDE_CHOICES.find((ide) => ide.value === ideName) || null;
229
+ return IDE_CHOICES.find(ide => ide.value === ideName) || null;
210
230
  }
211
231
 
212
232
  module.exports = {
@@ -219,5 +239,6 @@ module.exports = {
219
239
  promptInstall,
220
240
  confirm,
221
241
  getIdeConfig,
222
- IDE_CHOICES,
242
+ getIdeChoices,
243
+ IDE_CHOICES, // @deprecated - kept for backward compatibility
223
244
  };
@@ -18,17 +18,17 @@ const packageJson = require(packageJsonPath);
18
18
  * @returns {Promise<string|null>} Latest version or null
19
19
  */
20
20
  async function getLatestVersion(packageName = 'agileflow') {
21
- return new Promise((resolve) => {
21
+ return new Promise(resolve => {
22
22
  const url = `https://registry.npmjs.org/${packageName}/latest`;
23
23
 
24
- const req = https.get(url, { timeout: 5000 }, (res) => {
24
+ const req = https.get(url, { timeout: 5000 }, res => {
25
25
  if (res.statusCode !== 200) {
26
26
  resolve(null);
27
27
  return;
28
28
  }
29
29
 
30
30
  let data = '';
31
- res.on('data', (chunk) => {
31
+ res.on('data', chunk => {
32
32
  data += chunk;
33
33
  });
34
34
 
@@ -137,8 +137,8 @@ async function runAutoInstall() {
137
137
  output: process.stdout,
138
138
  });
139
139
 
140
- const answer = await new Promise((resolve) => {
141
- readline.question('Choose an option (1 or 2): ', (ans) => {
140
+ const answer = await new Promise(resolve => {
141
+ readline.question('Choose an option (1 or 2): ', ans => {
142
142
  readline.close();
143
143
  resolve(ans.trim());
144
144
  });
@@ -176,7 +176,6 @@ async function runAutoInstall() {
176
176
  console.log('');
177
177
  log('To skip auto-setup in the future, set: AGILEFLOW_SKIP_INSTALL=true', 'dim');
178
178
  console.log('');
179
-
180
179
  } catch (error) {
181
180
  console.log('');
182
181
  log('⚠️ Auto-setup encountered an issue.', 'yellow');