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.
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +110 -32
- package/scripts/agileflow-statusline.sh +155 -9
- package/scripts/agileflow-welcome.js +171 -47
- package/scripts/auto-self-improve.js +344 -0
- package/scripts/check-update.js +1 -4
- package/scripts/get-env.js +15 -7
- package/scripts/lib/frontmatter-parser.js +4 -1
- package/scripts/obtain-context.js +59 -48
- package/scripts/ralph-loop.js +503 -0
- package/scripts/validate-expertise.sh +19 -15
- package/src/core/agents/design.md +1 -1
- package/src/core/agents/documentation.md +1 -1
- package/src/core/agents/integrations.md +1 -1
- package/src/core/agents/mobile.md +1 -1
- package/src/core/agents/monitoring.md +1 -1
- package/src/core/agents/performance.md +1 -1
- package/src/core/commands/babysit.md +73 -0
- package/src/core/commands/configure.md +50 -7
- package/src/core/experts/documentation/expertise.yaml +4 -0
- package/src/core/experts/research/expertise.yaml +2 -2
- package/tools/cli/commands/list.js +3 -1
- package/tools/cli/commands/uninstall.js +4 -5
- package/tools/cli/commands/update.js +11 -3
- package/tools/cli/lib/content-injector.js +6 -1
- package/tools/cli/lib/docs-setup.js +1 -1
|
@@ -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 };
|
package/scripts/check-update.js
CHANGED
|
@@ -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
|
}
|
package/scripts/get-env.js
CHANGED
|
@@ -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.
|
|
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 (
|
|
169
|
-
const wipColor = info.agileflow.wipCount > 0 ? c.
|
|
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.
|
|
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.
|
|
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
|
|
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 -
|
|
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(
|
|
160
|
-
const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(
|
|
161
|
-
const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(
|
|
162
|
-
const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(
|
|
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.
|
|
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
|
|
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.
|
|
217
|
-
byStatus['in-progress'] ? C.
|
|
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.
|
|
223
|
-
byStatus['blocked'] ? C.
|
|
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.
|
|
229
|
-
byStatus['ready'] ? C.
|
|
239
|
+
C.skyBlue,
|
|
240
|
+
byStatus['ready'] ? C.skyBlue : C.dim
|
|
230
241
|
);
|
|
231
|
-
const completedColor = `${C.bold}${C.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
338
|
+
content += `Uncommitted: ${C.mintGreen}clean${C.reset}\n`;
|
|
328
339
|
}
|
|
329
340
|
|
|
330
|
-
// 2. STATUS.JSON - Full Content
|
|
331
|
-
content += `\n${C.
|
|
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.
|
|
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.
|
|
366
|
+
content += `Active session: ${C.lightGreen}${duration} min${C.reset}\n`;
|
|
356
367
|
if (current.current_story) {
|
|
357
|
-
content += `Working on: ${C.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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) {
|