agileflow 2.74.0 → 2.76.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/README.md +3 -3
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +105 -27
- package/scripts/agileflow-welcome.js +95 -2
- package/scripts/auto-self-improve.js +301 -0
- package/scripts/ralph-loop.js +491 -0
- package/src/core/agents/accessibility.md +1 -1
- package/src/core/agents/adr-writer.md +6 -6
- package/src/core/agents/analytics.md +1 -1
- package/src/core/agents/api.md +130 -41
- package/src/core/agents/ci.md +3 -3
- package/src/core/agents/compliance.md +1 -1
- package/src/core/agents/database.md +121 -36
- package/src/core/agents/datamigration.md +1 -1
- package/src/core/agents/design.md +2 -2
- package/src/core/agents/devops.md +2 -2
- package/src/core/agents/documentation.md +2 -2
- package/src/core/agents/epic-planner.md +4 -4
- package/src/core/agents/integrations.md +4 -4
- package/src/core/agents/mentor.md +6 -6
- package/src/core/agents/mobile.md +2 -2
- package/src/core/agents/monitoring.md +2 -2
- package/src/core/agents/performance.md +2 -2
- package/src/core/agents/product.md +3 -3
- package/src/core/agents/qa.md +1 -1
- package/src/core/agents/refactor.md +1 -1
- package/src/core/agents/security.md +4 -4
- package/src/core/agents/testing.md +2 -2
- package/src/core/agents/ui.md +129 -44
- package/src/core/commands/babysit.md +210 -0
- package/src/core/commands/blockers.md +3 -3
- package/src/core/commands/configure.md +50 -7
- package/src/core/commands/context/export.md +99 -0
- package/src/core/commands/context/full.md +172 -0
- package/src/core/commands/context/note.md +128 -0
- package/src/core/commands/research/ask.md +453 -0
- package/src/core/commands/research/import.md +287 -0
- package/src/core/commands/research/list.md +93 -0
- package/src/core/commands/research/view.md +113 -0
- package/src/core/experts/documentation/expertise.yaml +4 -0
- package/src/core/experts/research/expertise.yaml +4 -4
- package/tools/cli/lib/docs-setup.js +1 -1
- package/src/core/commands/context.md +0 -417
- package/src/core/commands/research.md +0 -124
|
@@ -0,0 +1,301 @@
|
|
|
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', 'adr-writer', 'analytics', 'api', 'ci', 'compliance',
|
|
40
|
+
'database', 'datamigration', 'design', 'devops', 'documentation',
|
|
41
|
+
'epic-planner', 'integrations', 'mentor', 'mobile', 'monitoring',
|
|
42
|
+
'performance', 'product', 'qa', 'readme-updater', 'refactor',
|
|
43
|
+
'research', 'security', 'testing', 'ui'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// File patterns that suggest domain expertise
|
|
47
|
+
const DOMAIN_PATTERNS = {
|
|
48
|
+
'database': [/schema/, /migration/, /\.sql$/, /prisma/, /drizzle/, /sequelize/],
|
|
49
|
+
'api': [/\/api\//, /controller/, /route/, /endpoint/, /graphql/],
|
|
50
|
+
'ui': [/component/, /\.tsx$/, /\.jsx$/, /styles/, /\.css$/, /\.scss$/],
|
|
51
|
+
'testing': [/\.test\./, /\.spec\./, /__tests__/, /jest/, /vitest/],
|
|
52
|
+
'security': [/auth/, /password/, /token/, /jwt/, /oauth/, /permission/],
|
|
53
|
+
'ci': [/\.github\/workflows/, /\.gitlab-ci/, /dockerfile/i, /docker-compose/],
|
|
54
|
+
'documentation': [/\.md$/, /readme/i, /docs\//, /jsdoc/],
|
|
55
|
+
'performance': [/cache/, /optimize/, /performance/, /benchmark/],
|
|
56
|
+
'devops': [/deploy/, /kubernetes/, /k8s/, /terraform/, /ansible/],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Find project root
|
|
60
|
+
function getProjectRoot() {
|
|
61
|
+
let dir = process.cwd();
|
|
62
|
+
while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
|
|
63
|
+
dir = path.dirname(dir);
|
|
64
|
+
}
|
|
65
|
+
return dir !== '/' ? dir : process.cwd();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Read session state
|
|
69
|
+
function getSessionState(rootDir) {
|
|
70
|
+
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
71
|
+
try {
|
|
72
|
+
if (fs.existsSync(statePath)) {
|
|
73
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {}
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Get git diff summary
|
|
80
|
+
function getGitDiff(rootDir) {
|
|
81
|
+
try {
|
|
82
|
+
// Get list of changed files (staged and unstaged)
|
|
83
|
+
const diffFiles = execSync('git diff --name-only HEAD 2>/dev/null || git diff --name-only', {
|
|
84
|
+
cwd: rootDir,
|
|
85
|
+
encoding: 'utf8',
|
|
86
|
+
}).trim().split('\n').filter(Boolean);
|
|
87
|
+
|
|
88
|
+
// Get staged files
|
|
89
|
+
const stagedFiles = execSync('git diff --cached --name-only 2>/dev/null', {
|
|
90
|
+
cwd: rootDir,
|
|
91
|
+
encoding: 'utf8',
|
|
92
|
+
}).trim().split('\n').filter(Boolean);
|
|
93
|
+
|
|
94
|
+
// Get untracked files
|
|
95
|
+
const untrackedFiles = execSync('git ls-files --others --exclude-standard 2>/dev/null', {
|
|
96
|
+
cwd: rootDir,
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
}).trim().split('\n').filter(Boolean);
|
|
99
|
+
|
|
100
|
+
// Combine all
|
|
101
|
+
const allFiles = [...new Set([...diffFiles, ...stagedFiles, ...untrackedFiles])];
|
|
102
|
+
|
|
103
|
+
// Get diff stats
|
|
104
|
+
let additions = 0;
|
|
105
|
+
let deletions = 0;
|
|
106
|
+
try {
|
|
107
|
+
const stats = execSync('git diff --shortstat HEAD 2>/dev/null || echo ""', {
|
|
108
|
+
cwd: rootDir,
|
|
109
|
+
encoding: 'utf8',
|
|
110
|
+
});
|
|
111
|
+
const addMatch = stats.match(/(\d+) insertion/);
|
|
112
|
+
const delMatch = stats.match(/(\d+) deletion/);
|
|
113
|
+
if (addMatch) additions = parseInt(addMatch[1]);
|
|
114
|
+
if (delMatch) deletions = parseInt(delMatch[1]);
|
|
115
|
+
} catch (e) {}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
files: allFiles,
|
|
119
|
+
additions,
|
|
120
|
+
deletions,
|
|
121
|
+
hasChanges: allFiles.length > 0,
|
|
122
|
+
};
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return { files: [], additions: 0, deletions: 0, hasChanges: false };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Detect which domain the changes relate to
|
|
129
|
+
function detectDomain(files) {
|
|
130
|
+
const domainScores = {};
|
|
131
|
+
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
for (const [domain, patterns] of Object.entries(DOMAIN_PATTERNS)) {
|
|
134
|
+
for (const pattern of patterns) {
|
|
135
|
+
if (pattern.test(file.toLowerCase())) {
|
|
136
|
+
domainScores[domain] = (domainScores[domain] || 0) + 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Return domain with highest score
|
|
143
|
+
const sorted = Object.entries(domainScores).sort((a, b) => b[1] - a[1]);
|
|
144
|
+
return sorted.length > 0 ? sorted[0][0] : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Generate learning summary from changes
|
|
148
|
+
function generateLearningSummary(diff, activeAgent) {
|
|
149
|
+
const { files, additions, deletions } = diff;
|
|
150
|
+
|
|
151
|
+
if (files.length === 0) return null;
|
|
152
|
+
|
|
153
|
+
// Categorize files
|
|
154
|
+
const newFiles = files.filter(f => !f.includes('/'));
|
|
155
|
+
const testFiles = files.filter(f => /\.(test|spec)\.[jt]sx?$/.test(f));
|
|
156
|
+
const configFiles = files.filter(f => /\.(json|yaml|yml|toml|config\.)/.test(f));
|
|
157
|
+
const codeFiles = files.filter(f => /\.[jt]sx?$/.test(f) && !testFiles.includes(f));
|
|
158
|
+
|
|
159
|
+
// Build summary
|
|
160
|
+
const parts = [];
|
|
161
|
+
|
|
162
|
+
if (codeFiles.length > 0) {
|
|
163
|
+
const dirs = [...new Set(codeFiles.map(f => path.dirname(f)))];
|
|
164
|
+
parts.push(`Modified ${codeFiles.length} code file(s) in: ${dirs.slice(0, 3).join(', ')}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (testFiles.length > 0) {
|
|
168
|
+
parts.push(`Updated ${testFiles.length} test file(s)`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (configFiles.length > 0) {
|
|
172
|
+
parts.push(`Changed config: ${configFiles.slice(0, 2).join(', ')}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (additions > 50 || deletions > 50) {
|
|
176
|
+
parts.push(`Significant changes: +${additions}/-${deletions} lines`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return parts.length > 0 ? parts.join('. ') : null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Find expertise file for agent
|
|
183
|
+
function getExpertisePath(rootDir, agent) {
|
|
184
|
+
// Try installed location first
|
|
185
|
+
const installedPath = path.join(rootDir, '.agileflow', 'experts', agent, 'expertise.yaml');
|
|
186
|
+
if (fs.existsSync(installedPath)) return installedPath;
|
|
187
|
+
|
|
188
|
+
// Try source location
|
|
189
|
+
const sourcePath = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'experts', agent, 'expertise.yaml');
|
|
190
|
+
if (fs.existsSync(sourcePath)) return sourcePath;
|
|
191
|
+
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Append learning to expertise file
|
|
196
|
+
function appendLearning(expertisePath, learning) {
|
|
197
|
+
try {
|
|
198
|
+
let content = fs.readFileSync(expertisePath, 'utf8');
|
|
199
|
+
|
|
200
|
+
// Find the learnings section
|
|
201
|
+
const learningsMatch = content.match(/^learnings:\s*$/m);
|
|
202
|
+
|
|
203
|
+
if (!learningsMatch) {
|
|
204
|
+
// No learnings section, add it at the end
|
|
205
|
+
content += `\n\nlearnings:\n${learning}`;
|
|
206
|
+
} else {
|
|
207
|
+
// Find where to insert (after "learnings:" line)
|
|
208
|
+
const insertPos = learningsMatch.index + learningsMatch[0].length;
|
|
209
|
+
content = content.slice(0, insertPos) + '\n' + learning + content.slice(insertPos);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fs.writeFileSync(expertisePath, content);
|
|
213
|
+
return true;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Format learning as YAML
|
|
220
|
+
function formatLearning(summary, files, detectedDomain) {
|
|
221
|
+
const date = new Date().toISOString().split('T')[0];
|
|
222
|
+
const topFiles = files.slice(0, 5).map(f => ` - ${f}`).join('\n');
|
|
223
|
+
|
|
224
|
+
return ` - date: "${date}"
|
|
225
|
+
auto_generated: true
|
|
226
|
+
context: "Session work - ${detectedDomain || 'general'} domain"
|
|
227
|
+
insight: "${summary.replace(/"/g, '\\"')}"
|
|
228
|
+
files_touched:
|
|
229
|
+
${topFiles}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Main function
|
|
233
|
+
function main() {
|
|
234
|
+
const rootDir = getProjectRoot();
|
|
235
|
+
const state = getSessionState(rootDir);
|
|
236
|
+
const diff = getGitDiff(rootDir);
|
|
237
|
+
|
|
238
|
+
// Check if there were any changes
|
|
239
|
+
if (!diff.hasChanges) {
|
|
240
|
+
return; // Silent exit - no changes to learn from
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Detect which agent was active
|
|
244
|
+
let activeAgent = null;
|
|
245
|
+
|
|
246
|
+
// Check session state for active command
|
|
247
|
+
if (state.active_command?.name) {
|
|
248
|
+
const name = state.active_command.name.replace('agileflow-', '');
|
|
249
|
+
if (AGENTS_WITH_EXPERTISE.includes(name)) {
|
|
250
|
+
activeAgent = name;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// If no agent from session, detect from file changes
|
|
255
|
+
if (!activeAgent) {
|
|
256
|
+
activeAgent = detectDomain(diff.files);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// If still no agent, skip
|
|
260
|
+
if (!activeAgent || !AGENTS_WITH_EXPERTISE.includes(activeAgent)) {
|
|
261
|
+
return; // Silent exit - can't determine which agent to update
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Find expertise file
|
|
265
|
+
const expertisePath = getExpertisePath(rootDir, activeAgent);
|
|
266
|
+
if (!expertisePath) {
|
|
267
|
+
return; // Silent exit - no expertise file found
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Generate learning summary
|
|
271
|
+
const summary = generateLearningSummary(diff, activeAgent);
|
|
272
|
+
if (!summary) {
|
|
273
|
+
return; // Silent exit - no meaningful summary
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Format and append learning
|
|
277
|
+
const learningYaml = formatLearning(summary, diff.files, activeAgent);
|
|
278
|
+
const success = appendLearning(expertisePath, learningYaml);
|
|
279
|
+
|
|
280
|
+
if (success) {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(`${c.green}✓ Auto-learned:${c.reset} ${c.dim}${activeAgent}${c.reset}`);
|
|
283
|
+
console.log(`${c.dim} ${summary}${c.reset}`);
|
|
284
|
+
console.log(`${c.dim} → Updated ${path.basename(path.dirname(expertisePath))}/expertise.yaml${c.reset}`);
|
|
285
|
+
console.log('');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Run if executed directly
|
|
290
|
+
if (require.main === module) {
|
|
291
|
+
try {
|
|
292
|
+
main();
|
|
293
|
+
} catch (e) {
|
|
294
|
+
// Silent fail - don't break the workflow
|
|
295
|
+
if (process.env.DEBUG) {
|
|
296
|
+
console.error('auto-self-improve error:', e.message);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = { main, detectDomain, generateLearningSummary };
|