agileflow 2.84.1 → 2.85.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/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/scripts/agileflow-welcome.js +385 -124
- package/scripts/obtain-context.js +7 -6
- package/scripts/session-boundary.js +138 -0
- package/scripts/session-manager.js +224 -0
- package/src/core/commands/session/new.md +80 -49
- package/src/core/commands/session/resume.md +40 -16
|
@@ -46,7 +46,70 @@ try {
|
|
|
46
46
|
// Update checker not available
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* PERFORMANCE OPTIMIZATION: Load all project files once into cache
|
|
51
|
+
* This eliminates duplicate file reads across multiple functions.
|
|
52
|
+
* Estimated savings: 40-80ms
|
|
53
|
+
*/
|
|
54
|
+
function loadProjectFiles(rootDir) {
|
|
55
|
+
const cache = {
|
|
56
|
+
status: null,
|
|
57
|
+
metadata: null,
|
|
58
|
+
settings: null,
|
|
59
|
+
sessionState: null,
|
|
60
|
+
configYaml: null,
|
|
61
|
+
cliPackage: null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const paths = {
|
|
65
|
+
status: 'docs/09-agents/status.json',
|
|
66
|
+
metadata: 'docs/00-meta/agileflow-metadata.json',
|
|
67
|
+
settings: '.claude/settings.json',
|
|
68
|
+
sessionState: 'docs/09-agents/session-state.json',
|
|
69
|
+
configYaml: '.agileflow/config.yaml',
|
|
70
|
+
cliPackage: 'packages/cli/package.json',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
for (const [key, relPath] of Object.entries(paths)) {
|
|
74
|
+
const fullPath = path.join(rootDir, relPath);
|
|
75
|
+
try {
|
|
76
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
77
|
+
if (key === 'configYaml') {
|
|
78
|
+
cache[key] = fs.readFileSync(fullPath, 'utf8');
|
|
79
|
+
} else {
|
|
80
|
+
cache[key] = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Silently ignore - file not available or invalid
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return cache;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* PERFORMANCE OPTIMIZATION: Batch git commands into single call
|
|
92
|
+
* Reduces subprocess overhead from 3 calls to 1.
|
|
93
|
+
* Estimated savings: 20-40ms
|
|
94
|
+
*/
|
|
95
|
+
function getGitInfo(rootDir) {
|
|
96
|
+
try {
|
|
97
|
+
const output = execSync(
|
|
98
|
+
'git branch --show-current && git rev-parse --short HEAD && git log -1 --format="%s"',
|
|
99
|
+
{ cwd: rootDir, encoding: 'utf8', timeout: 5000 }
|
|
100
|
+
);
|
|
101
|
+
const lines = output.trim().split('\n');
|
|
102
|
+
return {
|
|
103
|
+
branch: lines[0] || 'unknown',
|
|
104
|
+
commit: lines[1] || 'unknown',
|
|
105
|
+
lastCommit: lines[2] || '',
|
|
106
|
+
};
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { branch: 'unknown', commit: 'unknown', lastCommit: '' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getProjectInfo(rootDir, cache = null) {
|
|
50
113
|
const info = {
|
|
51
114
|
name: 'agileflow',
|
|
52
115
|
version: 'unknown',
|
|
@@ -66,61 +129,89 @@ function getProjectInfo(rootDir) {
|
|
|
66
129
|
// 2. AgileFlow metadata (installed user projects - legacy)
|
|
67
130
|
// 3. packages/cli/package.json (AgileFlow dev project)
|
|
68
131
|
try {
|
|
69
|
-
// Primary: .agileflow/config.yaml
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
73
|
-
const versionMatch = content.match(/^version:\s*['"]?([0-9.]+)/m);
|
|
132
|
+
// Primary: .agileflow/config.yaml (use cache if available)
|
|
133
|
+
if (cache?.configYaml) {
|
|
134
|
+
const versionMatch = cache.configYaml.match(/^version:\s*['"]?([0-9.]+)/m);
|
|
74
135
|
if (versionMatch) {
|
|
75
136
|
info.version = versionMatch[1];
|
|
76
137
|
}
|
|
138
|
+
} else if (cache?.metadata?.version) {
|
|
139
|
+
// Fallback: metadata from cache
|
|
140
|
+
info.version = cache.metadata.version;
|
|
141
|
+
} else if (cache?.cliPackage?.version) {
|
|
142
|
+
// Dev project: from cache
|
|
143
|
+
info.version = cache.cliPackage.version;
|
|
77
144
|
} else {
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
if (fs.existsSync(
|
|
81
|
-
const
|
|
82
|
-
|
|
145
|
+
// No cache - fall back to file reads (for backwards compatibility)
|
|
146
|
+
const configPath = path.join(rootDir, '.agileflow', 'config.yaml');
|
|
147
|
+
if (fs.existsSync(configPath)) {
|
|
148
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
149
|
+
const versionMatch = content.match(/^version:\s*['"]?([0-9.]+)/m);
|
|
150
|
+
if (versionMatch) {
|
|
151
|
+
info.version = versionMatch[1];
|
|
152
|
+
}
|
|
83
153
|
} else {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fs.readFileSync(
|
|
87
|
-
|
|
88
|
-
|
|
154
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
155
|
+
if (fs.existsSync(metadataPath)) {
|
|
156
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
157
|
+
info.version = metadata.version || info.version;
|
|
158
|
+
} else {
|
|
159
|
+
const pkg = JSON.parse(
|
|
160
|
+
fs.readFileSync(path.join(rootDir, 'packages/cli/package.json'), 'utf8')
|
|
161
|
+
);
|
|
162
|
+
info.version = pkg.version || info.version;
|
|
163
|
+
}
|
|
89
164
|
}
|
|
90
165
|
}
|
|
91
166
|
} catch (e) {
|
|
92
167
|
// Silently fail - version will remain 'unknown'
|
|
93
168
|
}
|
|
94
169
|
|
|
95
|
-
// Get git info
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
cwd: rootDir,
|
|
101
|
-
encoding: 'utf8',
|
|
102
|
-
}).trim();
|
|
103
|
-
} catch (e) {}
|
|
170
|
+
// Get git info (batched into single command for performance)
|
|
171
|
+
const gitInfo = getGitInfo(rootDir);
|
|
172
|
+
info.branch = gitInfo.branch;
|
|
173
|
+
info.commit = gitInfo.commit;
|
|
174
|
+
info.lastCommit = gitInfo.lastCommit;
|
|
104
175
|
|
|
105
|
-
// Get status info
|
|
176
|
+
// Get status info (use cache if available)
|
|
106
177
|
try {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
info.
|
|
113
|
-
if (
|
|
114
|
-
info.
|
|
115
|
-
|
|
116
|
-
|
|
178
|
+
const status = cache?.status;
|
|
179
|
+
if (status?.stories) {
|
|
180
|
+
for (const [id, story] of Object.entries(status.stories)) {
|
|
181
|
+
info.totalStories++;
|
|
182
|
+
if (story.status === 'in_progress') {
|
|
183
|
+
info.wipCount++;
|
|
184
|
+
if (!info.currentStory) {
|
|
185
|
+
info.currentStory = { id, title: story.title };
|
|
186
|
+
}
|
|
187
|
+
} else if (story.status === 'blocked') {
|
|
188
|
+
info.blockedCount++;
|
|
189
|
+
} else if (story.status === 'completed') {
|
|
190
|
+
info.completedCount++;
|
|
191
|
+
} else if (story.status === 'ready') {
|
|
192
|
+
info.readyCount++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else if (!cache) {
|
|
196
|
+
// No cache - fall back to file read
|
|
197
|
+
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
198
|
+
if (fs.existsSync(statusPath)) {
|
|
199
|
+
const statusData = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
200
|
+
if (statusData.stories) {
|
|
201
|
+
for (const [id, story] of Object.entries(statusData.stories)) {
|
|
202
|
+
info.totalStories++;
|
|
203
|
+
if (story.status === 'in_progress') {
|
|
204
|
+
info.wipCount++;
|
|
205
|
+
if (!info.currentStory) {
|
|
206
|
+
info.currentStory = { id, title: story.title };
|
|
207
|
+
}
|
|
208
|
+
} else if (story.status === 'blocked') {
|
|
209
|
+
info.blockedCount++;
|
|
210
|
+
} else if (story.status === 'completed') {
|
|
211
|
+
info.completedCount++;
|
|
212
|
+
} else if (story.status === 'ready') {
|
|
213
|
+
info.readyCount++;
|
|
117
214
|
}
|
|
118
|
-
} else if (story.status === 'blocked') {
|
|
119
|
-
info.blockedCount++;
|
|
120
|
-
} else if (story.status === 'completed') {
|
|
121
|
-
info.completedCount++;
|
|
122
|
-
} else if (story.status === 'ready') {
|
|
123
|
-
info.readyCount++;
|
|
124
215
|
}
|
|
125
216
|
}
|
|
126
217
|
}
|
|
@@ -130,25 +221,39 @@ function getProjectInfo(rootDir) {
|
|
|
130
221
|
return info;
|
|
131
222
|
}
|
|
132
223
|
|
|
133
|
-
function runArchival(rootDir) {
|
|
224
|
+
function runArchival(rootDir, cache = null) {
|
|
134
225
|
const result = { ran: false, threshold: 7, archived: 0, remaining: 0 };
|
|
135
226
|
|
|
136
227
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
228
|
+
// Use cached metadata if available
|
|
229
|
+
const metadata = cache?.metadata;
|
|
230
|
+
if (metadata) {
|
|
140
231
|
if (metadata.archival?.enabled === false) {
|
|
141
232
|
result.disabled = true;
|
|
142
233
|
return result;
|
|
143
234
|
}
|
|
144
235
|
result.threshold = metadata.archival?.threshold_days || 7;
|
|
236
|
+
} else {
|
|
237
|
+
// No cache - fall back to file read
|
|
238
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
239
|
+
if (fs.existsSync(metadataPath)) {
|
|
240
|
+
const metadataData = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
241
|
+
if (metadataData.archival?.enabled === false) {
|
|
242
|
+
result.disabled = true;
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
result.threshold = metadataData.archival?.threshold_days || 7;
|
|
246
|
+
}
|
|
145
247
|
}
|
|
146
248
|
|
|
147
|
-
|
|
148
|
-
|
|
249
|
+
// Use cached status if available
|
|
250
|
+
const status = cache?.status;
|
|
251
|
+
if (!status && !cache) {
|
|
252
|
+
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
253
|
+
if (!fs.existsSync(statusPath)) return result;
|
|
254
|
+
}
|
|
149
255
|
|
|
150
|
-
const
|
|
151
|
-
const stories = status.stories || {};
|
|
256
|
+
const stories = (status || {}).stories || {};
|
|
152
257
|
|
|
153
258
|
const cutoffDate = new Date();
|
|
154
259
|
cutoffDate.setDate(cutoffDate.getDate() - result.threshold);
|
|
@@ -182,15 +287,23 @@ function runArchival(rootDir) {
|
|
|
182
287
|
return result;
|
|
183
288
|
}
|
|
184
289
|
|
|
185
|
-
function clearActiveCommands(rootDir) {
|
|
290
|
+
function clearActiveCommands(rootDir, cache = null) {
|
|
186
291
|
const result = { ran: false, cleared: 0, commandNames: [] };
|
|
187
292
|
|
|
188
293
|
try {
|
|
189
294
|
const sessionStatePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
190
|
-
if (!fs.existsSync(sessionStatePath)) return result;
|
|
191
295
|
|
|
192
|
-
|
|
193
|
-
|
|
296
|
+
// Use cached sessionState if available, but we still need to read fresh for clearing
|
|
297
|
+
// because we need to write back. Cache is only useful to check if file exists.
|
|
298
|
+
let state;
|
|
299
|
+
if (cache?.sessionState) {
|
|
300
|
+
state = cache.sessionState;
|
|
301
|
+
result.ran = true;
|
|
302
|
+
} else {
|
|
303
|
+
if (!fs.existsSync(sessionStatePath)) return result;
|
|
304
|
+
state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
305
|
+
result.ran = true;
|
|
306
|
+
}
|
|
194
307
|
|
|
195
308
|
// Handle new array format (active_commands)
|
|
196
309
|
if (state.active_commands && state.active_commands.length > 0) {
|
|
@@ -245,52 +358,72 @@ function checkParallelSessions(rootDir) {
|
|
|
245
358
|
|
|
246
359
|
result.available = true;
|
|
247
360
|
|
|
248
|
-
// Try to
|
|
361
|
+
// Try to use combined full-status command (saves ~200ms vs 3 separate calls)
|
|
249
362
|
const scriptPath = fs.existsSync(managerPath) ? managerPath : SESSION_MANAGER_PATH;
|
|
250
363
|
|
|
251
|
-
// Register this session
|
|
252
364
|
try {
|
|
253
|
-
|
|
365
|
+
// PERFORMANCE: Single subprocess call instead of 3 (register + count + status)
|
|
366
|
+
const fullStatusOutput = execSync(`node "${scriptPath}" full-status`, {
|
|
254
367
|
cwd: rootDir,
|
|
255
368
|
encoding: 'utf8',
|
|
256
369
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
257
370
|
});
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
result.
|
|
371
|
+
const data = JSON.parse(fullStatusOutput);
|
|
372
|
+
|
|
373
|
+
result.registered = data.registered;
|
|
374
|
+
result.currentId = data.id;
|
|
375
|
+
result.otherActive = data.otherActive || 0;
|
|
376
|
+
result.cleaned = data.cleaned || 0;
|
|
377
|
+
|
|
378
|
+
if (data.current) {
|
|
379
|
+
result.isMain = data.current.is_main === true;
|
|
380
|
+
result.nickname = data.current.nickname;
|
|
381
|
+
result.branch = data.current.branch;
|
|
382
|
+
result.sessionPath = data.current.path;
|
|
383
|
+
}
|
|
261
384
|
} catch (e) {
|
|
262
|
-
//
|
|
263
|
-
|
|
385
|
+
// Fall back to individual calls if full-status not available (older version)
|
|
386
|
+
try {
|
|
387
|
+
const registerOutput = execSync(`node "${scriptPath}" register`, {
|
|
388
|
+
cwd: rootDir,
|
|
389
|
+
encoding: 'utf8',
|
|
390
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
391
|
+
});
|
|
392
|
+
const registerData = JSON.parse(registerOutput);
|
|
393
|
+
result.registered = true;
|
|
394
|
+
result.currentId = registerData.id;
|
|
395
|
+
} catch (e) {
|
|
396
|
+
// Registration failed
|
|
397
|
+
}
|
|
264
398
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
399
|
+
try {
|
|
400
|
+
const countOutput = execSync(`node "${scriptPath}" count`, {
|
|
401
|
+
cwd: rootDir,
|
|
402
|
+
encoding: 'utf8',
|
|
403
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
404
|
+
});
|
|
405
|
+
const countData = JSON.parse(countOutput);
|
|
406
|
+
result.otherActive = countData.count || 0;
|
|
407
|
+
} catch (e) {
|
|
408
|
+
// Count failed
|
|
409
|
+
}
|
|
277
410
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
411
|
+
try {
|
|
412
|
+
const statusOutput = execSync(`node "${scriptPath}" status`, {
|
|
413
|
+
cwd: rootDir,
|
|
414
|
+
encoding: 'utf8',
|
|
415
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
416
|
+
});
|
|
417
|
+
const statusData = JSON.parse(statusOutput);
|
|
418
|
+
if (statusData.current) {
|
|
419
|
+
result.isMain = statusData.current.is_main === true;
|
|
420
|
+
result.nickname = statusData.current.nickname;
|
|
421
|
+
result.branch = statusData.current.branch;
|
|
422
|
+
result.sessionPath = statusData.current.path;
|
|
423
|
+
}
|
|
424
|
+
} catch (e) {
|
|
425
|
+
// Status failed
|
|
291
426
|
}
|
|
292
|
-
} catch (e) {
|
|
293
|
-
// Status failed
|
|
294
427
|
}
|
|
295
428
|
} catch (e) {
|
|
296
429
|
// Session system not available
|
|
@@ -299,29 +432,36 @@ function checkParallelSessions(rootDir) {
|
|
|
299
432
|
return result;
|
|
300
433
|
}
|
|
301
434
|
|
|
302
|
-
function checkPreCompact(rootDir) {
|
|
435
|
+
function checkPreCompact(rootDir, cache = null) {
|
|
303
436
|
const result = { configured: false, scriptExists: false, version: null, outdated: false };
|
|
304
437
|
|
|
305
438
|
try {
|
|
306
|
-
// Check if PreCompact hook is configured in settings
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
309
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
439
|
+
// Check if PreCompact hook is configured in settings (use cache if available)
|
|
440
|
+
const settings = cache?.settings;
|
|
441
|
+
if (settings) {
|
|
310
442
|
if (settings.hooks?.PreCompact?.length > 0) {
|
|
311
443
|
result.configured = true;
|
|
312
444
|
}
|
|
445
|
+
} else {
|
|
446
|
+
// No cache - fall back to file read
|
|
447
|
+
const settingsPath = path.join(rootDir, '.claude/settings.json');
|
|
448
|
+
if (fs.existsSync(settingsPath)) {
|
|
449
|
+
const settingsData = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
450
|
+
if (settingsData.hooks?.PreCompact?.length > 0) {
|
|
451
|
+
result.configured = true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
313
454
|
}
|
|
314
455
|
|
|
315
|
-
// Check if the script exists
|
|
456
|
+
// Check if the script exists (must always check filesystem)
|
|
316
457
|
const scriptPath = path.join(rootDir, 'scripts/precompact-context.sh');
|
|
317
458
|
if (fs.existsSync(scriptPath)) {
|
|
318
459
|
result.scriptExists = true;
|
|
319
460
|
}
|
|
320
461
|
|
|
321
|
-
// Check configured version from metadata
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
324
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
462
|
+
// Check configured version from metadata (use cache if available)
|
|
463
|
+
const metadata = cache?.metadata;
|
|
464
|
+
if (metadata) {
|
|
325
465
|
if (metadata.features?.precompact?.configured_version) {
|
|
326
466
|
result.version = metadata.features.precompact.configured_version;
|
|
327
467
|
// PreCompact v2.40.0+ has multi-command support
|
|
@@ -331,20 +471,40 @@ function checkPreCompact(rootDir) {
|
|
|
331
471
|
result.outdated = true;
|
|
332
472
|
result.version = 'unknown';
|
|
333
473
|
}
|
|
474
|
+
} else if (!cache) {
|
|
475
|
+
// No cache - fall back to file read
|
|
476
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
477
|
+
if (fs.existsSync(metadataPath)) {
|
|
478
|
+
const metadataData = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
479
|
+
if (metadataData.features?.precompact?.configured_version) {
|
|
480
|
+
result.version = metadataData.features.precompact.configured_version;
|
|
481
|
+
result.outdated = compareVersions(result.version, '2.40.0') < 0;
|
|
482
|
+
} else if (result.configured) {
|
|
483
|
+
result.outdated = true;
|
|
484
|
+
result.version = 'unknown';
|
|
485
|
+
}
|
|
486
|
+
}
|
|
334
487
|
}
|
|
335
488
|
} catch (e) {}
|
|
336
489
|
|
|
337
490
|
return result;
|
|
338
491
|
}
|
|
339
492
|
|
|
340
|
-
function checkDamageControl(rootDir) {
|
|
493
|
+
function checkDamageControl(rootDir, cache = null) {
|
|
341
494
|
const result = { configured: false, level: 'standard', patternCount: 0, scriptsOk: true };
|
|
342
495
|
|
|
343
496
|
try {
|
|
344
|
-
// Check if PreToolUse hooks are configured in settings
|
|
345
|
-
|
|
346
|
-
if (
|
|
347
|
-
|
|
497
|
+
// Check if PreToolUse hooks are configured in settings (use cache if available)
|
|
498
|
+
let settings = cache?.settings;
|
|
499
|
+
if (!settings && !cache) {
|
|
500
|
+
// No cache - fall back to file read
|
|
501
|
+
const settingsPath = path.join(rootDir, '.claude/settings.json');
|
|
502
|
+
if (fs.existsSync(settingsPath)) {
|
|
503
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (settings) {
|
|
348
508
|
if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
|
|
349
509
|
// Check for damage-control hooks
|
|
350
510
|
const hasDamageControlHooks = settings.hooks.PreToolUse.some(h =>
|
|
@@ -493,26 +653,112 @@ function getChangelogEntries(version) {
|
|
|
493
653
|
return entries;
|
|
494
654
|
}
|
|
495
655
|
|
|
496
|
-
// Run auto-update if enabled
|
|
497
|
-
async function runAutoUpdate(rootDir) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
execSync('npx agileflow@latest update --force', {
|
|
656
|
+
// Run auto-update if enabled (quiet mode - minimal output)
|
|
657
|
+
async function runAutoUpdate(rootDir, fromVersion, toVersion) {
|
|
658
|
+
const runUpdate = () => {
|
|
659
|
+
// Use stdio: 'pipe' to capture output instead of showing everything
|
|
660
|
+
return execSync('npx agileflow@latest update --force', {
|
|
502
661
|
cwd: rootDir,
|
|
503
662
|
encoding: 'utf8',
|
|
504
|
-
stdio: '
|
|
663
|
+
stdio: 'pipe',
|
|
505
664
|
timeout: 120000, // 2 minute timeout
|
|
506
665
|
});
|
|
507
|
-
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
console.log(`${c.skyBlue}Updating AgileFlow${c.reset} ${c.dim}v${fromVersion} → v${toVersion}${c.reset}`);
|
|
670
|
+
// Use --force to skip prompts for non-interactive auto-update
|
|
671
|
+
runUpdate();
|
|
672
|
+
console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
|
|
508
673
|
return true;
|
|
509
674
|
} catch (e) {
|
|
510
|
-
|
|
511
|
-
|
|
675
|
+
// Check if this is a stale npm cache issue (ETARGET = version not found)
|
|
676
|
+
if (e.message && (e.message.includes('ETARGET') || e.message.includes('notarget'))) {
|
|
677
|
+
console.log(`${c.dim} Clearing npm cache and retrying...${c.reset}`);
|
|
678
|
+
try {
|
|
679
|
+
execSync('npm cache clean --force', { stdio: 'pipe', timeout: 30000 });
|
|
680
|
+
runUpdate();
|
|
681
|
+
console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
|
|
682
|
+
return true;
|
|
683
|
+
} catch (retryError) {
|
|
684
|
+
console.log(`${c.peach}Auto-update failed after cache clean${c.reset}`);
|
|
685
|
+
console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
console.log(`${c.peach}Auto-update failed${c.reset}`);
|
|
690
|
+
console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
|
|
512
691
|
return false;
|
|
513
692
|
}
|
|
514
693
|
}
|
|
515
694
|
|
|
695
|
+
/**
|
|
696
|
+
* PERFORMANCE OPTIMIZATION: Fast expertise count (directory scan only)
|
|
697
|
+
* Just counts expert directories without reading/validating each expertise.yaml.
|
|
698
|
+
* Saves ~50-150ms by avoiding 29 file reads.
|
|
699
|
+
* Full validation is available via /agileflow:validate-expertise command.
|
|
700
|
+
*/
|
|
701
|
+
function getExpertiseCountFast(rootDir) {
|
|
702
|
+
const result = { total: 0, passed: 0, warnings: 0, failed: 0, issues: [], validated: false };
|
|
703
|
+
|
|
704
|
+
// Find experts directory
|
|
705
|
+
let expertsDir = path.join(rootDir, '.agileflow', 'experts');
|
|
706
|
+
if (!fs.existsSync(expertsDir)) {
|
|
707
|
+
expertsDir = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'experts');
|
|
708
|
+
}
|
|
709
|
+
if (!fs.existsSync(expertsDir)) {
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
try {
|
|
714
|
+
const domains = fs
|
|
715
|
+
.readdirSync(expertsDir, { withFileTypes: true })
|
|
716
|
+
.filter(d => d.isDirectory() && d.name !== 'templates');
|
|
717
|
+
|
|
718
|
+
result.total = domains.length;
|
|
719
|
+
|
|
720
|
+
// Quick check: just verify expertise.yaml exists in each directory
|
|
721
|
+
// Full validation (staleness, required fields) deferred to separate command
|
|
722
|
+
for (const domain of domains) {
|
|
723
|
+
const filePath = path.join(expertsDir, domain.name, 'expertise.yaml');
|
|
724
|
+
if (!fs.existsSync(filePath)) {
|
|
725
|
+
result.failed++;
|
|
726
|
+
result.issues.push(`${domain.name}: missing file`);
|
|
727
|
+
} else {
|
|
728
|
+
// Spot-check first few files for staleness (sample 3 max for speed)
|
|
729
|
+
if (result.passed < 3) {
|
|
730
|
+
try {
|
|
731
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
732
|
+
const lastUpdatedMatch = content.match(/^last_updated:\s*['"]?(\d{4}-\d{2}-\d{2})/m);
|
|
733
|
+
if (lastUpdatedMatch) {
|
|
734
|
+
const lastDate = new Date(lastUpdatedMatch[1]);
|
|
735
|
+
const daysSince = Math.floor((Date.now() - lastDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
736
|
+
if (daysSince > 30) {
|
|
737
|
+
result.warnings++;
|
|
738
|
+
result.issues.push(`${domain.name}: stale (${daysSince}d)`);
|
|
739
|
+
} else {
|
|
740
|
+
result.passed++;
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
result.passed++;
|
|
744
|
+
}
|
|
745
|
+
} catch (e) {
|
|
746
|
+
result.passed++;
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
// Assume rest are ok for fast display
|
|
750
|
+
result.passed++;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
} catch (e) {
|
|
755
|
+
// Silently fail
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return result;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Full validation function (kept for /agileflow:validate-expertise command)
|
|
516
762
|
function validateExpertise(rootDir) {
|
|
517
763
|
const result = { total: 0, passed: 0, warnings: 0, failed: 0, issues: [] };
|
|
518
764
|
|
|
@@ -950,13 +1196,21 @@ function formatSessionBanner(parallelSessions) {
|
|
|
950
1196
|
// Main
|
|
951
1197
|
async function main() {
|
|
952
1198
|
const rootDir = getProjectRoot();
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const
|
|
1199
|
+
|
|
1200
|
+
// PERFORMANCE: Load all project files once into cache
|
|
1201
|
+
// This eliminates 6-8 duplicate file reads across functions
|
|
1202
|
+
const cache = loadProjectFiles(rootDir);
|
|
1203
|
+
|
|
1204
|
+
// All functions now use cached file data where possible
|
|
1205
|
+
const info = getProjectInfo(rootDir, cache);
|
|
1206
|
+
const archival = runArchival(rootDir, cache);
|
|
1207
|
+
const session = clearActiveCommands(rootDir, cache);
|
|
1208
|
+
const precompact = checkPreCompact(rootDir, cache);
|
|
957
1209
|
const parallelSessions = checkParallelSessions(rootDir);
|
|
958
|
-
|
|
959
|
-
|
|
1210
|
+
// PERFORMANCE: Use fast expertise count (directory scan only, ~3 file samples)
|
|
1211
|
+
// Full validation available via /agileflow:validate-expertise
|
|
1212
|
+
const expertise = getExpertiseCountFast(rootDir);
|
|
1213
|
+
const damageControl = checkDamageControl(rootDir, cache);
|
|
960
1214
|
|
|
961
1215
|
// Check for updates (async, cached)
|
|
962
1216
|
let updateInfo = {};
|
|
@@ -964,11 +1218,18 @@ async function main() {
|
|
|
964
1218
|
updateInfo = await checkUpdates();
|
|
965
1219
|
|
|
966
1220
|
// If auto-update is enabled and update available, run it
|
|
967
|
-
if (updateInfo.available && updateInfo.autoUpdate) {
|
|
968
|
-
const updated = await runAutoUpdate(rootDir);
|
|
1221
|
+
if (updateInfo.available && updateInfo.autoUpdate && updateInfo.latest) {
|
|
1222
|
+
const updated = await runAutoUpdate(rootDir, info.version, updateInfo.latest);
|
|
969
1223
|
if (updated) {
|
|
970
|
-
//
|
|
971
|
-
|
|
1224
|
+
// Mark as "just updated" so the welcome table shows it
|
|
1225
|
+
updateInfo.justUpdated = true;
|
|
1226
|
+
updateInfo.previousVersion = info.version;
|
|
1227
|
+
// Update local info with new version
|
|
1228
|
+
info.version = updateInfo.latest;
|
|
1229
|
+
// Get changelog entries for the new version
|
|
1230
|
+
updateInfo.changelog = getChangelogEntries(updateInfo.latest);
|
|
1231
|
+
// Clear the "update available" flag since we just updated
|
|
1232
|
+
updateInfo.available = false;
|
|
972
1233
|
}
|
|
973
1234
|
}
|
|
974
1235
|
|