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
@@ -13,7 +13,10 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
- const { execSync } = require('child_process');
16
+ const { execSync, spawnSync } = require('child_process');
17
+
18
+ // Session manager path (relative to script location)
19
+ const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
17
20
 
18
21
  // ANSI color codes
19
22
  const c = {
@@ -39,9 +42,16 @@ const c = {
39
42
 
40
43
  // Box drawing characters
41
44
  const box = {
42
- tl: '╭', tr: '╮', bl: '╰', br: '╯',
43
- h: '', v: '│',
44
- lT: '', rT: '┤', tT: '┬', bT: '┴',
45
+ tl: '╭',
46
+ tr: '',
47
+ bl: '',
48
+ br: '╯',
49
+ h: '─',
50
+ v: '│',
51
+ lT: '├',
52
+ rT: '┤',
53
+ tT: '┬',
54
+ bT: '┴',
45
55
  cross: '┼',
46
56
  };
47
57
 
@@ -70,7 +80,9 @@ function getProjectInfo(rootDir) {
70
80
 
71
81
  // Get package info
72
82
  try {
73
- const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'packages/cli/package.json'), 'utf8'));
83
+ const pkg = JSON.parse(
84
+ fs.readFileSync(path.join(rootDir, 'packages/cli/package.json'), 'utf8')
85
+ );
74
86
  info.version = pkg.version || info.version;
75
87
  } catch (e) {
76
88
  try {
@@ -83,7 +95,10 @@ function getProjectInfo(rootDir) {
83
95
  try {
84
96
  info.branch = execSync('git branch --show-current', { cwd: rootDir, encoding: 'utf8' }).trim();
85
97
  info.commit = execSync('git rev-parse --short HEAD', { cwd: rootDir, encoding: 'utf8' }).trim();
86
- info.lastCommit = execSync('git log -1 --format="%s"', { cwd: rootDir, encoding: 'utf8' }).trim();
98
+ info.lastCommit = execSync('git log -1 --format="%s"', {
99
+ cwd: rootDir,
100
+ encoding: 'utf8',
101
+ }).trim();
87
102
  } catch (e) {}
88
103
 
89
104
  // Get status info
@@ -155,7 +170,7 @@ function runArchival(rootDir) {
155
170
  execSync('bash scripts/archive-completed-stories.sh', {
156
171
  cwd: rootDir,
157
172
  encoding: 'utf8',
158
- stdio: 'pipe'
173
+ stdio: 'pipe',
159
174
  });
160
175
  result.archived = toArchiveCount;
161
176
  result.remaining -= toArchiveCount;
@@ -201,6 +216,60 @@ function clearActiveCommands(rootDir) {
201
216
  return result;
202
217
  }
203
218
 
219
+ function checkParallelSessions(rootDir) {
220
+ const result = {
221
+ available: false,
222
+ registered: false,
223
+ otherActive: 0,
224
+ currentId: null,
225
+ cleaned: 0,
226
+ };
227
+
228
+ try {
229
+ // Check if session manager exists
230
+ const managerPath = path.join(rootDir, '.agileflow', 'scripts', 'session-manager.js');
231
+ if (!fs.existsSync(managerPath) && !fs.existsSync(SESSION_MANAGER_PATH)) {
232
+ return result;
233
+ }
234
+
235
+ result.available = true;
236
+
237
+ // Try to register current session and get status
238
+ const scriptPath = fs.existsSync(managerPath) ? managerPath : SESSION_MANAGER_PATH;
239
+
240
+ // Register this session
241
+ try {
242
+ const registerOutput = execSync(`node "${scriptPath}" register`, {
243
+ cwd: rootDir,
244
+ encoding: 'utf8',
245
+ stdio: ['pipe', 'pipe', 'pipe'],
246
+ });
247
+ const registerData = JSON.parse(registerOutput);
248
+ result.registered = true;
249
+ result.currentId = registerData.id;
250
+ } catch (e) {
251
+ // Registration failed, continue anyway
252
+ }
253
+
254
+ // Get count of other active sessions
255
+ try {
256
+ const countOutput = execSync(`node "${scriptPath}" count`, {
257
+ cwd: rootDir,
258
+ encoding: 'utf8',
259
+ stdio: ['pipe', 'pipe', 'pipe'],
260
+ });
261
+ const countData = JSON.parse(countOutput);
262
+ result.otherActive = countData.count || 0;
263
+ } catch (e) {
264
+ // Count failed
265
+ }
266
+ } catch (e) {
267
+ // Session system not available
268
+ }
269
+
270
+ return result;
271
+ }
272
+
204
273
  function checkPreCompact(rootDir) {
205
274
  const result = { configured: false, scriptExists: false, version: null, outdated: false };
206
275
 
@@ -258,7 +327,7 @@ function getFeatureVersions(rootDir) {
258
327
  hooks: { version: null, outdated: false },
259
328
  archival: { version: null, outdated: false },
260
329
  statusline: { version: null, outdated: false },
261
- precompact: { version: null, outdated: false }
330
+ precompact: { version: null, outdated: false },
262
331
  };
263
332
 
264
333
  // Minimum compatible versions for each feature
@@ -266,7 +335,7 @@ function getFeatureVersions(rootDir) {
266
335
  hooks: '2.35.0',
267
336
  archival: '2.35.0',
268
337
  statusline: '2.35.0',
269
- precompact: '2.40.0' // Multi-command support
338
+ precompact: '2.40.0', // Multi-command support
270
339
  };
271
340
 
272
341
  try {
@@ -277,7 +346,8 @@ function getFeatureVersions(rootDir) {
277
346
  for (const feature of Object.keys(result)) {
278
347
  if (metadata.features?.[feature]?.configured_version) {
279
348
  result[feature].version = metadata.features[feature].configured_version;
280
- result[feature].outdated = compareVersions(result[feature].version, minVersions[feature]) < 0;
349
+ result[feature].outdated =
350
+ compareVersions(result[feature].version, minVersions[feature]) < 0;
281
351
  }
282
352
  }
283
353
  }
@@ -291,7 +361,8 @@ function pad(str, len, align = 'left') {
291
361
  const diff = len - stripped.length;
292
362
  if (diff <= 0) return str;
293
363
  if (align === 'right') return ' '.repeat(diff) + str;
294
- if (align === 'center') return ' '.repeat(Math.floor(diff/2)) + str + ' '.repeat(Math.ceil(diff/2));
364
+ if (align === 'center')
365
+ return ' '.repeat(Math.floor(diff / 2)) + str + ' '.repeat(Math.ceil(diff / 2));
295
366
  return str + ' '.repeat(diff);
296
367
  }
297
368
 
@@ -323,7 +394,7 @@ function truncate(str, maxLen, suffix = '..') {
323
394
  return str.substring(0, cutIndex) + suffix;
324
395
  }
325
396
 
326
- function formatTable(info, archival, session, precompact) {
397
+ function formatTable(info, archival, session, precompact, parallelSessions) {
327
398
  const W = 58; // inner width
328
399
  const R = W - 24; // right column width (34 chars)
329
400
  const lines = [];
@@ -336,17 +407,20 @@ function formatTable(info, archival, session, precompact) {
336
407
  return `${c.dim}${box.v}${c.reset} ${pad(leftStr, 20)} ${c.dim}${box.v}${c.reset} ${pad(rightStr, R)} ${c.dim}${box.v}${c.reset}`;
337
408
  };
338
409
 
339
- const divider = () => `${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 22)}${box.rT}${c.reset}`;
410
+ const divider = () =>
411
+ `${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 22)}${box.rT}${c.reset}`;
340
412
  const topBorder = `${c.dim}${box.tl}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 22)}${box.tr}${c.reset}`;
341
413
  const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 22)}${box.br}${c.reset}`;
342
414
 
343
415
  // Header (truncate branch name if too long)
344
- const branchColor = info.branch === 'main' ? c.green : info.branch.startsWith('fix') ? c.red : c.cyan;
416
+ const branchColor =
417
+ info.branch === 'main' ? c.green : info.branch.startsWith('fix') ? c.red : c.cyan;
345
418
  // Fixed parts: "agileflow " (10) + "v" (1) + version + " " (2) + " (" (2) + commit (7) + ")" (1) = 23 + version.length
346
- const maxBranchLen = (W - 1) - 23 - info.version.length;
347
- const branchDisplay = info.branch.length > maxBranchLen
348
- ? info.branch.substring(0, maxBranchLen - 2) + '..'
349
- : info.branch;
419
+ const maxBranchLen = W - 1 - 23 - info.version.length;
420
+ const branchDisplay =
421
+ info.branch.length > maxBranchLen
422
+ ? info.branch.substring(0, maxBranchLen - 2) + '..'
423
+ : info.branch;
350
424
  const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}v${info.version}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
351
425
  const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 1)} ${c.dim}${box.v}${c.reset}`;
352
426
 
@@ -355,10 +429,38 @@ function formatTable(info, archival, session, precompact) {
355
429
  lines.push(divider());
356
430
 
357
431
  // Stories section
358
- lines.push(row('In Progress', info.wipCount > 0 ? `${info.wipCount}` : '0', c.dim, info.wipCount > 0 ? c.yellow : c.dim));
359
- lines.push(row('Blocked', info.blockedCount > 0 ? `${info.blockedCount}` : '0', c.dim, info.blockedCount > 0 ? c.red : c.dim));
360
- lines.push(row('Ready', info.readyCount > 0 ? `${info.readyCount}` : '0', c.dim, info.readyCount > 0 ? c.cyan : c.dim));
361
- lines.push(row('Completed', info.completedCount > 0 ? `${info.completedCount}` : '0', c.dim, info.completedCount > 0 ? c.green : c.dim));
432
+ lines.push(
433
+ row(
434
+ 'In Progress',
435
+ info.wipCount > 0 ? `${info.wipCount}` : '0',
436
+ c.dim,
437
+ info.wipCount > 0 ? c.yellow : c.dim
438
+ )
439
+ );
440
+ lines.push(
441
+ row(
442
+ 'Blocked',
443
+ info.blockedCount > 0 ? `${info.blockedCount}` : '0',
444
+ c.dim,
445
+ info.blockedCount > 0 ? c.red : c.dim
446
+ )
447
+ );
448
+ lines.push(
449
+ row(
450
+ 'Ready',
451
+ info.readyCount > 0 ? `${info.readyCount}` : '0',
452
+ c.dim,
453
+ info.readyCount > 0 ? c.cyan : c.dim
454
+ )
455
+ );
456
+ lines.push(
457
+ row(
458
+ 'Completed',
459
+ info.completedCount > 0 ? `${info.completedCount}` : '0',
460
+ c.dim,
461
+ info.completedCount > 0 ? c.green : c.dim
462
+ )
463
+ );
362
464
 
363
465
  lines.push(divider());
364
466
 
@@ -366,16 +468,15 @@ function formatTable(info, archival, session, precompact) {
366
468
  if (archival.disabled) {
367
469
  lines.push(row('Auto-archival', 'disabled', c.dim, c.dim));
368
470
  } else {
369
- const archivalStatus = archival.archived > 0
370
- ? `archived ${archival.archived} stories`
371
- : `nothing to archive`;
372
- lines.push(row('Auto-archival', archivalStatus, c.dim, archival.archived > 0 ? c.green : c.dim));
471
+ const archivalStatus =
472
+ archival.archived > 0 ? `archived ${archival.archived} stories` : `nothing to archive`;
473
+ lines.push(
474
+ row('Auto-archival', archivalStatus, c.dim, archival.archived > 0 ? c.green : c.dim)
475
+ );
373
476
  }
374
477
 
375
478
  // Session cleanup
376
- const sessionStatus = session.cleared > 0
377
- ? `cleared ${session.cleared} command(s)`
378
- : `clean`;
479
+ const sessionStatus = session.cleared > 0 ? `cleared ${session.cleared} command(s)` : `clean`;
379
480
  lines.push(row('Session state', sessionStatus, c.dim, session.cleared > 0 ? c.green : c.dim));
380
481
 
381
482
  // PreCompact status with version check
@@ -396,11 +497,31 @@ function formatTable(info, archival, session, precompact) {
396
497
  lines.push(row('Context preserve', 'not configured', c.dim, c.dim));
397
498
  }
398
499
 
500
+ // Parallel sessions status
501
+ if (parallelSessions && parallelSessions.available) {
502
+ if (parallelSessions.otherActive > 0) {
503
+ const sessionStr = `⚠️ ${parallelSessions.otherActive} other active`;
504
+ lines.push(row('Sessions', sessionStr, c.dim, c.yellow));
505
+ } else {
506
+ const sessionStr = parallelSessions.currentId
507
+ ? `✓ Session ${parallelSessions.currentId} (only)`
508
+ : '✓ Only session';
509
+ lines.push(row('Sessions', sessionStr, c.dim, c.green));
510
+ }
511
+ }
512
+
399
513
  lines.push(divider());
400
514
 
401
515
  // Current story (if any) - row() auto-truncates
402
516
  if (info.currentStory) {
403
- lines.push(row('Current', `${c.blue}${info.currentStory.id}${c.reset}: ${info.currentStory.title}`, c.dim, ''));
517
+ lines.push(
518
+ row(
519
+ 'Current',
520
+ `${c.blue}${info.currentStory.id}${c.reset}: ${info.currentStory.title}`,
521
+ c.dim,
522
+ ''
523
+ )
524
+ );
404
525
  } else {
405
526
  lines.push(row('Current', 'No active story', c.dim, c.dim));
406
527
  }
@@ -420,8 +541,17 @@ function main() {
420
541
  const archival = runArchival(rootDir);
421
542
  const session = clearActiveCommands(rootDir);
422
543
  const precompact = checkPreCompact(rootDir);
544
+ const parallelSessions = checkParallelSessions(rootDir);
545
+
546
+ console.log(formatTable(info, archival, session, precompact, parallelSessions));
423
547
 
424
- console.log(formatTable(info, archival, session, precompact));
548
+ // Show warning and tip if other sessions are active
549
+ if (parallelSessions.otherActive > 0) {
550
+ console.log('');
551
+ console.log(`${c.yellow}⚠️ Other Claude session(s) active in this repo.${c.reset}`);
552
+ console.log(`${c.dim} Run /agileflow:session:status to see all sessions.${c.reset}`);
553
+ console.log(`${c.dim} Run /agileflow:session:new to create isolated workspace.${c.reset}`);
554
+ }
425
555
  }
426
556
 
427
557
  main();
@@ -5,58 +5,25 @@
5
5
  *
6
6
  * Scans agents/ directory and extracts metadata from frontmatter.
7
7
  * Returns structured agent registry for use in generators.
8
+ *
9
+ * Set DEBUG_REGISTRY=1 for verbose logging of skipped files.
8
10
  */
9
11
 
10
12
  const fs = require('fs');
11
13
  const path = require('path');
14
+ const { extractFrontmatter, normalizeTools } = require('../lib/frontmatter-parser');
15
+
16
+ // Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
17
+ const DEBUG = process.env.DEBUG_REGISTRY === '1';
12
18
 
13
19
  /**
14
- * Extract YAML frontmatter from markdown file
15
- * Handles multi-line values like tools arrays
16
- * @param {string} filePath - Path to markdown file
17
- * @returns {object} Frontmatter object
20
+ * Log debug messages when DEBUG_REGISTRY=1
21
+ * @param {string} message - Message to log
18
22
  */
19
- function extractFrontmatter(filePath) {
20
- const content = fs.readFileSync(filePath, 'utf-8');
21
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
22
-
23
- if (!frontmatterMatch) {
24
- return {};
25
- }
26
-
27
- const frontmatter = {};
28
- const lines = frontmatterMatch[1].split('\n');
29
- let currentKey = null;
30
- let currentArray = null;
31
-
32
- for (const line of lines) {
33
- // Handle array items (lines starting with -)
34
- if (line.trim().startsWith('-')) {
35
- if (currentArray) {
36
- currentArray.push(line.trim().substring(1).trim());
37
- }
38
- continue;
39
- }
40
-
41
- // Handle key-value pairs
42
- const match = line.match(/^(\w+):\s*(.*)$/);
43
- if (match) {
44
- const [, key, value] = match;
45
- currentKey = key;
46
-
47
- // If value is empty, it's likely an array
48
- if (!value) {
49
- currentArray = [];
50
- frontmatter[key] = currentArray;
51
- } else {
52
- // Remove quotes if present
53
- frontmatter[key] = value.replace(/^["']|["']$/g, '');
54
- currentArray = null;
55
- }
56
- }
23
+ function debugLog(message) {
24
+ if (DEBUG) {
25
+ console.error(`[agent-registry] ${message}`);
57
26
  }
58
-
59
- return frontmatter;
60
27
  }
61
28
 
62
29
  /**
@@ -74,7 +41,7 @@ function categorizeAgent(name, description) {
74
41
  'Maintenance & Optimization': ['refactor', 'performance', 'monitoring'],
75
42
  'Documentation & Knowledge': ['documentation', 'readme-updater', 'research'],
76
43
  'Compliance & Governance': ['compliance', 'analytics'],
77
- 'Mentorship': ['mentor']
44
+ Mentorship: ['mentor'],
78
45
  };
79
46
 
80
47
  for (const [category, keywords] of Object.entries(categories)) {
@@ -93,25 +60,40 @@ function categorizeAgent(name, description) {
93
60
  */
94
61
  function scanAgents(agentsDir) {
95
62
  const agents = [];
96
- const files = fs.readdirSync(agentsDir);
63
+ const skipped = [];
64
+
65
+ let files;
66
+ try {
67
+ files = fs.readdirSync(agentsDir);
68
+ } catch (err) {
69
+ debugLog(`Failed to read directory: ${err.message}`);
70
+ return agents;
71
+ }
72
+
73
+ debugLog(`Scanning ${files.length} files in ${agentsDir}`);
97
74
 
98
75
  for (const file of files) {
99
- if (!file.endsWith('.md')) continue;
76
+ if (!file.endsWith('.md')) {
77
+ debugLog(`Skipping non-md file: ${file}`);
78
+ continue;
79
+ }
100
80
 
101
81
  const filePath = path.join(agentsDir, file);
102
82
  const frontmatter = extractFrontmatter(filePath);
103
83
  const name = file.replace('.md', '');
104
84
 
105
- // Parse tools array if it exists
106
- let tools = [];
107
- if (frontmatter.tools) {
108
- if (Array.isArray(frontmatter.tools)) {
109
- tools = frontmatter.tools;
110
- } else if (typeof frontmatter.tools === 'string') {
111
- tools = frontmatter.tools.split(',').map(t => t.trim());
112
- }
85
+ // Check if frontmatter was extracted successfully
86
+ if (Object.keys(frontmatter).length === 0) {
87
+ skipped.push({ file, reason: 'no frontmatter or parse error' });
88
+ debugLog(`Skipping ${file}: no frontmatter found`);
89
+ continue;
113
90
  }
114
91
 
92
+ // Normalize tools field (handles array or comma-separated string)
93
+ const tools = normalizeTools(frontmatter.tools);
94
+
95
+ debugLog(`Loaded ${file}: name="${frontmatter.name || name}", tools=${tools.length}`);
96
+
115
97
  agents.push({
116
98
  name,
117
99
  file,
@@ -121,7 +103,7 @@ function scanAgents(agentsDir) {
121
103
  tools,
122
104
  model: frontmatter.model || 'haiku',
123
105
  color: frontmatter.color || 'blue',
124
- category: categorizeAgent(name, frontmatter.description || '')
106
+ category: categorizeAgent(name, frontmatter.description || ''),
125
107
  });
126
108
  }
127
109
 
@@ -133,6 +115,12 @@ function scanAgents(agentsDir) {
133
115
  return a.name.localeCompare(b.name);
134
116
  });
135
117
 
118
+ if (skipped.length > 0) {
119
+ debugLog(`Skipped ${skipped.length} files: ${skipped.map(s => s.file).join(', ')}`);
120
+ }
121
+
122
+ debugLog(`Found ${agents.length} agents`);
123
+
136
124
  return agents;
137
125
  }
138
126
 
@@ -159,7 +147,7 @@ function main() {
159
147
  }
160
148
 
161
149
  // Export for use in other scripts
162
- module.exports = { scanAgents, extractFrontmatter, categorizeAgent };
150
+ module.exports = { scanAgents, categorizeAgent };
163
151
 
164
152
  // Run if called directly
165
153
  if (require.main === module) {
@@ -5,37 +5,25 @@
5
5
  *
6
6
  * Scans commands/ directory and extracts metadata from frontmatter.
7
7
  * Returns structured command registry for use in generators.
8
+ *
9
+ * Set DEBUG_REGISTRY=1 for verbose logging of skipped files.
8
10
  */
9
11
 
10
12
  const fs = require('fs');
11
13
  const path = require('path');
14
+ const { extractFrontmatter } = require('../lib/frontmatter-parser');
15
+
16
+ // Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
17
+ const DEBUG = process.env.DEBUG_REGISTRY === '1';
12
18
 
13
19
  /**
14
- * Extract frontmatter from markdown file
15
- * @param {string} filePath - Path to markdown file
16
- * @returns {object} Frontmatter object
20
+ * Log debug messages when DEBUG_REGISTRY=1
21
+ * @param {string} message - Message to log
17
22
  */
18
- function extractFrontmatter(filePath) {
19
- const content = fs.readFileSync(filePath, 'utf-8');
20
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
21
-
22
- if (!frontmatterMatch) {
23
- return {};
23
+ function debugLog(message) {
24
+ if (DEBUG) {
25
+ console.error(`[command-registry] ${message}`);
24
26
  }
25
-
26
- const frontmatter = {};
27
- const lines = frontmatterMatch[1].split('\n');
28
-
29
- for (const line of lines) {
30
- const match = line.match(/^(\w+):\s*(.+)$/);
31
- if (match) {
32
- const [, key, value] = match;
33
- // Remove quotes if present
34
- frontmatter[key] = value.replace(/^["']|["']$/g, '');
35
- }
36
- }
37
-
38
- return frontmatter;
39
27
  }
40
28
 
41
29
  /**
@@ -47,15 +35,15 @@ function extractFrontmatter(filePath) {
47
35
  function categorizeCommand(name, description) {
48
36
  const categories = {
49
37
  'Story Management': ['story', 'epic', 'assign', 'status'],
50
- 'Development': ['verify', 'baseline', 'resume', 'session-init', 'babysit'],
38
+ Development: ['verify', 'baseline', 'resume', 'session-init', 'babysit'],
51
39
  'Quality & Testing': ['tests', 'review', 'ci'],
52
- 'Documentation': ['docs', 'adr', 'readme-sync'],
40
+ Documentation: ['docs', 'adr', 'readme-sync'],
53
41
  'Planning & Metrics': ['sprint', 'velocity', 'metrics', 'board', 'deps'],
54
42
  'Research & Strategy': ['research', 'product'],
55
43
  'Deployment & Operations': ['deploy', 'packages'],
56
- 'Collaboration': ['update', 'handoff', 'feedback', 'retro'],
57
- 'Maintenance': ['debt', 'compress', 'template'],
58
- 'System': ['setup', 'help', 'diagnose', 'auto', 'agent']
44
+ Collaboration: ['update', 'handoff', 'feedback', 'retro'],
45
+ Maintenance: ['debt', 'compress', 'template'],
46
+ System: ['setup', 'help', 'diagnose', 'auto', 'agent'],
59
47
  };
60
48
 
61
49
  for (const [category, keywords] of Object.entries(categories)) {
@@ -74,22 +62,44 @@ function categorizeCommand(name, description) {
74
62
  */
75
63
  function scanCommands(commandsDir) {
76
64
  const commands = [];
77
- const files = fs.readdirSync(commandsDir);
65
+ const skipped = [];
66
+
67
+ let files;
68
+ try {
69
+ files = fs.readdirSync(commandsDir);
70
+ } catch (err) {
71
+ debugLog(`Failed to read directory: ${err.message}`);
72
+ return commands;
73
+ }
74
+
75
+ debugLog(`Scanning ${files.length} files in ${commandsDir}`);
78
76
 
79
77
  for (const file of files) {
80
- if (!file.endsWith('.md')) continue;
78
+ if (!file.endsWith('.md')) {
79
+ debugLog(`Skipping non-md file: ${file}`);
80
+ continue;
81
+ }
81
82
 
82
83
  const filePath = path.join(commandsDir, file);
83
84
  const frontmatter = extractFrontmatter(filePath);
84
85
  const name = file.replace('.md', '');
85
86
 
87
+ // Check if frontmatter was extracted successfully
88
+ if (Object.keys(frontmatter).length === 0) {
89
+ skipped.push({ file, reason: 'no frontmatter or parse error' });
90
+ debugLog(`Skipping ${file}: no frontmatter found`);
91
+ continue;
92
+ }
93
+
94
+ debugLog(`Loaded ${file}: description="${frontmatter.description || '(none)'}"`);
95
+
86
96
  commands.push({
87
97
  name,
88
98
  file,
89
99
  path: filePath,
90
100
  description: frontmatter.description || '',
91
101
  argumentHint: frontmatter['argument-hint'] || '',
92
- category: categorizeCommand(name, frontmatter.description || '')
102
+ category: categorizeCommand(name, frontmatter.description || ''),
93
103
  });
94
104
  }
95
105
 
@@ -101,6 +111,12 @@ function scanCommands(commandsDir) {
101
111
  return a.name.localeCompare(b.name);
102
112
  });
103
113
 
114
+ if (skipped.length > 0) {
115
+ debugLog(`Skipped ${skipped.length} files: ${skipped.map(s => s.file).join(', ')}`);
116
+ }
117
+
118
+ debugLog(`Found ${commands.length} commands`);
119
+
104
120
  return commands;
105
121
  }
106
122
 
@@ -127,7 +143,7 @@ function main() {
127
143
  }
128
144
 
129
145
  // Export for use in other scripts
130
- module.exports = { scanCommands, extractFrontmatter, categorizeCommand };
146
+ module.exports = { scanCommands, categorizeCommand };
131
147
 
132
148
  // Run if called directly
133
149
  if (require.main === module) {
@@ -25,7 +25,7 @@ function runGenerator(scriptName) {
25
25
  try {
26
26
  execSync(`node "${scriptPath}"`, {
27
27
  cwd: __dirname,
28
- stdio: 'inherit'
28
+ stdio: 'inherit',
29
29
  });
30
30
  console.log(`✅ ${scriptName} completed successfully`);
31
31
  return true;
@@ -42,11 +42,7 @@ function main() {
42
42
  console.log('🚀 AgileFlow Content Generation System');
43
43
  console.log('Generating content from metadata...\n');
44
44
 
45
- const generators = [
46
- 'inject-help.js',
47
- 'inject-babysit.js',
48
- 'inject-readme.js'
49
- ];
45
+ const generators = ['inject-help.js', 'inject-babysit.js', 'inject-readme.js'];
50
46
 
51
47
  const results = [];
52
48
 
@@ -83,7 +83,9 @@ function injectContentByMarker(content, markerName, generated) {
83
83
  const timestamp = new Date().toISOString().split('T')[0];
84
84
  const injectedContent = `${startMarker}\n<!-- Auto-generated on ${timestamp}. Do not edit manually. -->\n\n${generated}\n${endMarker}`;
85
85
 
86
- return content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length);
86
+ return (
87
+ content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length)
88
+ );
87
89
  }
88
90
 
89
91
  /**
@@ -159,7 +161,12 @@ function main() {
159
161
  }
160
162
 
161
163
  // Export for use in orchestrator
162
- module.exports = { generateAgentList, generateCommandReference, injectContentByMarker, addMarkersIfMissing };
164
+ module.exports = {
165
+ generateAgentList,
166
+ generateCommandReference,
167
+ injectContentByMarker,
168
+ addMarkersIfMissing,
169
+ };
163
170
 
164
171
  // Run if called directly
165
172
  if (require.main === module) {
@@ -62,7 +62,9 @@ function injectContent(content, generated) {
62
62
  const timestamp = new Date().toISOString().split('T')[0];
63
63
  const injectedContent = `${startMarker}\n<!-- Auto-generated on ${timestamp}. Do not edit manually. -->\n\n${generated}\n${endMarker}`;
64
64
 
65
- return content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length);
65
+ return (
66
+ content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length)
67
+ );
66
68
  }
67
69
 
68
70
  /**