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.
Files changed (44) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/scripts/agileflow-configure.js +105 -27
  4. package/scripts/agileflow-welcome.js +95 -2
  5. package/scripts/auto-self-improve.js +301 -0
  6. package/scripts/ralph-loop.js +491 -0
  7. package/src/core/agents/accessibility.md +1 -1
  8. package/src/core/agents/adr-writer.md +6 -6
  9. package/src/core/agents/analytics.md +1 -1
  10. package/src/core/agents/api.md +130 -41
  11. package/src/core/agents/ci.md +3 -3
  12. package/src/core/agents/compliance.md +1 -1
  13. package/src/core/agents/database.md +121 -36
  14. package/src/core/agents/datamigration.md +1 -1
  15. package/src/core/agents/design.md +2 -2
  16. package/src/core/agents/devops.md +2 -2
  17. package/src/core/agents/documentation.md +2 -2
  18. package/src/core/agents/epic-planner.md +4 -4
  19. package/src/core/agents/integrations.md +4 -4
  20. package/src/core/agents/mentor.md +6 -6
  21. package/src/core/agents/mobile.md +2 -2
  22. package/src/core/agents/monitoring.md +2 -2
  23. package/src/core/agents/performance.md +2 -2
  24. package/src/core/agents/product.md +3 -3
  25. package/src/core/agents/qa.md +1 -1
  26. package/src/core/agents/refactor.md +1 -1
  27. package/src/core/agents/security.md +4 -4
  28. package/src/core/agents/testing.md +2 -2
  29. package/src/core/agents/ui.md +129 -44
  30. package/src/core/commands/babysit.md +210 -0
  31. package/src/core/commands/blockers.md +3 -3
  32. package/src/core/commands/configure.md +50 -7
  33. package/src/core/commands/context/export.md +99 -0
  34. package/src/core/commands/context/full.md +172 -0
  35. package/src/core/commands/context/note.md +128 -0
  36. package/src/core/commands/research/ask.md +453 -0
  37. package/src/core/commands/research/import.md +287 -0
  38. package/src/core/commands/research/list.md +93 -0
  39. package/src/core/commands/research/view.md +113 -0
  40. package/src/core/experts/documentation/expertise.yaml +4 -0
  41. package/src/core/experts/research/expertise.yaml +4 -4
  42. package/tools/cli/lib/docs-setup.js +1 -1
  43. package/src/core/commands/context.md +0 -417
  44. 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 };