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
@@ -91,7 +91,9 @@ function injectContentByMarker(content, markerName, generated) {
91
91
  const timestamp = new Date().toISOString().split('T')[0];
92
92
  const injectedContent = `${startMarker}\n<!-- Auto-generated on ${timestamp}. Do not edit manually. -->\n\n${generated}\n${endMarker}`;
93
93
 
94
- return content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length);
94
+ return (
95
+ content.substring(0, startIdx) + injectedContent + content.substring(endIdx + endMarker.length)
96
+ );
95
97
  }
96
98
 
97
99
  /**
@@ -117,7 +119,9 @@ function main() {
117
119
  const agents = scanAgents(agentsDir);
118
120
  const skills = scanSkills(skillsDir);
119
121
 
120
- console.log(`Found: ${commands.length} commands, ${agents.length} agents, ${skills.length} skills`);
122
+ console.log(
123
+ `Found: ${commands.length} commands, ${agents.length} agents, ${skills.length} skills`
124
+ );
121
125
 
122
126
  // Read README
123
127
  let readmeContent = fs.readFileSync(readmeFile, 'utf-8');
@@ -127,7 +131,7 @@ function main() {
127
131
  const stats = generateStats({
128
132
  commands: commands.length,
129
133
  agents: agents.length,
130
- skills: skills.length
134
+ skills: skills.length,
131
135
  });
132
136
 
133
137
  console.log('Generating agent table...');
@@ -5,37 +5,25 @@
5
5
  *
6
6
  * Scans skills/ directory and extracts metadata from SKILL.md frontmatter.
7
7
  * Returns structured skill 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 YAML 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 {};
24
- }
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
- }
23
+ function debugLog(message) {
24
+ if (DEBUG) {
25
+ console.error(`[skill-registry] ${message}`);
36
26
  }
37
-
38
- return frontmatter;
39
27
  }
40
28
 
41
29
  /**
@@ -48,10 +36,10 @@ function categorizeSkill(name, description) {
48
36
  const categories = {
49
37
  'Story & Planning': ['story', 'epic', 'sprint', 'acceptance-criteria'],
50
38
  'Code Generation': ['type-definitions', 'validation-schema', 'error-handler'],
51
- 'Testing': ['test-case'],
52
- 'Documentation': ['adr', 'api-documentation', 'changelog', 'pr-description'],
53
- 'Architecture': ['sql-schema', 'diagram'],
54
- 'Deployment': ['deployment-guide', 'migration-checklist']
39
+ Testing: ['test-case'],
40
+ Documentation: ['adr', 'api-documentation', 'changelog', 'pr-description'],
41
+ Architecture: ['sql-schema', 'diagram'],
42
+ Deployment: ['deployment-guide', 'migration-checklist'],
55
43
  };
56
44
 
57
45
  const lowerName = name.toLowerCase();
@@ -73,32 +61,65 @@ function categorizeSkill(name, description) {
73
61
  */
74
62
  function scanSkills(skillsDir) {
75
63
  const skills = [];
64
+ const skipped = [];
65
+
66
+ let skillDirs;
67
+ try {
68
+ skillDirs = fs.readdirSync(skillsDir);
69
+ } catch (err) {
70
+ debugLog(`Failed to read directory: ${err.message}`);
71
+ return skills;
72
+ }
76
73
 
77
- // Each skill is in its own directory with a SKILL.md file
78
- const skillDirs = fs.readdirSync(skillsDir);
74
+ debugLog(`Scanning ${skillDirs.length} entries in ${skillsDir}`);
79
75
 
80
76
  for (const skillDir of skillDirs) {
81
77
  const skillPath = path.join(skillsDir, skillDir);
82
78
 
83
79
  // Skip if not a directory
84
- if (!fs.statSync(skillPath).isDirectory()) continue;
80
+ let stat;
81
+ try {
82
+ stat = fs.statSync(skillPath);
83
+ } catch (err) {
84
+ debugLog(`Failed to stat ${skillDir}: ${err.message}`);
85
+ continue;
86
+ }
87
+
88
+ if (!stat.isDirectory()) {
89
+ debugLog(`Skipping non-directory: ${skillDir}`);
90
+ continue;
91
+ }
85
92
 
86
93
  const skillFile = path.join(skillPath, 'SKILL.md');
87
94
 
88
95
  // Skip if SKILL.md doesn't exist
89
- if (!fs.existsSync(skillFile)) continue;
96
+ if (!fs.existsSync(skillFile)) {
97
+ skipped.push({ dir: skillDir, reason: 'no SKILL.md file' });
98
+ debugLog(`Skipping ${skillDir}: no SKILL.md found`);
99
+ continue;
100
+ }
90
101
 
91
102
  const frontmatter = extractFrontmatter(skillFile);
103
+
104
+ // Check if frontmatter was extracted successfully
105
+ if (Object.keys(frontmatter).length === 0) {
106
+ skipped.push({ dir: skillDir, reason: 'no frontmatter or parse error' });
107
+ debugLog(`Skipping ${skillDir}: no frontmatter in SKILL.md`);
108
+ continue;
109
+ }
110
+
92
111
  const name = frontmatter.name || skillDir;
93
112
  const description = frontmatter.description || '';
94
113
 
114
+ debugLog(`Loaded ${skillDir}: name="${name}"`);
115
+
95
116
  skills.push({
96
117
  name,
97
118
  directory: skillDir,
98
119
  file: 'SKILL.md',
99
120
  path: skillFile,
100
121
  description,
101
- category: categorizeSkill(name, description)
122
+ category: categorizeSkill(name, description),
102
123
  });
103
124
  }
104
125
 
@@ -110,6 +131,12 @@ function scanSkills(skillsDir) {
110
131
  return a.name.localeCompare(b.name);
111
132
  });
112
133
 
134
+ if (skipped.length > 0) {
135
+ debugLog(`Skipped ${skipped.length} directories: ${skipped.map(s => s.dir).join(', ')}`);
136
+ }
137
+
138
+ debugLog(`Found ${skills.length} skills`);
139
+
113
140
  return skills;
114
141
  }
115
142
 
@@ -136,7 +163,7 @@ function main() {
136
163
  }
137
164
 
138
165
  // Export for use in other scripts
139
- module.exports = { scanSkills, extractFrontmatter, categorizeSkill };
166
+ module.exports = { scanSkills, categorizeSkill };
140
167
 
141
168
  // Run if called directly
142
169
  if (require.main === module) {
@@ -35,9 +35,7 @@ function getProjectInfo() {
35
35
  }
36
36
 
37
37
  try {
38
- rootPackage = JSON.parse(
39
- fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')
40
- );
38
+ rootPackage = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
41
39
  } catch (err) {
42
40
  // Ignore if not found
43
41
  }
@@ -50,17 +48,17 @@ function getProjectInfo() {
50
48
  try {
51
49
  gitBranch = execSync('git branch --show-current', {
52
50
  cwd: rootDir,
53
- encoding: 'utf8'
51
+ encoding: 'utf8',
54
52
  }).trim();
55
53
  gitCommit = execSync('git rev-parse --short HEAD', {
56
54
  cwd: rootDir,
57
- encoding: 'utf8'
55
+ encoding: 'utf8',
58
56
  }).trim();
59
57
 
60
58
  // Get recent commits (last 5)
61
59
  const commitLog = execSync('git log --oneline -5 2>/dev/null', {
62
60
  cwd: rootDir,
63
- encoding: 'utf8'
61
+ encoding: 'utf8',
64
62
  }).trim();
65
63
  recentCommits = commitLog.split('\n').filter(Boolean);
66
64
  } catch (err) {
@@ -68,10 +66,10 @@ function getProjectInfo() {
68
66
  }
69
67
 
70
68
  // Get AgileFlow status info
71
- let activeStories = [];
69
+ const activeStories = [];
72
70
  let wipCount = 0;
73
71
  let blockedCount = 0;
74
- let activeEpics = [];
72
+ const activeEpics = [];
75
73
 
76
74
  try {
77
75
  const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
@@ -163,13 +161,16 @@ function formatOutput(info, asJson = false, compact = false) {
163
161
 
164
162
  // Header line with project info (brand color name, dim version, colored branch)
165
163
  const branchColor = info.git.branch === 'main' ? c.green : c.cyan;
166
- lines.push(`${c.brand}${c.bold}${info.project.name}${c.reset} ${c.dim}v${info.project.version}${c.reset} | ${branchColor}${info.git.branch}${c.reset} ${c.dim}(${info.git.commit})${c.reset}`);
164
+ lines.push(
165
+ `${c.brand}${c.bold}${info.project.name}${c.reset} ${c.dim}v${info.project.version}${c.reset} | ${branchColor}${info.git.branch}${c.reset} ${c.dim}(${info.git.commit})${c.reset}`
166
+ );
167
167
 
168
168
  // Status line (yellow WIP, red blocked)
169
169
  const wipColor = info.agileflow.wipCount > 0 ? c.yellow : c.dim;
170
- let statusLine = info.agileflow.wipCount > 0
171
- ? `${wipColor}WIP: ${info.agileflow.wipCount}${c.reset}`
172
- : `${c.dim}No active work${c.reset}`;
170
+ let statusLine =
171
+ info.agileflow.wipCount > 0
172
+ ? `${wipColor}WIP: ${info.agileflow.wipCount}${c.reset}`
173
+ : `${c.dim}No active work${c.reset}`;
173
174
  if (info.agileflow.blockedCount > 0) {
174
175
  statusLine += ` | ${c.red}Blocked: ${info.agileflow.blockedCount}${c.reset}`;
175
176
  }
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Frontmatter Parser - Shared YAML frontmatter extraction
5
+ *
6
+ * Consolidates frontmatter parsing logic used across:
7
+ * - command-registry.js
8
+ * - agent-registry.js
9
+ * - skill-registry.js
10
+ * - content-injector.js
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const yaml = require('js-yaml');
15
+
16
+ /**
17
+ * Parse YAML frontmatter from markdown content
18
+ * @param {string} content - Markdown content with frontmatter
19
+ * @returns {object} Parsed frontmatter object, or empty object if none found
20
+ */
21
+ function parseFrontmatter(content) {
22
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
23
+
24
+ if (!match) {
25
+ return {};
26
+ }
27
+
28
+ try {
29
+ const parsed = yaml.load(match[1]);
30
+ // Return empty object if yaml.load returns null/undefined
31
+ return parsed && typeof parsed === 'object' ? parsed : {};
32
+ } catch (err) {
33
+ // Return empty object on parse error (invalid YAML)
34
+ return {};
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Extract frontmatter from a markdown file
40
+ * @param {string} filePath - Path to markdown file
41
+ * @returns {object} Parsed frontmatter object, or empty object if none/error
42
+ */
43
+ function extractFrontmatter(filePath) {
44
+ try {
45
+ const content = fs.readFileSync(filePath, 'utf-8');
46
+ return parseFrontmatter(content);
47
+ } catch (err) {
48
+ // Return empty object on file read error
49
+ return {};
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Extract markdown body (content after frontmatter)
55
+ * @param {string} content - Full markdown content
56
+ * @returns {string} Content after frontmatter, or original if no frontmatter
57
+ */
58
+ function extractBody(content) {
59
+ const match = content.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)/);
60
+ return match ? match[1].trim() : content.trim();
61
+ }
62
+
63
+ /**
64
+ * Normalize tools field - handles array or comma-separated string
65
+ * @param {string|Array} tools - Tools field from frontmatter
66
+ * @returns {Array} Array of tool names
67
+ */
68
+ function normalizeTools(tools) {
69
+ if (!tools) return [];
70
+ if (Array.isArray(tools)) return tools;
71
+ if (typeof tools === 'string') {
72
+ return tools.split(',').map(t => t.trim()).filter(Boolean);
73
+ }
74
+ return [];
75
+ }
76
+
77
+ module.exports = {
78
+ parseFrontmatter,
79
+ extractFrontmatter,
80
+ extractBody,
81
+ normalizeTools,
82
+ };
@@ -28,7 +28,11 @@ if (commandName) {
28
28
  if (fs.existsSync(sessionStatePath)) {
29
29
  try {
30
30
  const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
31
- state.active_command = { name: commandName, activated_at: new Date().toISOString(), state: {} };
31
+ state.active_command = {
32
+ name: commandName,
33
+ activated_at: new Date().toISOString(),
34
+ state: {},
35
+ };
32
36
  fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
33
37
  } catch (e) {
34
38
  // Silently continue if session state can't be updated
@@ -92,9 +96,16 @@ function safeExec(cmd) {
92
96
  function generateSummary() {
93
97
  // Box drawing characters
94
98
  const box = {
95
- tl: '╭', tr: '╮', bl: '╰', br: '╯',
96
- h: '', v: '│',
97
- lT: '', rT: '┤', tT: '┬', bT: '┴',
99
+ tl: '╭',
100
+ tr: '',
101
+ bl: '',
102
+ br: '╯',
103
+ h: '─',
104
+ v: '│',
105
+ lT: '├',
106
+ rT: '┤',
107
+ tT: '┬',
108
+ bT: '┴',
98
109
  cross: '┼',
99
110
  };
100
111
 
@@ -144,7 +155,8 @@ function generateSummary() {
144
155
  return `${C.dim}${box.v}${C.reset} ${pad(leftStr, L)} ${C.dim}${box.v}${C.reset} ${pad(rightStr, R)} ${C.dim}${box.v}${C.reset}\n`;
145
156
  }
146
157
 
147
- const divider = () => `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
158
+ const divider = () =>
159
+ `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
148
160
  const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(W + 2)}${box.tr}${C.reset}\n`;
149
161
  const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
150
162
  const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(R + 2)}${box.br}${C.reset}\n`;
@@ -156,12 +168,15 @@ function generateSummary() {
156
168
  const statusLines = (safeExec('git status --short') || '').split('\n').filter(Boolean);
157
169
  const statusJson = safeReadJSON('docs/09-agents/status.json');
158
170
  const sessionState = safeReadJSON('docs/09-agents/session-state.json');
159
- const researchFiles = safeLs('docs/10-research').filter(f => f.endsWith('.md') && f !== 'README.md').sort().reverse();
171
+ const researchFiles = safeLs('docs/10-research')
172
+ .filter(f => f.endsWith('.md') && f !== 'README.md')
173
+ .sort()
174
+ .reverse();
160
175
  const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
161
176
 
162
177
  // Count stories by status
163
- let byStatus = {};
164
- let readyStories = [];
178
+ const byStatus = {};
179
+ const readyStories = [];
165
180
  if (statusJson && statusJson.stories) {
166
181
  Object.entries(statusJson.stories).forEach(([id, story]) => {
167
182
  const s = story.status || 'unknown';
@@ -187,23 +202,45 @@ function generateSummary() {
187
202
  const title = commandName ? `Context [${commandName}]` : 'Context Summary';
188
203
  const branchColor = branch === 'main' ? C.green : branch.startsWith('fix') ? C.red : C.cyan;
189
204
  const maxBranchLen = 20;
190
- const branchDisplay = branch.length > maxBranchLen ? branch.substring(0, maxBranchLen - 2) + '..' : branch;
205
+ const branchDisplay =
206
+ branch.length > maxBranchLen ? branch.substring(0, maxBranchLen - 2) + '..' : branch;
191
207
  const header = `${C.brand}${C.bold}${title}${C.reset} ${branchColor}${branchDisplay}${C.reset} ${C.dim}(${lastCommitShort})${C.reset}`;
192
208
  summary += `${C.dim}${box.v}${C.reset} ${pad(header, W)} ${C.dim}${box.v}${C.reset}\n`;
193
209
 
194
210
  summary += headerDivider;
195
211
 
196
212
  // Story counts with colorful labels
197
- summary += row('In Progress', byStatus['in-progress'] ? `${byStatus['in-progress']}` : '0', C.yellow, byStatus['in-progress'] ? C.brightYellow : C.dim);
198
- summary += row('Blocked', byStatus['blocked'] ? `${byStatus['blocked']}` : '0', C.red, byStatus['blocked'] ? C.red : C.dim);
199
- summary += row('Ready', byStatus['ready'] ? `${byStatus['ready']}` : '0', C.cyan, byStatus['ready'] ? C.brightCyan : C.dim);
213
+ summary += row(
214
+ 'In Progress',
215
+ byStatus['in-progress'] ? `${byStatus['in-progress']}` : '0',
216
+ C.yellow,
217
+ byStatus['in-progress'] ? C.brightYellow : C.dim
218
+ );
219
+ summary += row(
220
+ 'Blocked',
221
+ byStatus['blocked'] ? `${byStatus['blocked']}` : '0',
222
+ C.red,
223
+ byStatus['blocked'] ? C.red : C.dim
224
+ );
225
+ summary += row(
226
+ 'Ready',
227
+ byStatus['ready'] ? `${byStatus['ready']}` : '0',
228
+ C.cyan,
229
+ byStatus['ready'] ? C.brightCyan : C.dim
230
+ );
200
231
  const completedColor = `${C.bold}${C.green}`;
201
- summary += row('Completed', byStatus['done'] ? `${byStatus['done']}` : '0', completedColor, byStatus['done'] ? completedColor : C.dim);
232
+ summary += row(
233
+ 'Completed',
234
+ byStatus['done'] ? `${byStatus['done']}` : '0',
235
+ completedColor,
236
+ byStatus['done'] ? completedColor : C.dim
237
+ );
202
238
 
203
239
  summary += divider();
204
240
 
205
241
  // Git status
206
- const uncommittedStatus = statusLines.length > 0 ? `${statusLines.length} uncommitted` : '✓ clean';
242
+ const uncommittedStatus =
243
+ statusLines.length > 0 ? `${statusLines.length} uncommitted` : '✓ clean';
207
244
  summary += row('Git', uncommittedStatus, C.blue, statusLines.length > 0 ? C.yellow : C.green);
208
245
 
209
246
  // Session
@@ -228,10 +265,12 @@ function generateSummary() {
228
265
  { path: 'docs/04-architecture/README.md', label: 'arch' },
229
266
  { path: 'docs/02-practices/README.md', label: 'practices' },
230
267
  ];
231
- const keyFileStatus = keyFileChecks.map(f => {
232
- const exists = fs.existsSync(f.path);
233
- return exists ? `${C.brightGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
234
- }).join(' ');
268
+ const keyFileStatus = keyFileChecks
269
+ .map(f => {
270
+ const exists = fs.existsSync(f.path);
271
+ return exists ? `${C.brightGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
272
+ })
273
+ .join(' ');
235
274
  summary += row('Key files', keyFileStatus, C.magenta, '');
236
275
 
237
276
  // Research
@@ -245,7 +284,12 @@ function generateSummary() {
245
284
  summary += divider();
246
285
 
247
286
  // Last commit
248
- summary += row('Last commit', `${C.yellow}${lastCommitShort}${C.reset} ${lastCommitMsg}`, C.dim, '');
287
+ summary += row(
288
+ 'Last commit',
289
+ `${C.yellow}${lastCommitShort}${C.reset} ${lastCommitMsg}`,
290
+ C.dim,
291
+ ''
292
+ );
249
293
 
250
294
  summary += bottomBorder;
251
295
 
@@ -276,8 +320,9 @@ function generateFullContent() {
276
320
  content += `Last commit: ${C.dim}${lastCommit}${C.reset}\n`;
277
321
  if (statusLines.length > 0) {
278
322
  content += `Uncommitted: ${C.yellow}${statusLines.length} file(s)${C.reset}\n`;
279
- statusLines.slice(0, 10).forEach(line => content += ` ${C.dim}${line}${C.reset}\n`);
280
- if (statusLines.length > 10) content += ` ${C.dim}... and ${statusLines.length - 10} more${C.reset}\n`;
323
+ statusLines.slice(0, 10).forEach(line => (content += ` ${C.dim}${line}${C.reset}\n`));
324
+ if (statusLines.length > 10)
325
+ content += ` ${C.dim}... and ${statusLines.length - 10} more${C.reset}\n`;
281
326
  } else {
282
327
  content += `Uncommitted: ${C.green}clean${C.reset}\n`;
283
328
  }
@@ -289,7 +334,11 @@ function generateFullContent() {
289
334
 
290
335
  if (statusJson) {
291
336
  content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
292
- content += JSON.stringify(statusJson, null, 2).split('\n').map(l => ` ${l}`).join('\n') + '\n';
337
+ content +=
338
+ JSON.stringify(statusJson, null, 2)
339
+ .split('\n')
340
+ .map(l => ` ${l}`)
341
+ .join('\n') + '\n';
293
342
  content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
294
343
  } else {
295
344
  content += `${C.dim}No status.json found${C.reset}\n`;
@@ -321,7 +370,11 @@ function generateFullContent() {
321
370
  content += `\n${C.cyan}${C.bold}═══ Documentation ═══${C.reset}\n`;
322
371
  const docsDir = 'docs';
323
372
  const docFolders = safeLs(docsDir).filter(f => {
324
- try { return fs.statSync(path.join(docsDir, f)).isDirectory(); } catch { return false; }
373
+ try {
374
+ return fs.statSync(path.join(docsDir, f)).isDirectory();
375
+ } catch {
376
+ return false;
377
+ }
325
378
  });
326
379
 
327
380
  if (docFolders.length > 0) {
@@ -330,7 +383,7 @@ function generateFullContent() {
330
383
  const files = safeLs(folderPath);
331
384
  const mdFiles = files.filter(f => f.endsWith('.md'));
332
385
  const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
333
- let info = [];
386
+ const info = [];
334
387
  if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
335
388
  if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
336
389
  content += ` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}\n`;
@@ -344,7 +397,7 @@ function generateFullContent() {
344
397
  if (researchFiles.length > 0) {
345
398
  researchFiles.sort().reverse();
346
399
  content += `${C.dim}───${C.reset} Available Research Notes\n`;
347
- researchFiles.forEach(file => content += ` ${C.dim}${file}${C.reset}\n`);
400
+ researchFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
348
401
 
349
402
  const mostRecentFile = researchFiles[0];
350
403
  const mostRecentPath = path.join(researchDir, mostRecentFile);
@@ -414,7 +467,7 @@ function generateFullContent() {
414
467
  content += `\n${C.cyan}${C.bold}═══ Epic Files ═══${C.reset}\n`;
415
468
  const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
416
469
  if (epicFiles.length > 0) {
417
- epicFiles.forEach(file => content += ` ${C.dim}${file}${C.reset}\n`);
470
+ epicFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
418
471
  } else {
419
472
  content += `${C.dim}No epic files${C.reset}\n`;
420
473
  }