agileflow 2.75.0 → 2.77.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.
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * auto-self-improve.js - Automatic Agent Expertise Updates
5
+ *
6
+ * This script runs as a Stop hook and automatically updates agent
7
+ * expertise files based on work performed during the session.
8
+ *
9
+ * How it works:
10
+ * 1. Reads session-state.json to find which agent was active
11
+ * 2. Analyzes git diff to see what changed
12
+ * 3. Detects patterns, new files, significant changes
13
+ * 4. Generates a learning entry
14
+ * 5. Appends to the agent's expertise.yaml
15
+ *
16
+ * Usage (as Stop hook):
17
+ * node scripts/auto-self-improve.js
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { execSync } = require('child_process');
23
+
24
+ // ANSI colors
25
+ const c = {
26
+ reset: '\x1b[0m',
27
+ bold: '\x1b[1m',
28
+ dim: '\x1b[2m',
29
+ red: '\x1b[31m',
30
+ green: '\x1b[32m',
31
+ yellow: '\x1b[33m',
32
+ blue: '\x1b[34m',
33
+ cyan: '\x1b[36m',
34
+ brand: '\x1b[38;2;232;104;58m',
35
+ };
36
+
37
+ // Agents that have expertise files
38
+ const AGENTS_WITH_EXPERTISE = [
39
+ 'accessibility',
40
+ 'adr-writer',
41
+ 'analytics',
42
+ 'api',
43
+ 'ci',
44
+ 'compliance',
45
+ 'database',
46
+ 'datamigration',
47
+ 'design',
48
+ 'devops',
49
+ 'documentation',
50
+ 'epic-planner',
51
+ 'integrations',
52
+ 'mentor',
53
+ 'mobile',
54
+ 'monitoring',
55
+ 'performance',
56
+ 'product',
57
+ 'qa',
58
+ 'readme-updater',
59
+ 'refactor',
60
+ 'research',
61
+ 'security',
62
+ 'testing',
63
+ 'ui',
64
+ ];
65
+
66
+ // File patterns that suggest domain expertise
67
+ const DOMAIN_PATTERNS = {
68
+ database: [/schema/, /migration/, /\.sql$/, /prisma/, /drizzle/, /sequelize/],
69
+ api: [/\/api\//, /controller/, /route/, /endpoint/, /graphql/],
70
+ ui: [/component/, /\.tsx$/, /\.jsx$/, /styles/, /\.css$/, /\.scss$/],
71
+ testing: [/\.test\./, /\.spec\./, /__tests__/, /jest/, /vitest/],
72
+ security: [/auth/, /password/, /token/, /jwt/, /oauth/, /permission/],
73
+ ci: [/\.github\/workflows/, /\.gitlab-ci/, /dockerfile/i, /docker-compose/],
74
+ documentation: [/\.md$/, /readme/i, /docs\//, /jsdoc/],
75
+ performance: [/cache/, /optimize/, /performance/, /benchmark/],
76
+ devops: [/deploy/, /kubernetes/, /k8s/, /terraform/, /ansible/],
77
+ };
78
+
79
+ // Find project root
80
+ function getProjectRoot() {
81
+ let dir = process.cwd();
82
+ while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
83
+ dir = path.dirname(dir);
84
+ }
85
+ return dir !== '/' ? dir : process.cwd();
86
+ }
87
+
88
+ // Read session state
89
+ function getSessionState(rootDir) {
90
+ const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
91
+ try {
92
+ if (fs.existsSync(statePath)) {
93
+ return JSON.parse(fs.readFileSync(statePath, 'utf8'));
94
+ }
95
+ } catch (e) {}
96
+ return {};
97
+ }
98
+
99
+ // Get git diff summary
100
+ function getGitDiff(rootDir) {
101
+ try {
102
+ // Get list of changed files (staged and unstaged)
103
+ const diffFiles = execSync('git diff --name-only HEAD 2>/dev/null || git diff --name-only', {
104
+ cwd: rootDir,
105
+ encoding: 'utf8',
106
+ })
107
+ .trim()
108
+ .split('\n')
109
+ .filter(Boolean);
110
+
111
+ // Get staged files
112
+ const stagedFiles = execSync('git diff --cached --name-only 2>/dev/null', {
113
+ cwd: rootDir,
114
+ encoding: 'utf8',
115
+ })
116
+ .trim()
117
+ .split('\n')
118
+ .filter(Boolean);
119
+
120
+ // Get untracked files
121
+ const untrackedFiles = execSync('git ls-files --others --exclude-standard 2>/dev/null', {
122
+ cwd: rootDir,
123
+ encoding: 'utf8',
124
+ })
125
+ .trim()
126
+ .split('\n')
127
+ .filter(Boolean);
128
+
129
+ // Combine all
130
+ const allFiles = [...new Set([...diffFiles, ...stagedFiles, ...untrackedFiles])];
131
+
132
+ // Get diff stats
133
+ let additions = 0;
134
+ let deletions = 0;
135
+ try {
136
+ const stats = execSync('git diff --shortstat HEAD 2>/dev/null || echo ""', {
137
+ cwd: rootDir,
138
+ encoding: 'utf8',
139
+ });
140
+ const addMatch = stats.match(/(\d+) insertion/);
141
+ const delMatch = stats.match(/(\d+) deletion/);
142
+ if (addMatch) additions = parseInt(addMatch[1]);
143
+ if (delMatch) deletions = parseInt(delMatch[1]);
144
+ } catch (e) {}
145
+
146
+ return {
147
+ files: allFiles,
148
+ additions,
149
+ deletions,
150
+ hasChanges: allFiles.length > 0,
151
+ };
152
+ } catch (e) {
153
+ return { files: [], additions: 0, deletions: 0, hasChanges: false };
154
+ }
155
+ }
156
+
157
+ // Detect which domain the changes relate to
158
+ function detectDomain(files) {
159
+ const domainScores = {};
160
+
161
+ for (const file of files) {
162
+ for (const [domain, patterns] of Object.entries(DOMAIN_PATTERNS)) {
163
+ for (const pattern of patterns) {
164
+ if (pattern.test(file.toLowerCase())) {
165
+ domainScores[domain] = (domainScores[domain] || 0) + 1;
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Return domain with highest score
172
+ const sorted = Object.entries(domainScores).sort((a, b) => b[1] - a[1]);
173
+ return sorted.length > 0 ? sorted[0][0] : null;
174
+ }
175
+
176
+ // Generate learning summary from changes
177
+ function generateLearningSummary(diff, activeAgent) {
178
+ const { files, additions, deletions } = diff;
179
+
180
+ if (files.length === 0) return null;
181
+
182
+ // Categorize files
183
+ const newFiles = files.filter(f => !f.includes('/'));
184
+ const testFiles = files.filter(f => /\.(test|spec)\.[jt]sx?$/.test(f));
185
+ const configFiles = files.filter(f => /\.(json|yaml|yml|toml|config\.)/.test(f));
186
+ const codeFiles = files.filter(f => /\.[jt]sx?$/.test(f) && !testFiles.includes(f));
187
+
188
+ // Build summary
189
+ const parts = [];
190
+
191
+ if (codeFiles.length > 0) {
192
+ const dirs = [...new Set(codeFiles.map(f => path.dirname(f)))];
193
+ parts.push(`Modified ${codeFiles.length} code file(s) in: ${dirs.slice(0, 3).join(', ')}`);
194
+ }
195
+
196
+ if (testFiles.length > 0) {
197
+ parts.push(`Updated ${testFiles.length} test file(s)`);
198
+ }
199
+
200
+ if (configFiles.length > 0) {
201
+ parts.push(`Changed config: ${configFiles.slice(0, 2).join(', ')}`);
202
+ }
203
+
204
+ if (additions > 50 || deletions > 50) {
205
+ parts.push(`Significant changes: +${additions}/-${deletions} lines`);
206
+ }
207
+
208
+ return parts.length > 0 ? parts.join('. ') : null;
209
+ }
210
+
211
+ // Find expertise file for agent
212
+ function getExpertisePath(rootDir, agent) {
213
+ // Try installed location first
214
+ const installedPath = path.join(rootDir, '.agileflow', 'experts', agent, 'expertise.yaml');
215
+ if (fs.existsSync(installedPath)) return installedPath;
216
+
217
+ // Try source location
218
+ const sourcePath = path.join(
219
+ rootDir,
220
+ 'packages',
221
+ 'cli',
222
+ 'src',
223
+ 'core',
224
+ 'experts',
225
+ agent,
226
+ 'expertise.yaml'
227
+ );
228
+ if (fs.existsSync(sourcePath)) return sourcePath;
229
+
230
+ return null;
231
+ }
232
+
233
+ // Append learning to expertise file
234
+ function appendLearning(expertisePath, learning) {
235
+ try {
236
+ let content = fs.readFileSync(expertisePath, 'utf8');
237
+
238
+ // Find the learnings section
239
+ const learningsMatch = content.match(/^learnings:\s*$/m);
240
+
241
+ if (!learningsMatch) {
242
+ // No learnings section, add it at the end
243
+ content += `\n\nlearnings:\n${learning}`;
244
+ } else {
245
+ // Find where to insert (after "learnings:" line)
246
+ const insertPos = learningsMatch.index + learningsMatch[0].length;
247
+ content = content.slice(0, insertPos) + '\n' + learning + content.slice(insertPos);
248
+ }
249
+
250
+ fs.writeFileSync(expertisePath, content);
251
+ return true;
252
+ } catch (e) {
253
+ return false;
254
+ }
255
+ }
256
+
257
+ // Format learning as YAML
258
+ function formatLearning(summary, files, detectedDomain) {
259
+ const date = new Date().toISOString().split('T')[0];
260
+ const topFiles = files
261
+ .slice(0, 5)
262
+ .map(f => ` - ${f}`)
263
+ .join('\n');
264
+
265
+ return ` - date: "${date}"
266
+ auto_generated: true
267
+ context: "Session work - ${detectedDomain || 'general'} domain"
268
+ insight: "${summary.replace(/"/g, '\\"')}"
269
+ files_touched:
270
+ ${topFiles}`;
271
+ }
272
+
273
+ // Main function
274
+ function main() {
275
+ const rootDir = getProjectRoot();
276
+ const state = getSessionState(rootDir);
277
+ const diff = getGitDiff(rootDir);
278
+
279
+ // Check if there were any changes
280
+ if (!diff.hasChanges) {
281
+ return; // Silent exit - no changes to learn from
282
+ }
283
+
284
+ // Detect which agent was active
285
+ let activeAgent = null;
286
+
287
+ // Check session state for active command
288
+ if (state.active_command?.name) {
289
+ const name = state.active_command.name.replace('agileflow-', '');
290
+ if (AGENTS_WITH_EXPERTISE.includes(name)) {
291
+ activeAgent = name;
292
+ }
293
+ }
294
+
295
+ // If no agent from session, detect from file changes
296
+ if (!activeAgent) {
297
+ activeAgent = detectDomain(diff.files);
298
+ }
299
+
300
+ // If still no agent, skip
301
+ if (!activeAgent || !AGENTS_WITH_EXPERTISE.includes(activeAgent)) {
302
+ return; // Silent exit - can't determine which agent to update
303
+ }
304
+
305
+ // Find expertise file
306
+ const expertisePath = getExpertisePath(rootDir, activeAgent);
307
+ if (!expertisePath) {
308
+ return; // Silent exit - no expertise file found
309
+ }
310
+
311
+ // Generate learning summary
312
+ const summary = generateLearningSummary(diff, activeAgent);
313
+ if (!summary) {
314
+ return; // Silent exit - no meaningful summary
315
+ }
316
+
317
+ // Format and append learning
318
+ const learningYaml = formatLearning(summary, diff.files, activeAgent);
319
+ const success = appendLearning(expertisePath, learningYaml);
320
+
321
+ if (success) {
322
+ console.log('');
323
+ console.log(`${c.green}✓ Auto-learned:${c.reset} ${c.dim}${activeAgent}${c.reset}`);
324
+ console.log(`${c.dim} ${summary}${c.reset}`);
325
+ console.log(
326
+ `${c.dim} → Updated ${path.basename(path.dirname(expertisePath))}/expertise.yaml${c.reset}`
327
+ );
328
+ console.log('');
329
+ }
330
+ }
331
+
332
+ // Run if executed directly
333
+ if (require.main === module) {
334
+ try {
335
+ main();
336
+ } catch (e) {
337
+ // Silent fail - don't break the workflow
338
+ if (process.env.DEBUG) {
339
+ console.error('auto-self-improve error:', e.message);
340
+ }
341
+ }
342
+ }
343
+
344
+ module.exports = { main, detectDomain, generateLearningSummary };
@@ -232,10 +232,7 @@ async function checkForUpdates(options = {}) {
232
232
  }
233
233
 
234
234
  // Check if we just updated (lastSeenVersion < installed)
235
- if (
236
- config.lastSeenVersion &&
237
- compareVersions(config.lastSeenVersion, installedVersion) < 0
238
- ) {
235
+ if (config.lastSeenVersion && compareVersions(config.lastSeenVersion, installedVersion) < 0) {
239
236
  result.justUpdated = true;
240
237
  result.previousVersion = config.lastSeenVersion;
241
238
  }
@@ -154,32 +154,40 @@ function formatOutput(info, asJson = false, compact = false) {
154
154
  cyan: '\x1b[36m',
155
155
  red: '\x1b[31m',
156
156
  brand: '\x1b[38;2;232;104;58m', // #e8683a - AgileFlow brand orange
157
+
158
+ // Vibrant 256-color palette (modern, sleek look)
159
+ mintGreen: '\x1b[38;5;158m', // Healthy/success states
160
+ peach: '\x1b[38;5;215m', // Warning states
161
+ coral: '\x1b[38;5;203m', // Critical/error states
162
+ lightGreen: '\x1b[38;5;194m', // Session healthy
163
+ skyBlue: '\x1b[38;5;117m', // Directories/paths
164
+ lavender: '\x1b[38;5;147m', // Model info
157
165
  };
158
166
 
159
- // Beautiful compact colorful format
167
+ // Beautiful compact colorful format (using vibrant 256-color palette)
160
168
  const lines = [];
161
169
 
162
170
  // Header line with project info (brand color name, dim version, colored branch)
163
- const branchColor = info.git.branch === 'main' ? c.green : c.cyan;
171
+ const branchColor = info.git.branch === 'main' ? c.mintGreen : c.skyBlue;
164
172
  lines.push(
165
173
  `${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
174
  );
167
175
 
168
- // Status line (yellow WIP, red blocked)
169
- const wipColor = info.agileflow.wipCount > 0 ? c.yellow : c.dim;
176
+ // Status line (peach WIP, coral blocked)
177
+ const wipColor = info.agileflow.wipCount > 0 ? c.peach : c.dim;
170
178
  let statusLine =
171
179
  info.agileflow.wipCount > 0
172
180
  ? `${wipColor}WIP: ${info.agileflow.wipCount}${c.reset}`
173
181
  : `${c.dim}No active work${c.reset}`;
174
182
  if (info.agileflow.blockedCount > 0) {
175
- statusLine += ` | ${c.red}Blocked: ${info.agileflow.blockedCount}${c.reset}`;
183
+ statusLine += ` | ${c.coral}Blocked: ${info.agileflow.blockedCount}${c.reset}`;
176
184
  }
177
185
  lines.push(statusLine);
178
186
 
179
- // Active story (if any) - just the first one (blue label)
187
+ // Active story (if any) - just the first one (sky blue label)
180
188
  if (info.agileflow.activeStories.length > 0) {
181
189
  const story = info.agileflow.activeStories[0];
182
- lines.push(`${c.blue}Current:${c.reset} ${story.id} - ${story.title}`);
190
+ lines.push(`${c.skyBlue}Current:${c.reset} ${story.id} - ${story.title}`);
183
191
  }
184
192
 
185
193
  // Last commit (just one, dim)
@@ -69,7 +69,10 @@ function normalizeTools(tools) {
69
69
  if (!tools) return [];
70
70
  if (Array.isArray(tools)) return tools;
71
71
  if (typeof tools === 'string') {
72
- return tools.split(',').map(t => t.trim()).filter(Boolean);
72
+ return tools
73
+ .split(',')
74
+ .map(t => t.trim())
75
+ .filter(Boolean);
73
76
  }
74
77
  return [];
75
78
  }
@@ -55,6 +55,16 @@ const C = {
55
55
  brightYellow: '\x1b[93m',
56
56
  brightGreen: '\x1b[92m',
57
57
  brand: '\x1b[38;2;232;104;58m', // AgileFlow brand orange
58
+
59
+ // Vibrant 256-color palette (modern, sleek look)
60
+ mintGreen: '\x1b[38;5;158m', // Healthy/success states
61
+ peach: '\x1b[38;5;215m', // Warning states
62
+ coral: '\x1b[38;5;203m', // Critical/error states
63
+ lightGreen: '\x1b[38;5;194m', // Session healthy
64
+ lightYellow: '\x1b[38;5;228m', // Session warning
65
+ skyBlue: '\x1b[38;5;117m', // Directories/paths
66
+ lavender: '\x1b[38;5;147m', // Model info
67
+ softGold: '\x1b[38;5;222m', // Cost/money
58
68
  };
59
69
 
60
70
  function safeRead(filePath) {
@@ -111,7 +121,7 @@ function generateSummary() {
111
121
 
112
122
  const W = 58; // Total inner width (matches welcome script)
113
123
  const L = 20; // Left column width
114
- const R = W - L - 3; // Right column width (35 chars)
124
+ const R = W - 24; // Right column width (34 chars) - matches welcome
115
125
 
116
126
  // Pad string to length, accounting for ANSI codes
117
127
  function pad(str, len) {
@@ -155,11 +165,12 @@ function generateSummary() {
155
165
  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`;
156
166
  }
157
167
 
168
+ // All borders use same width formula: 22 dashes + separator + 36 dashes = 61 total chars
158
169
  const divider = () =>
159
- `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
160
- const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(W + 2)}${box.tr}${C.reset}\n`;
161
- const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
162
- const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(R + 2)}${box.br}${C.reset}\n`;
170
+ `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(W - L - 2)}${box.rT}${C.reset}\n`;
171
+ const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(W - L - 2)}${box.tr}${C.reset}\n`;
172
+ const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(W - L - 2)}${box.rT}${C.reset}\n`;
173
+ const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(W - L - 2)}${box.br}${C.reset}\n`;
163
174
 
164
175
  // Gather data
165
176
  const branch = safeExec('git branch --show-current') || 'unknown';
@@ -200,35 +211,35 @@ function generateSummary() {
200
211
 
201
212
  // Header row (full width, no column divider)
202
213
  const title = commandName ? `Context [${commandName}]` : 'Context Summary';
203
- const branchColor = branch === 'main' ? C.green : branch.startsWith('fix') ? C.red : C.cyan;
214
+ const branchColor = branch === 'main' ? C.mintGreen : branch.startsWith('fix') ? C.coral : C.skyBlue;
204
215
  const maxBranchLen = 20;
205
216
  const branchDisplay =
206
217
  branch.length > maxBranchLen ? branch.substring(0, maxBranchLen - 2) + '..' : branch;
207
218
  const header = `${C.brand}${C.bold}${title}${C.reset} ${branchColor}${branchDisplay}${C.reset} ${C.dim}(${lastCommitShort})${C.reset}`;
208
- summary += `${C.dim}${box.v}${C.reset} ${pad(header, W)} ${C.dim}${box.v}${C.reset}\n`;
219
+ summary += `${C.dim}${box.v}${C.reset} ${pad(header, W - 1)} ${C.dim}${box.v}${C.reset}\n`;
209
220
 
210
221
  summary += headerDivider;
211
222
 
212
- // Story counts with colorful labels
223
+ // Story counts with vibrant 256-color palette
213
224
  summary += row(
214
225
  'In Progress',
215
226
  byStatus['in-progress'] ? `${byStatus['in-progress']}` : '0',
216
- C.yellow,
217
- byStatus['in-progress'] ? C.brightYellow : C.dim
227
+ C.peach,
228
+ byStatus['in-progress'] ? C.peach : C.dim
218
229
  );
219
230
  summary += row(
220
231
  'Blocked',
221
232
  byStatus['blocked'] ? `${byStatus['blocked']}` : '0',
222
- C.red,
223
- byStatus['blocked'] ? C.red : C.dim
233
+ C.coral,
234
+ byStatus['blocked'] ? C.coral : C.dim
224
235
  );
225
236
  summary += row(
226
237
  'Ready',
227
238
  byStatus['ready'] ? `${byStatus['ready']}` : '0',
228
- C.cyan,
229
- byStatus['ready'] ? C.brightCyan : C.dim
239
+ C.skyBlue,
240
+ byStatus['ready'] ? C.skyBlue : C.dim
230
241
  );
231
- const completedColor = `${C.bold}${C.green}`;
242
+ const completedColor = `${C.bold}${C.mintGreen}`;
232
243
  summary += row(
233
244
  'Completed',
234
245
  byStatus['done'] ? `${byStatus['done']}` : '0',
@@ -238,27 +249,27 @@ function generateSummary() {
238
249
 
239
250
  summary += divider();
240
251
 
241
- // Git status
252
+ // Git status (using vibrant 256-color palette)
242
253
  const uncommittedStatus =
243
254
  statusLines.length > 0 ? `${statusLines.length} uncommitted` : '✓ clean';
244
- summary += row('Git', uncommittedStatus, C.blue, statusLines.length > 0 ? C.yellow : C.green);
255
+ summary += row('Git', uncommittedStatus, C.blue, statusLines.length > 0 ? C.peach : C.mintGreen);
245
256
 
246
257
  // Session
247
258
  const sessionText = sessionDuration !== null ? `${sessionDuration} min active` : 'no session';
248
- summary += row('Session', sessionText, C.blue, sessionDuration !== null ? C.brightGreen : C.dim);
259
+ summary += row('Session', sessionText, C.blue, sessionDuration !== null ? C.lightGreen : C.dim);
249
260
 
250
261
  // Current story
251
262
  const storyText = currentStory ? currentStory : 'none';
252
- summary += row('Working on', storyText, C.blue, currentStory ? C.brightYellow : C.dim);
263
+ summary += row('Working on', storyText, C.blue, currentStory ? C.lightYellow : C.dim);
253
264
 
254
265
  // Ready stories (if any)
255
266
  if (readyStories.length > 0) {
256
- summary += row('⭐ Up Next', readyStories.slice(0, 3).join(', '), C.brightCyan, C.cyan);
267
+ summary += row('⭐ Up Next', readyStories.slice(0, 3).join(', '), C.skyBlue, C.skyBlue);
257
268
  }
258
269
 
259
270
  summary += divider();
260
271
 
261
- // Key files
272
+ // Key files (using vibrant 256-color palette)
262
273
  const keyFileChecks = [
263
274
  { path: 'CLAUDE.md', label: 'CLAUDE' },
264
275
  { path: 'README.md', label: 'README' },
@@ -268,31 +279,31 @@ function generateSummary() {
268
279
  const keyFileStatus = keyFileChecks
269
280
  .map(f => {
270
281
  const exists = fs.existsSync(f.path);
271
- return exists ? `${C.brightGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
282
+ return exists ? `${C.mintGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
272
283
  })
273
284
  .join(' ');
274
- summary += row('Key files', keyFileStatus, C.magenta, '');
285
+ summary += row('Key files', keyFileStatus, C.lavender, '');
275
286
 
276
287
  // Research
277
288
  const researchText = researchFiles.length > 0 ? `${researchFiles.length} notes` : 'none';
278
- summary += row('Research', researchText, C.magenta, researchFiles.length > 0 ? C.cyan : C.dim);
289
+ summary += row('Research', researchText, C.lavender, researchFiles.length > 0 ? C.skyBlue : C.dim);
279
290
 
280
291
  // Epics
281
292
  const epicText = epicFiles.length > 0 ? `${epicFiles.length} epics` : 'none';
282
- summary += row('Epics', epicText, C.magenta, epicFiles.length > 0 ? C.cyan : C.dim);
293
+ summary += row('Epics', epicText, C.lavender, epicFiles.length > 0 ? C.skyBlue : C.dim);
283
294
 
284
295
  summary += divider();
285
296
 
286
- // Last commit
297
+ // Last commit (using vibrant 256-color palette)
287
298
  summary += row(
288
299
  'Last commit',
289
- `${C.yellow}${lastCommitShort}${C.reset} ${lastCommitMsg}`,
300
+ `${C.peach}${lastCommitShort}${C.reset} ${lastCommitMsg}`,
290
301
  C.dim,
291
302
  ''
292
303
  );
293
304
 
294
305
  summary += bottomBorder;
295
-
306
+ summary += '\n';
296
307
  summary += `${C.dim}Full context continues below (Claude sees all)...${C.reset}\n\n`;
297
308
 
298
309
  return summary;
@@ -306,29 +317,29 @@ function generateFullContent() {
306
317
  let content = '';
307
318
 
308
319
  const title = commandName ? `AgileFlow Context [${commandName}]` : 'AgileFlow Context';
309
- content += `${C.magenta}${C.bold}${title}${C.reset}\n`;
320
+ content += `${C.lavender}${C.bold}${title}${C.reset}\n`;
310
321
  content += `${C.dim}Generated: ${new Date().toISOString()}${C.reset}\n`;
311
322
 
312
- // 1. GIT STATUS
313
- content += `\n${C.cyan}${C.bold}═══ Git Status ═══${C.reset}\n`;
323
+ // 1. GIT STATUS (using vibrant 256-color palette)
324
+ content += `\n${C.skyBlue}${C.bold}═══ Git Status ═══${C.reset}\n`;
314
325
  const branch = safeExec('git branch --show-current') || 'unknown';
315
326
  const status = safeExec('git status --short') || '';
316
327
  const statusLines = status.split('\n').filter(Boolean);
317
328
  const lastCommit = safeExec('git log -1 --format="%h %s"') || 'no commits';
318
329
 
319
- content += `Branch: ${C.green}${branch}${C.reset}\n`;
330
+ content += `Branch: ${C.mintGreen}${branch}${C.reset}\n`;
320
331
  content += `Last commit: ${C.dim}${lastCommit}${C.reset}\n`;
321
332
  if (statusLines.length > 0) {
322
- content += `Uncommitted: ${C.yellow}${statusLines.length} file(s)${C.reset}\n`;
333
+ content += `Uncommitted: ${C.peach}${statusLines.length} file(s)${C.reset}\n`;
323
334
  statusLines.slice(0, 10).forEach(line => (content += ` ${C.dim}${line}${C.reset}\n`));
324
335
  if (statusLines.length > 10)
325
336
  content += ` ${C.dim}... and ${statusLines.length - 10} more${C.reset}\n`;
326
337
  } else {
327
- content += `Uncommitted: ${C.green}clean${C.reset}\n`;
338
+ content += `Uncommitted: ${C.mintGreen}clean${C.reset}\n`;
328
339
  }
329
340
 
330
- // 2. STATUS.JSON - Full Content
331
- content += `\n${C.cyan}${C.bold}═══ Status.json (Full Content) ═══${C.reset}\n`;
341
+ // 2. STATUS.JSON - Full Content (using vibrant 256-color palette)
342
+ content += `\n${C.skyBlue}${C.bold}═══ Status.json (Full Content) ═══${C.reset}\n`;
332
343
  const statusJsonPath = 'docs/09-agents/status.json';
333
344
  const statusJson = safeReadJSON(statusJsonPath);
334
345
 
@@ -344,30 +355,30 @@ function generateFullContent() {
344
355
  content += `${C.dim}No status.json found${C.reset}\n`;
345
356
  }
346
357
 
347
- // 3. SESSION STATE
348
- content += `\n${C.cyan}${C.bold}═══ Session State ═══${C.reset}\n`;
358
+ // 3. SESSION STATE (using vibrant 256-color palette)
359
+ content += `\n${C.skyBlue}${C.bold}═══ Session State ═══${C.reset}\n`;
349
360
  const sessionState = safeReadJSON('docs/09-agents/session-state.json');
350
361
  if (sessionState) {
351
362
  const current = sessionState.current_session;
352
363
  if (current && current.started_at) {
353
364
  const started = new Date(current.started_at);
354
365
  const duration = Math.round((Date.now() - started.getTime()) / 60000);
355
- content += `Active session: ${C.green}${duration} min${C.reset}\n`;
366
+ content += `Active session: ${C.lightGreen}${duration} min${C.reset}\n`;
356
367
  if (current.current_story) {
357
- content += `Working on: ${C.yellow}${current.current_story}${C.reset}\n`;
368
+ content += `Working on: ${C.lightYellow}${current.current_story}${C.reset}\n`;
358
369
  }
359
370
  } else {
360
371
  content += `${C.dim}No active session${C.reset}\n`;
361
372
  }
362
373
  if (sessionState.active_command) {
363
- content += `Active command: ${C.cyan}${sessionState.active_command.name}${C.reset}\n`;
374
+ content += `Active command: ${C.skyBlue}${sessionState.active_command.name}${C.reset}\n`;
364
375
  }
365
376
  } else {
366
377
  content += `${C.dim}No session-state.json found${C.reset}\n`;
367
378
  }
368
379
 
369
- // 4. DOCS STRUCTURE
370
- content += `\n${C.cyan}${C.bold}═══ Documentation ═══${C.reset}\n`;
380
+ // 4. DOCS STRUCTURE (using vibrant 256-color palette)
381
+ content += `\n${C.skyBlue}${C.bold}═══ Documentation ═══${C.reset}\n`;
371
382
  const docsDir = 'docs';
372
383
  const docFolders = safeLs(docsDir).filter(f => {
373
384
  try {
@@ -390,8 +401,8 @@ function generateFullContent() {
390
401
  });
391
402
  }
392
403
 
393
- // 5. RESEARCH NOTES - List + Full content of most recent
394
- content += `\n${C.cyan}${C.bold}═══ Research Notes ═══${C.reset}\n`;
404
+ // 5. RESEARCH NOTES - List + Full content of most recent (using vibrant 256-color palette)
405
+ content += `\n${C.skyBlue}${C.bold}═══ Research Notes ═══${C.reset}\n`;
395
406
  const researchDir = 'docs/10-research';
396
407
  const researchFiles = safeLs(researchDir).filter(f => f.endsWith('.md') && f !== 'README.md');
397
408
  if (researchFiles.length > 0) {
@@ -404,7 +415,7 @@ function generateFullContent() {
404
415
  const mostRecentContent = safeRead(mostRecentPath);
405
416
 
406
417
  if (mostRecentContent) {
407
- content += `\n${C.green}📄 Most Recent: ${mostRecentFile}${C.reset}\n`;
418
+ content += `\n${C.mintGreen}📄 Most Recent: ${mostRecentFile}${C.reset}\n`;
408
419
  content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
409
420
  content += mostRecentContent + '\n';
410
421
  content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
@@ -413,8 +424,8 @@ function generateFullContent() {
413
424
  content += `${C.dim}No research notes${C.reset}\n`;
414
425
  }
415
426
 
416
- // 6. BUS MESSAGES
417
- content += `\n${C.cyan}${C.bold}═══ Recent Agent Messages ═══${C.reset}\n`;
427
+ // 6. BUS MESSAGES (using vibrant 256-color palette)
428
+ content += `\n${C.skyBlue}${C.bold}═══ Recent Agent Messages ═══${C.reset}\n`;
418
429
  const busPath = 'docs/09-agents/bus/log.jsonl';
419
430
  const busContent = safeRead(busPath);
420
431
  if (busContent) {