agileflow 2.56.0 → 2.57.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-welcome.js +172 -9
- package/scripts/check-update.js +325 -0
- package/src/core/commands/whats-new.md +94 -0
package/package.json
CHANGED
|
@@ -18,6 +18,14 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
18
18
|
// Session manager path (relative to script location)
|
|
19
19
|
const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
|
|
20
20
|
|
|
21
|
+
// Update checker module
|
|
22
|
+
let updateChecker;
|
|
23
|
+
try {
|
|
24
|
+
updateChecker = require('./check-update.js');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Update checker not available
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
// ANSI color codes
|
|
22
30
|
const c = {
|
|
23
31
|
reset: '\x1b[0m',
|
|
@@ -322,6 +330,98 @@ function compareVersions(a, b) {
|
|
|
322
330
|
return 0;
|
|
323
331
|
}
|
|
324
332
|
|
|
333
|
+
// Check for updates (async but we'll use sync approach for welcome)
|
|
334
|
+
async function checkUpdates() {
|
|
335
|
+
const result = {
|
|
336
|
+
available: false,
|
|
337
|
+
installed: null,
|
|
338
|
+
latest: null,
|
|
339
|
+
justUpdated: false,
|
|
340
|
+
previousVersion: null,
|
|
341
|
+
autoUpdate: false,
|
|
342
|
+
changelog: [],
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (!updateChecker) return result;
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const updateInfo = await updateChecker.checkForUpdates();
|
|
349
|
+
result.installed = updateInfo.installed;
|
|
350
|
+
result.latest = updateInfo.latest;
|
|
351
|
+
result.available = updateInfo.updateAvailable;
|
|
352
|
+
result.justUpdated = updateInfo.justUpdated;
|
|
353
|
+
result.previousVersion = updateInfo.previousVersion;
|
|
354
|
+
result.autoUpdate = updateInfo.autoUpdate;
|
|
355
|
+
|
|
356
|
+
// If just updated, try to get changelog entries
|
|
357
|
+
if (result.justUpdated && result.installed) {
|
|
358
|
+
result.changelog = getChangelogEntries(result.installed);
|
|
359
|
+
}
|
|
360
|
+
} catch (e) {
|
|
361
|
+
// Silently fail - update check is non-critical
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Parse CHANGELOG.md for entries of a specific version
|
|
368
|
+
function getChangelogEntries(version) {
|
|
369
|
+
const entries = [];
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
// Look for CHANGELOG.md in .agileflow or package location
|
|
373
|
+
const possiblePaths = [
|
|
374
|
+
path.join(__dirname, '..', 'CHANGELOG.md'),
|
|
375
|
+
path.join(__dirname, 'CHANGELOG.md'),
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
let changelogContent = null;
|
|
379
|
+
for (const p of possiblePaths) {
|
|
380
|
+
if (fs.existsSync(p)) {
|
|
381
|
+
changelogContent = fs.readFileSync(p, 'utf8');
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!changelogContent) return entries;
|
|
387
|
+
|
|
388
|
+
// Find the section for this version
|
|
389
|
+
const versionPattern = new RegExp(`## \\[${version}\\].*?\\n([\\s\\S]*?)(?=## \\[|$)`);
|
|
390
|
+
const match = changelogContent.match(versionPattern);
|
|
391
|
+
|
|
392
|
+
if (match) {
|
|
393
|
+
// Extract bullet points from Added/Changed/Fixed sections
|
|
394
|
+
const lines = match[1].split('\n');
|
|
395
|
+
for (const line of lines) {
|
|
396
|
+
const bulletMatch = line.match(/^- (.+)$/);
|
|
397
|
+
if (bulletMatch && entries.length < 3) {
|
|
398
|
+
entries.push(bulletMatch[1]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} catch (e) {
|
|
403
|
+
// Silently fail
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return entries;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Run auto-update if enabled
|
|
410
|
+
async function runAutoUpdate(rootDir) {
|
|
411
|
+
try {
|
|
412
|
+
console.log(`${c.cyan}Updating AgileFlow...${c.reset}`);
|
|
413
|
+
execSync('npx agileflow update', {
|
|
414
|
+
cwd: rootDir,
|
|
415
|
+
encoding: 'utf8',
|
|
416
|
+
stdio: 'inherit',
|
|
417
|
+
});
|
|
418
|
+
return true;
|
|
419
|
+
} catch (e) {
|
|
420
|
+
console.log(`${c.yellow}Auto-update failed. Run manually: npx agileflow update${c.reset}`);
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
325
425
|
function getFeatureVersions(rootDir) {
|
|
326
426
|
const result = {
|
|
327
427
|
hooks: { version: null, outdated: false },
|
|
@@ -394,7 +494,7 @@ function truncate(str, maxLen, suffix = '..') {
|
|
|
394
494
|
return str.substring(0, cutIndex) + suffix;
|
|
395
495
|
}
|
|
396
496
|
|
|
397
|
-
function formatTable(info, archival, session, precompact, parallelSessions) {
|
|
497
|
+
function formatTable(info, archival, session, precompact, parallelSessions, updateInfo = {}) {
|
|
398
498
|
const W = 58; // inner width
|
|
399
499
|
const R = W - 24; // right column width (34 chars)
|
|
400
500
|
const lines = [];
|
|
@@ -407,25 +507,66 @@ function formatTable(info, archival, session, precompact, parallelSessions) {
|
|
|
407
507
|
return `${c.dim}${box.v}${c.reset} ${pad(leftStr, 20)} ${c.dim}${box.v}${c.reset} ${pad(rightStr, R)} ${c.dim}${box.v}${c.reset}`;
|
|
408
508
|
};
|
|
409
509
|
|
|
510
|
+
// Helper for full-width row (spans both columns)
|
|
511
|
+
const fullRow = (content, color = '') => {
|
|
512
|
+
const contentStr = `${color}${content}${color ? c.reset : ''}`;
|
|
513
|
+
return `${c.dim}${box.v}${c.reset} ${pad(contentStr, W - 1)} ${c.dim}${box.v}${c.reset}`;
|
|
514
|
+
};
|
|
515
|
+
|
|
410
516
|
const divider = () =>
|
|
411
517
|
`${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 22)}${box.rT}${c.reset}`;
|
|
518
|
+
const fullDivider = () =>
|
|
519
|
+
`${c.dim}${box.lT}${box.h.repeat(W)}${box.rT}${c.reset}`;
|
|
412
520
|
const topBorder = `${c.dim}${box.tl}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 22)}${box.tr}${c.reset}`;
|
|
413
521
|
const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 22)}${box.br}${c.reset}`;
|
|
414
522
|
|
|
415
|
-
// Header
|
|
523
|
+
// Header with version and optional update indicator
|
|
416
524
|
const branchColor =
|
|
417
525
|
info.branch === 'main' ? c.green : info.branch.startsWith('fix') ? c.red : c.cyan;
|
|
418
|
-
|
|
419
|
-
|
|
526
|
+
|
|
527
|
+
// Build version string with update status
|
|
528
|
+
let versionStr = `v${info.version}`;
|
|
529
|
+
if (updateInfo.justUpdated && updateInfo.previousVersion) {
|
|
530
|
+
versionStr = `v${info.version} ${c.green}✓${c.reset}${c.dim} (was v${updateInfo.previousVersion})`;
|
|
531
|
+
} else if (updateInfo.available && updateInfo.latest) {
|
|
532
|
+
versionStr = `v${info.version} ${c.yellow}↑${updateInfo.latest}${c.reset}`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Calculate remaining space for branch
|
|
536
|
+
const versionVisibleLen = updateInfo.justUpdated
|
|
537
|
+
? info.version.length + 20 + (updateInfo.previousVersion?.length || 0)
|
|
538
|
+
: updateInfo.available
|
|
539
|
+
? info.version.length + 3 + (updateInfo.latest?.length || 0)
|
|
540
|
+
: info.version.length;
|
|
541
|
+
const maxBranchLen = W - 1 - 15 - versionVisibleLen;
|
|
420
542
|
const branchDisplay =
|
|
421
543
|
info.branch.length > maxBranchLen
|
|
422
|
-
? info.branch.substring(0, maxBranchLen - 2) + '..'
|
|
544
|
+
? info.branch.substring(0, Math.max(5, maxBranchLen - 2)) + '..'
|
|
423
545
|
: info.branch;
|
|
424
|
-
|
|
546
|
+
|
|
547
|
+
const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}${versionStr}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
|
|
425
548
|
const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 1)} ${c.dim}${box.v}${c.reset}`;
|
|
426
549
|
|
|
427
550
|
lines.push(topBorder);
|
|
428
551
|
lines.push(headerLine);
|
|
552
|
+
|
|
553
|
+
// Show update available notification
|
|
554
|
+
if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
|
|
555
|
+
lines.push(fullDivider());
|
|
556
|
+
lines.push(fullRow(`↑ Update available: v${updateInfo.latest}`, c.yellow));
|
|
557
|
+
lines.push(fullRow(` Run: npx agileflow update`, c.dim));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Show "just updated" changelog
|
|
561
|
+
if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
|
|
562
|
+
lines.push(fullDivider());
|
|
563
|
+
lines.push(fullRow(`What's new in v${info.version}:`, c.green));
|
|
564
|
+
for (const entry of updateInfo.changelog.slice(0, 2)) {
|
|
565
|
+
lines.push(fullRow(`• ${truncate(entry, W - 4)}`, c.dim));
|
|
566
|
+
}
|
|
567
|
+
lines.push(fullRow(`Run /agileflow:whats-new for full changelog`, c.dim));
|
|
568
|
+
}
|
|
569
|
+
|
|
429
570
|
lines.push(divider());
|
|
430
571
|
|
|
431
572
|
// Stories section
|
|
@@ -535,7 +676,7 @@ function formatTable(info, archival, session, precompact, parallelSessions) {
|
|
|
535
676
|
}
|
|
536
677
|
|
|
537
678
|
// Main
|
|
538
|
-
function main() {
|
|
679
|
+
async function main() {
|
|
539
680
|
const rootDir = getProjectRoot();
|
|
540
681
|
const info = getProjectInfo(rootDir);
|
|
541
682
|
const archival = runArchival(rootDir);
|
|
@@ -543,7 +684,29 @@ function main() {
|
|
|
543
684
|
const precompact = checkPreCompact(rootDir);
|
|
544
685
|
const parallelSessions = checkParallelSessions(rootDir);
|
|
545
686
|
|
|
546
|
-
|
|
687
|
+
// Check for updates (async, cached)
|
|
688
|
+
let updateInfo = {};
|
|
689
|
+
try {
|
|
690
|
+
updateInfo = await checkUpdates();
|
|
691
|
+
|
|
692
|
+
// If auto-update is enabled and update available, run it
|
|
693
|
+
if (updateInfo.available && updateInfo.autoUpdate) {
|
|
694
|
+
const updated = await runAutoUpdate(rootDir);
|
|
695
|
+
if (updated) {
|
|
696
|
+
// Re-run welcome after update (the new version will show changelog)
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Mark current version as seen to track for next update
|
|
702
|
+
if (updateInfo.justUpdated && updateChecker) {
|
|
703
|
+
updateChecker.markVersionSeen(info.version);
|
|
704
|
+
}
|
|
705
|
+
} catch (e) {
|
|
706
|
+
// Update check failed - continue without it
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
console.log(formatTable(info, archival, session, precompact, parallelSessions, updateInfo));
|
|
547
710
|
|
|
548
711
|
// Show warning and tip if other sessions are active
|
|
549
712
|
if (parallelSessions.otherActive > 0) {
|
|
@@ -554,4 +717,4 @@ function main() {
|
|
|
554
717
|
}
|
|
555
718
|
}
|
|
556
719
|
|
|
557
|
-
main();
|
|
720
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* check-update.js - Check for AgileFlow updates with caching
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Checks npm registry for latest version
|
|
8
|
+
* - Caches results to avoid excessive requests
|
|
9
|
+
* - Configurable check frequency (hourly, daily, weekly)
|
|
10
|
+
* - Returns structured JSON for easy parsing
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node check-update.js [--force] [--json]
|
|
14
|
+
*
|
|
15
|
+
* Options:
|
|
16
|
+
* --force Bypass cache and check npm directly
|
|
17
|
+
* --json Output as JSON (default is human-readable)
|
|
18
|
+
*
|
|
19
|
+
* Environment:
|
|
20
|
+
* DEBUG_UPDATE=1 Enable debug logging
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const https = require('https');
|
|
26
|
+
|
|
27
|
+
// Debug mode
|
|
28
|
+
const DEBUG = process.env.DEBUG_UPDATE === '1';
|
|
29
|
+
|
|
30
|
+
function debugLog(message, data = null) {
|
|
31
|
+
if (DEBUG) {
|
|
32
|
+
console.error(`[check-update] ${message}`, data ? JSON.stringify(data) : '');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Find project root (has .agileflow directory)
|
|
37
|
+
function getProjectRoot() {
|
|
38
|
+
let dir = process.cwd();
|
|
39
|
+
while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
|
|
40
|
+
dir = path.dirname(dir);
|
|
41
|
+
}
|
|
42
|
+
return dir !== '/' ? dir : process.cwd();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get installed AgileFlow version
|
|
46
|
+
function getInstalledVersion(rootDir) {
|
|
47
|
+
// First check .agileflow/package.json (installed version)
|
|
48
|
+
const agileflowPkg = path.join(rootDir, '.agileflow', 'package.json');
|
|
49
|
+
if (fs.existsSync(agileflowPkg)) {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(agileflowPkg, 'utf8'));
|
|
52
|
+
if (pkg.version) return pkg.version;
|
|
53
|
+
} catch (e) {
|
|
54
|
+
debugLog('Error reading .agileflow/package.json', e.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Fallback: check if this is the AgileFlow dev repo
|
|
59
|
+
const cliPkg = path.join(rootDir, 'packages/cli/package.json');
|
|
60
|
+
if (fs.existsSync(cliPkg)) {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(fs.readFileSync(cliPkg, 'utf8'));
|
|
63
|
+
if (pkg.name === 'agileflow' && pkg.version) return pkg.version;
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get update configuration from metadata
|
|
71
|
+
function getUpdateConfig(rootDir) {
|
|
72
|
+
const defaults = {
|
|
73
|
+
autoUpdate: false,
|
|
74
|
+
checkFrequency: 'daily', // hourly, daily, weekly, never
|
|
75
|
+
showChangelog: true,
|
|
76
|
+
lastCheck: null,
|
|
77
|
+
lastSeenVersion: null,
|
|
78
|
+
latestVersion: null,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
83
|
+
if (fs.existsSync(metadataPath)) {
|
|
84
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
85
|
+
if (metadata.updates) {
|
|
86
|
+
return { ...defaults, ...metadata.updates };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
debugLog('Error reading update config', e.message);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return defaults;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Save update configuration
|
|
97
|
+
function saveUpdateConfig(rootDir, config) {
|
|
98
|
+
try {
|
|
99
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
100
|
+
let metadata = {};
|
|
101
|
+
|
|
102
|
+
if (fs.existsSync(metadataPath)) {
|
|
103
|
+
metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
metadata.updates = config;
|
|
107
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
|
|
108
|
+
return true;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
debugLog('Error saving update config', e.message);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if cache is still valid
|
|
116
|
+
function isCacheValid(config) {
|
|
117
|
+
if (!config.lastCheck) return false;
|
|
118
|
+
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const lastCheck = new Date(config.lastCheck).getTime();
|
|
121
|
+
const age = now - lastCheck;
|
|
122
|
+
|
|
123
|
+
// Convert frequency to milliseconds
|
|
124
|
+
const frequencies = {
|
|
125
|
+
hourly: 60 * 60 * 1000, // 1 hour
|
|
126
|
+
daily: 24 * 60 * 60 * 1000, // 24 hours
|
|
127
|
+
weekly: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
128
|
+
never: Infinity,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const maxAge = frequencies[config.checkFrequency] || frequencies.daily;
|
|
132
|
+
|
|
133
|
+
debugLog('Cache check', {
|
|
134
|
+
lastCheck: config.lastCheck,
|
|
135
|
+
ageMs: age,
|
|
136
|
+
maxAgeMs: maxAge,
|
|
137
|
+
valid: age < maxAge,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return age < maxAge;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fetch latest version from npm
|
|
144
|
+
async function fetchLatestVersion() {
|
|
145
|
+
return new Promise(resolve => {
|
|
146
|
+
const options = {
|
|
147
|
+
hostname: 'registry.npmjs.org',
|
|
148
|
+
port: 443,
|
|
149
|
+
path: '/agileflow/latest',
|
|
150
|
+
method: 'GET',
|
|
151
|
+
headers: {
|
|
152
|
+
'User-Agent': 'agileflow-cli',
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
debugLog('Fetching from npm registry');
|
|
157
|
+
|
|
158
|
+
const req = https.request(options, res => {
|
|
159
|
+
let data = '';
|
|
160
|
+
|
|
161
|
+
res.on('data', chunk => {
|
|
162
|
+
data += chunk;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
res.on('end', () => {
|
|
166
|
+
if (res.statusCode !== 200) {
|
|
167
|
+
debugLog('Non-200 status', { statusCode: res.statusCode });
|
|
168
|
+
return resolve(null);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const json = JSON.parse(data);
|
|
173
|
+
debugLog('Version found', { version: json.version });
|
|
174
|
+
resolve(json.version || null);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
debugLog('JSON parse error', err.message);
|
|
177
|
+
resolve(null);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
req.on('error', err => {
|
|
183
|
+
debugLog('Network error', err.message);
|
|
184
|
+
resolve(null);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
req.setTimeout(5000, () => {
|
|
188
|
+
debugLog('Request timeout');
|
|
189
|
+
req.destroy();
|
|
190
|
+
resolve(null);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
req.end();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Compare semantic versions: returns -1 if a < b, 0 if equal, 1 if a > b
|
|
198
|
+
function compareVersions(a, b) {
|
|
199
|
+
if (!a || !b) return 0;
|
|
200
|
+
const partsA = a.split('.').map(Number);
|
|
201
|
+
const partsB = b.split('.').map(Number);
|
|
202
|
+
for (let i = 0; i < 3; i++) {
|
|
203
|
+
const numA = partsA[i] || 0;
|
|
204
|
+
const numB = partsB[i] || 0;
|
|
205
|
+
if (numA < numB) return -1;
|
|
206
|
+
if (numA > numB) return 1;
|
|
207
|
+
}
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Main check function
|
|
212
|
+
async function checkForUpdates(options = {}) {
|
|
213
|
+
const rootDir = getProjectRoot();
|
|
214
|
+
const installedVersion = getInstalledVersion(rootDir);
|
|
215
|
+
const config = getUpdateConfig(rootDir);
|
|
216
|
+
|
|
217
|
+
const result = {
|
|
218
|
+
installed: installedVersion,
|
|
219
|
+
latest: null,
|
|
220
|
+
updateAvailable: false,
|
|
221
|
+
autoUpdate: config.autoUpdate,
|
|
222
|
+
justUpdated: false,
|
|
223
|
+
previousVersion: config.lastSeenVersion,
|
|
224
|
+
fromCache: false,
|
|
225
|
+
error: null,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// No installed version found
|
|
229
|
+
if (!installedVersion) {
|
|
230
|
+
result.error = 'Could not determine installed version';
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check if we just updated (lastSeenVersion < installed)
|
|
235
|
+
if (
|
|
236
|
+
config.lastSeenVersion &&
|
|
237
|
+
compareVersions(config.lastSeenVersion, installedVersion) < 0
|
|
238
|
+
) {
|
|
239
|
+
result.justUpdated = true;
|
|
240
|
+
result.previousVersion = config.lastSeenVersion;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Use cache if valid and not forced
|
|
244
|
+
if (!options.force && isCacheValid(config) && config.latestVersion) {
|
|
245
|
+
result.latest = config.latestVersion;
|
|
246
|
+
result.fromCache = true;
|
|
247
|
+
} else if (config.checkFrequency !== 'never') {
|
|
248
|
+
// Fetch from npm
|
|
249
|
+
result.latest = await fetchLatestVersion();
|
|
250
|
+
|
|
251
|
+
// Update cache
|
|
252
|
+
if (result.latest) {
|
|
253
|
+
config.lastCheck = new Date().toISOString();
|
|
254
|
+
config.latestVersion = result.latest;
|
|
255
|
+
saveUpdateConfig(rootDir, config);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Compare versions
|
|
260
|
+
if (result.latest && compareVersions(installedVersion, result.latest) < 0) {
|
|
261
|
+
result.updateAvailable = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Mark version as seen (after update or dismissal)
|
|
268
|
+
function markVersionSeen(version) {
|
|
269
|
+
const rootDir = getProjectRoot();
|
|
270
|
+
const config = getUpdateConfig(rootDir);
|
|
271
|
+
config.lastSeenVersion = version;
|
|
272
|
+
saveUpdateConfig(rootDir, config);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// CLI interface
|
|
276
|
+
async function main() {
|
|
277
|
+
const args = process.argv.slice(2);
|
|
278
|
+
const force = args.includes('--force');
|
|
279
|
+
const jsonOutput = args.includes('--json');
|
|
280
|
+
const markSeen = args.includes('--mark-seen');
|
|
281
|
+
|
|
282
|
+
if (markSeen) {
|
|
283
|
+
const version = args[args.indexOf('--mark-seen') + 1];
|
|
284
|
+
if (version) {
|
|
285
|
+
markVersionSeen(version);
|
|
286
|
+
if (jsonOutput) {
|
|
287
|
+
console.log(JSON.stringify({ success: true, version }));
|
|
288
|
+
} else {
|
|
289
|
+
console.log(`Marked version ${version} as seen`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const result = await checkForUpdates({ force });
|
|
296
|
+
|
|
297
|
+
if (jsonOutput) {
|
|
298
|
+
console.log(JSON.stringify(result, null, 2));
|
|
299
|
+
} else {
|
|
300
|
+
if (result.error) {
|
|
301
|
+
console.log(`Error: ${result.error}`);
|
|
302
|
+
} else if (result.justUpdated) {
|
|
303
|
+
console.log(`Updated from v${result.previousVersion} to v${result.installed}`);
|
|
304
|
+
} else if (result.updateAvailable) {
|
|
305
|
+
console.log(`Update available: v${result.installed} -> v${result.latest}`);
|
|
306
|
+
console.log(`Run: npx agileflow update`);
|
|
307
|
+
} else {
|
|
308
|
+
console.log(`AgileFlow v${result.installed} is up to date`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Export for use as module
|
|
314
|
+
module.exports = {
|
|
315
|
+
checkForUpdates,
|
|
316
|
+
markVersionSeen,
|
|
317
|
+
getUpdateConfig,
|
|
318
|
+
saveUpdateConfig,
|
|
319
|
+
compareVersions,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Run CLI if executed directly
|
|
323
|
+
if (require.main === module) {
|
|
324
|
+
main().catch(console.error);
|
|
325
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show what's new in AgileFlow
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /agileflow:whats-new
|
|
6
|
+
|
|
7
|
+
Display recent AgileFlow updates and version history.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Prompt
|
|
12
|
+
|
|
13
|
+
ROLE: AgileFlow Version Reporter
|
|
14
|
+
|
|
15
|
+
**STEP 1: Get current version and changelog**
|
|
16
|
+
|
|
17
|
+
Read the AgileFlow CHANGELOG.md:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cat .agileflow/CHANGELOG.md 2>/dev/null || echo "Changelog not found"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Also check the installed version:
|
|
24
|
+
```bash
|
|
25
|
+
cat .agileflow/package.json 2>/dev/null | grep '"version"' || echo "Version unknown"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**STEP 2: Parse and display**
|
|
29
|
+
|
|
30
|
+
Parse the CHANGELOG.md and display:
|
|
31
|
+
|
|
32
|
+
1. **Current installed version** at the top
|
|
33
|
+
2. **Last 3-5 versions** with their changes
|
|
34
|
+
3. Use clear formatting with headers and bullets
|
|
35
|
+
|
|
36
|
+
**FORMAT:**
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
╭─────────────────────────────────────────────────────╮
|
|
40
|
+
│ AgileFlow Changelog │
|
|
41
|
+
│ Current: v2.57.0 │
|
|
42
|
+
╰─────────────────────────────────────────────────────╯
|
|
43
|
+
|
|
44
|
+
## v2.57.0 (2025-12-27)
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
• Auto-update system with configurable check frequency
|
|
48
|
+
• Update notifications in welcome message and status line
|
|
49
|
+
• Changelog display after updates
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## v2.56.0 (2025-12-27)
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
• Dynamic IDE discovery - Codex CLI now in setup
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
• Replaced hardcoded IDE list with dynamic loading
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## v2.55.0 (2025-12-26)
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
• Consolidated code improvements and debugging
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
📖 Full changelog: https://github.com/projectquestorg/AgileFlow/blob/main/packages/cli/CHANGELOG.md
|
|
71
|
+
🔄 Check for updates: npx agileflow update
|
|
72
|
+
⚙️ Configure auto-update: /agileflow:configure --auto-update
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**STEP 3: Check for updates**
|
|
76
|
+
|
|
77
|
+
After displaying, check if an update is available:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm view agileflow version 2>/dev/null
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If a newer version exists, show:
|
|
84
|
+
```
|
|
85
|
+
⚡ Update available: v2.57.0 → v2.58.0
|
|
86
|
+
Run: npx agileflow update
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**RULES:**
|
|
90
|
+
- Show max 5 versions (most recent first)
|
|
91
|
+
- Use clear section headers
|
|
92
|
+
- Include dates if available
|
|
93
|
+
- Always show the GitHub link for full history
|
|
94
|
+
- Mention update availability if applicable
|