agileflow 2.67.0 → 2.69.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 +11 -38
- package/scripts/agileflow-welcome.js +17 -6
- package/src/core/templates/agileflow-configure.js +179 -105
- package/src/core/templates/agileflow-welcome.js +344 -40
- package/tools/cli/commands/update.js +22 -4
- package/scripts/agileflow-stop.sh +0 -13
- package/src/core/templates/agileflow-stop.sh +0 -13
package/package.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* --detect Show current status
|
|
24
24
|
* --help Show help
|
|
25
25
|
*
|
|
26
|
-
* Features: sessionstart, precompact,
|
|
26
|
+
* Features: sessionstart, precompact, archival, statusline, autoupdate
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
const fs = require('fs');
|
|
@@ -39,7 +39,7 @@ const VERSION = '2.41.0';
|
|
|
39
39
|
const FEATURES = {
|
|
40
40
|
sessionstart: { hook: 'SessionStart', script: 'agileflow-welcome.js', type: 'node' },
|
|
41
41
|
precompact: { hook: 'PreCompact', script: 'precompact-context.sh', type: 'bash' },
|
|
42
|
-
|
|
42
|
+
// Note: Stop hook removed due to Claude Code reliability issues (see GitHub issues #6974, #11544)
|
|
43
43
|
archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
|
|
44
44
|
statusline: { script: 'agileflow-statusline.sh' },
|
|
45
45
|
autoupdate: { metadataOnly: true }, // Stored in metadata.updates.autoUpdate
|
|
@@ -60,24 +60,24 @@ const STATUSLINE_COMPONENTS = [
|
|
|
60
60
|
const PROFILES = {
|
|
61
61
|
full: {
|
|
62
62
|
description: 'All features enabled',
|
|
63
|
-
enable: ['sessionstart', 'precompact', '
|
|
63
|
+
enable: ['sessionstart', 'precompact', 'archival', 'statusline'],
|
|
64
64
|
archivalDays: 7,
|
|
65
65
|
},
|
|
66
66
|
basic: {
|
|
67
67
|
description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
|
|
68
68
|
enable: ['sessionstart', 'precompact', 'archival'],
|
|
69
|
-
disable: ['
|
|
69
|
+
disable: ['statusline'],
|
|
70
70
|
archivalDays: 7,
|
|
71
71
|
},
|
|
72
72
|
minimal: {
|
|
73
73
|
description: 'SessionStart + archival only',
|
|
74
74
|
enable: ['sessionstart', 'archival'],
|
|
75
|
-
disable: ['precompact', '
|
|
75
|
+
disable: ['precompact', 'statusline'],
|
|
76
76
|
archivalDays: 7,
|
|
77
77
|
},
|
|
78
78
|
none: {
|
|
79
79
|
description: 'Disable all AgileFlow features',
|
|
80
|
-
disable: ['sessionstart', 'precompact', '
|
|
80
|
+
disable: ['sessionstart', 'precompact', 'archival', 'statusline'],
|
|
81
81
|
},
|
|
82
82
|
};
|
|
83
83
|
|
|
@@ -154,7 +154,6 @@ function detectConfig() {
|
|
|
154
154
|
features: {
|
|
155
155
|
sessionstart: { enabled: false, valid: true, issues: [] },
|
|
156
156
|
precompact: { enabled: false, valid: true, issues: [] },
|
|
157
|
-
stop: { enabled: false, valid: true, issues: [] },
|
|
158
157
|
archival: { enabled: false, threshold: null },
|
|
159
158
|
statusline: { enabled: false, valid: true, issues: [] },
|
|
160
159
|
},
|
|
@@ -221,23 +220,7 @@ function detectConfig() {
|
|
|
221
220
|
}
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
// Stop
|
|
225
|
-
if (settings.hooks.Stop) {
|
|
226
|
-
if (Array.isArray(settings.hooks.Stop) && settings.hooks.Stop.length > 0) {
|
|
227
|
-
const hook = settings.hooks.Stop[0];
|
|
228
|
-
if (hook.matcher !== undefined && hook.hooks) {
|
|
229
|
-
status.features.stop.enabled = true;
|
|
230
|
-
} else {
|
|
231
|
-
status.features.stop.enabled = true;
|
|
232
|
-
status.features.stop.valid = false;
|
|
233
|
-
status.features.stop.issues.push('Old format - needs migration');
|
|
234
|
-
}
|
|
235
|
-
} else if (typeof settings.hooks.Stop === 'string') {
|
|
236
|
-
status.features.stop.enabled = true;
|
|
237
|
-
status.features.stop.valid = false;
|
|
238
|
-
status.features.stop.issues.push('String format - needs migration');
|
|
239
|
-
}
|
|
240
|
-
}
|
|
223
|
+
// Note: Stop hook removed due to reliability issues
|
|
241
224
|
}
|
|
242
225
|
|
|
243
226
|
// StatusLine
|
|
@@ -313,7 +296,6 @@ function printStatus(status) {
|
|
|
313
296
|
|
|
314
297
|
printFeature('sessionstart', 'SessionStart Hook');
|
|
315
298
|
printFeature('precompact', 'PreCompact Hook');
|
|
316
|
-
printFeature('stop', 'Stop Hook');
|
|
317
299
|
|
|
318
300
|
const arch = status.features.archival;
|
|
319
301
|
log(
|
|
@@ -357,9 +339,9 @@ function migrateSettings() {
|
|
|
357
339
|
|
|
358
340
|
let migrated = false;
|
|
359
341
|
|
|
360
|
-
// Migrate hooks
|
|
342
|
+
// Migrate hooks (Stop hook removed due to reliability issues)
|
|
361
343
|
if (settings.hooks) {
|
|
362
|
-
['SessionStart', 'PreCompact', '
|
|
344
|
+
['SessionStart', 'PreCompact', 'UserPromptSubmit'].forEach(hookName => {
|
|
363
345
|
const hook = settings.hooks[hookName];
|
|
364
346
|
if (!hook) return;
|
|
365
347
|
|
|
@@ -472,15 +454,6 @@ function enableFeature(feature, options = {}) {
|
|
|
472
454
|
);
|
|
473
455
|
} else if (feature === 'precompact') {
|
|
474
456
|
fs.writeFileSync(scriptPath, '#!/bin/bash\necho "PreCompact: preserving context"\n');
|
|
475
|
-
} else if (feature === 'stop') {
|
|
476
|
-
fs.writeFileSync(
|
|
477
|
-
scriptPath,
|
|
478
|
-
`#!/bin/bash
|
|
479
|
-
git rev-parse --git-dir > /dev/null 2>&1 || exit 0
|
|
480
|
-
CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
481
|
-
[ "$CHANGES" -gt 0 ] && echo -e "\\n\\033[33m$CHANGES uncommitted change(s)\\033[0m"
|
|
482
|
-
`
|
|
483
|
-
);
|
|
484
457
|
}
|
|
485
458
|
try {
|
|
486
459
|
fs.chmodSync(scriptPath, '755');
|
|
@@ -862,7 +835,7 @@ ${c.cyan}Feature Control:${c.reset}
|
|
|
862
835
|
--enable=<list> Enable features (comma-separated)
|
|
863
836
|
--disable=<list> Disable features (comma-separated)
|
|
864
837
|
|
|
865
|
-
Features: sessionstart, precompact,
|
|
838
|
+
Features: sessionstart, precompact, archival, statusline
|
|
866
839
|
|
|
867
840
|
${c.cyan}Statusline Components:${c.reset}
|
|
868
841
|
--show=<list> Show statusline components (comma-separated)
|
|
@@ -884,7 +857,7 @@ ${c.cyan}Examples:${c.reset}
|
|
|
884
857
|
node scripts/agileflow-configure.js --profile=full
|
|
885
858
|
|
|
886
859
|
# Enable specific features
|
|
887
|
-
node scripts/agileflow-configure.js --enable=sessionstart,precompact,
|
|
860
|
+
node scripts/agileflow-configure.js --enable=sessionstart,precompact,archival
|
|
888
861
|
|
|
889
862
|
# Disable a feature
|
|
890
863
|
node scripts/agileflow-configure.js --disable=statusline
|
|
@@ -86,15 +86,26 @@ function getProjectInfo(rootDir) {
|
|
|
86
86
|
currentStory: null,
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
// Get
|
|
89
|
+
// Get AgileFlow version (check multiple sources in priority order)
|
|
90
|
+
// 1. AgileFlow metadata (installed user projects)
|
|
91
|
+
// 2. packages/cli/package.json (AgileFlow dev project)
|
|
92
|
+
// 3. .agileflow/package.json (fallback)
|
|
90
93
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
95
|
+
if (fs.existsSync(metadataPath)) {
|
|
96
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
97
|
+
info.version = metadata.version || info.version;
|
|
98
|
+
} else {
|
|
99
|
+
// Dev project: check packages/cli/package.json
|
|
100
|
+
const pkg = JSON.parse(
|
|
101
|
+
fs.readFileSync(path.join(rootDir, 'packages/cli/package.json'), 'utf8')
|
|
102
|
+
);
|
|
103
|
+
info.version = pkg.version || info.version;
|
|
104
|
+
}
|
|
95
105
|
} catch (e) {
|
|
106
|
+
// Fallback: check .agileflow/package.json
|
|
96
107
|
try {
|
|
97
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
|
108
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, '.agileflow/package.json'), 'utf8'));
|
|
98
109
|
info.version = pkg.version || info.version;
|
|
99
110
|
} catch (e2) {}
|
|
100
111
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - RECONFIGURE: Change settings (archival days, etc.)
|
|
12
12
|
*
|
|
13
13
|
* Usage:
|
|
14
|
-
* node
|
|
14
|
+
* node scripts/agileflow-configure.js [options]
|
|
15
15
|
*
|
|
16
16
|
* Options:
|
|
17
17
|
* --profile=full|basic|minimal|none Apply a preset
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* --detect Show current status
|
|
24
24
|
* --help Show help
|
|
25
25
|
*
|
|
26
|
-
* Features: sessionstart, precompact,
|
|
26
|
+
* Features: sessionstart, precompact, archival, statusline, autoupdate
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
const fs = require('fs');
|
|
@@ -39,36 +39,46 @@ const VERSION = '2.41.0';
|
|
|
39
39
|
const FEATURES = {
|
|
40
40
|
sessionstart: { hook: 'SessionStart', script: 'agileflow-welcome.js', type: 'node' },
|
|
41
41
|
precompact: { hook: 'PreCompact', script: 'precompact-context.sh', type: 'bash' },
|
|
42
|
-
|
|
42
|
+
// Note: Stop hook removed due to Claude Code reliability issues (see GitHub issues #6974, #11544)
|
|
43
43
|
archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
|
|
44
|
-
statusline: { script: 'agileflow-statusline.sh' }
|
|
44
|
+
statusline: { script: 'agileflow-statusline.sh' },
|
|
45
|
+
autoupdate: { metadataOnly: true }, // Stored in metadata.updates.autoUpdate
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
// Statusline component names
|
|
48
|
-
const STATUSLINE_COMPONENTS = [
|
|
49
|
+
const STATUSLINE_COMPONENTS = [
|
|
50
|
+
'agileflow',
|
|
51
|
+
'model',
|
|
52
|
+
'story',
|
|
53
|
+
'epic',
|
|
54
|
+
'wip',
|
|
55
|
+
'context',
|
|
56
|
+
'cost',
|
|
57
|
+
'git',
|
|
58
|
+
];
|
|
49
59
|
|
|
50
60
|
const PROFILES = {
|
|
51
61
|
full: {
|
|
52
62
|
description: 'All features enabled',
|
|
53
|
-
enable: ['sessionstart', 'precompact', '
|
|
54
|
-
archivalDays: 7
|
|
63
|
+
enable: ['sessionstart', 'precompact', 'archival', 'statusline'],
|
|
64
|
+
archivalDays: 7,
|
|
55
65
|
},
|
|
56
66
|
basic: {
|
|
57
67
|
description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
|
|
58
68
|
enable: ['sessionstart', 'precompact', 'archival'],
|
|
59
|
-
disable: ['
|
|
60
|
-
archivalDays: 7
|
|
69
|
+
disable: ['statusline'],
|
|
70
|
+
archivalDays: 7,
|
|
61
71
|
},
|
|
62
72
|
minimal: {
|
|
63
73
|
description: 'SessionStart + archival only',
|
|
64
74
|
enable: ['sessionstart', 'archival'],
|
|
65
|
-
disable: ['precompact', '
|
|
66
|
-
archivalDays: 7
|
|
75
|
+
disable: ['precompact', 'statusline'],
|
|
76
|
+
archivalDays: 7,
|
|
67
77
|
},
|
|
68
78
|
none: {
|
|
69
79
|
description: 'Disable all AgileFlow features',
|
|
70
|
-
disable: ['sessionstart', 'precompact', '
|
|
71
|
-
}
|
|
80
|
+
disable: ['sessionstart', 'precompact', 'archival', 'statusline'],
|
|
81
|
+
},
|
|
72
82
|
};
|
|
73
83
|
|
|
74
84
|
// ============================================================================
|
|
@@ -76,8 +86,13 @@ const PROFILES = {
|
|
|
76
86
|
// ============================================================================
|
|
77
87
|
|
|
78
88
|
const c = {
|
|
79
|
-
reset: '\x1b[0m',
|
|
80
|
-
|
|
89
|
+
reset: '\x1b[0m',
|
|
90
|
+
dim: '\x1b[2m',
|
|
91
|
+
bold: '\x1b[1m',
|
|
92
|
+
green: '\x1b[32m',
|
|
93
|
+
yellow: '\x1b[33m',
|
|
94
|
+
red: '\x1b[31m',
|
|
95
|
+
cyan: '\x1b[36m',
|
|
81
96
|
};
|
|
82
97
|
|
|
83
98
|
const log = (msg, color = '') => console.log(`${color}${msg}${c.reset}`);
|
|
@@ -91,11 +106,16 @@ const header = msg => log(`\n${msg}`, c.bold + c.cyan);
|
|
|
91
106
|
// FILE UTILITIES
|
|
92
107
|
// ============================================================================
|
|
93
108
|
|
|
94
|
-
const ensureDir = dir => {
|
|
109
|
+
const ensureDir = dir => {
|
|
110
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
111
|
+
};
|
|
95
112
|
|
|
96
113
|
const readJSON = filePath => {
|
|
97
|
-
try {
|
|
98
|
-
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
99
119
|
};
|
|
100
120
|
|
|
101
121
|
const writeJSON = (filePath, data) => {
|
|
@@ -107,12 +127,14 @@ const copyTemplate = (templateName, destPath) => {
|
|
|
107
127
|
const sources = [
|
|
108
128
|
path.join(process.cwd(), '.agileflow', 'templates', templateName),
|
|
109
129
|
path.join(__dirname, templateName),
|
|
110
|
-
path.join(__dirname, '..', 'templates', templateName)
|
|
130
|
+
path.join(__dirname, '..', 'templates', templateName),
|
|
111
131
|
];
|
|
112
132
|
for (const src of sources) {
|
|
113
133
|
if (fs.existsSync(src)) {
|
|
114
134
|
fs.copyFileSync(src, destPath);
|
|
115
|
-
try {
|
|
135
|
+
try {
|
|
136
|
+
fs.chmodSync(destPath, '755');
|
|
137
|
+
} catch {}
|
|
116
138
|
return true;
|
|
117
139
|
}
|
|
118
140
|
}
|
|
@@ -132,18 +154,19 @@ function detectConfig() {
|
|
|
132
154
|
features: {
|
|
133
155
|
sessionstart: { enabled: false, valid: true, issues: [] },
|
|
134
156
|
precompact: { enabled: false, valid: true, issues: [] },
|
|
135
|
-
stop: { enabled: false, valid: true, issues: [] },
|
|
136
157
|
archival: { enabled: false, threshold: null },
|
|
137
|
-
statusline: { enabled: false, valid: true, issues: [] }
|
|
158
|
+
statusline: { enabled: false, valid: true, issues: [] },
|
|
138
159
|
},
|
|
139
|
-
metadata: { exists: false, version: null }
|
|
160
|
+
metadata: { exists: false, version: null },
|
|
140
161
|
};
|
|
141
162
|
|
|
142
163
|
// Git
|
|
143
164
|
if (fs.existsSync('.git')) {
|
|
144
165
|
status.git.initialized = true;
|
|
145
166
|
try {
|
|
146
|
-
status.git.remote = execSync('git remote get-url origin 2>/dev/null', {
|
|
167
|
+
status.git.remote = execSync('git remote get-url origin 2>/dev/null', {
|
|
168
|
+
encoding: 'utf8',
|
|
169
|
+
}).trim();
|
|
147
170
|
} catch {}
|
|
148
171
|
}
|
|
149
172
|
|
|
@@ -160,7 +183,10 @@ function detectConfig() {
|
|
|
160
183
|
if (settings.hooks) {
|
|
161
184
|
// SessionStart
|
|
162
185
|
if (settings.hooks.SessionStart) {
|
|
163
|
-
if (
|
|
186
|
+
if (
|
|
187
|
+
Array.isArray(settings.hooks.SessionStart) &&
|
|
188
|
+
settings.hooks.SessionStart.length > 0
|
|
189
|
+
) {
|
|
164
190
|
const hook = settings.hooks.SessionStart[0];
|
|
165
191
|
if (hook.matcher !== undefined && hook.hooks) {
|
|
166
192
|
status.features.sessionstart.enabled = true;
|
|
@@ -194,23 +220,7 @@ function detectConfig() {
|
|
|
194
220
|
}
|
|
195
221
|
}
|
|
196
222
|
|
|
197
|
-
// Stop
|
|
198
|
-
if (settings.hooks.Stop) {
|
|
199
|
-
if (Array.isArray(settings.hooks.Stop) && settings.hooks.Stop.length > 0) {
|
|
200
|
-
const hook = settings.hooks.Stop[0];
|
|
201
|
-
if (hook.matcher !== undefined && hook.hooks) {
|
|
202
|
-
status.features.stop.enabled = true;
|
|
203
|
-
} else {
|
|
204
|
-
status.features.stop.enabled = true;
|
|
205
|
-
status.features.stop.valid = false;
|
|
206
|
-
status.features.stop.issues.push('Old format - needs migration');
|
|
207
|
-
}
|
|
208
|
-
} else if (typeof settings.hooks.Stop === 'string') {
|
|
209
|
-
status.features.stop.enabled = true;
|
|
210
|
-
status.features.stop.valid = false;
|
|
211
|
-
status.features.stop.issues.push('String format - needs migration');
|
|
212
|
-
}
|
|
213
|
-
}
|
|
223
|
+
// Note: Stop hook removed due to reliability issues
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
// StatusLine
|
|
@@ -248,8 +258,10 @@ function printStatus(status) {
|
|
|
248
258
|
header('📊 Current Configuration');
|
|
249
259
|
|
|
250
260
|
// Git
|
|
251
|
-
log(
|
|
252
|
-
status.git.initialized ?
|
|
261
|
+
log(
|
|
262
|
+
`Git: ${status.git.initialized ? '✅' : '❌'} ${status.git.initialized ? 'initialized' : 'not initialized'}${status.git.remote ? ` (${status.git.remote})` : ''}`,
|
|
263
|
+
status.git.initialized ? c.green : c.dim
|
|
264
|
+
);
|
|
253
265
|
|
|
254
266
|
// Settings
|
|
255
267
|
if (!status.settingsExists) {
|
|
@@ -284,11 +296,12 @@ function printStatus(status) {
|
|
|
284
296
|
|
|
285
297
|
printFeature('sessionstart', 'SessionStart Hook');
|
|
286
298
|
printFeature('precompact', 'PreCompact Hook');
|
|
287
|
-
printFeature('stop', 'Stop Hook');
|
|
288
299
|
|
|
289
300
|
const arch = status.features.archival;
|
|
290
|
-
log(
|
|
291
|
-
arch.enabled ?
|
|
301
|
+
log(
|
|
302
|
+
` ${arch.enabled ? '✅' : '❌'} Archival: ${arch.enabled ? `${arch.threshold} days` : 'disabled'}`,
|
|
303
|
+
arch.enabled ? c.green : c.dim
|
|
304
|
+
);
|
|
292
305
|
|
|
293
306
|
printFeature('statusline', 'Status Line');
|
|
294
307
|
|
|
@@ -326,19 +339,21 @@ function migrateSettings() {
|
|
|
326
339
|
|
|
327
340
|
let migrated = false;
|
|
328
341
|
|
|
329
|
-
// Migrate hooks
|
|
342
|
+
// Migrate hooks (Stop hook removed due to reliability issues)
|
|
330
343
|
if (settings.hooks) {
|
|
331
|
-
['SessionStart', 'PreCompact', '
|
|
344
|
+
['SessionStart', 'PreCompact', 'UserPromptSubmit'].forEach(hookName => {
|
|
332
345
|
const hook = settings.hooks[hookName];
|
|
333
346
|
if (!hook) return;
|
|
334
347
|
|
|
335
348
|
// String format → array format
|
|
336
349
|
if (typeof hook === 'string') {
|
|
337
350
|
const isNode = hook.includes('node ') || hook.endsWith('.js');
|
|
338
|
-
settings.hooks[hookName] = [
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
351
|
+
settings.hooks[hookName] = [
|
|
352
|
+
{
|
|
353
|
+
matcher: '',
|
|
354
|
+
hooks: [{ type: 'command', command: isNode ? hook : `bash ${hook}` }],
|
|
355
|
+
},
|
|
356
|
+
];
|
|
342
357
|
success(`Migrated ${hookName} from string format`);
|
|
343
358
|
migrated = true;
|
|
344
359
|
}
|
|
@@ -348,19 +363,23 @@ function migrateSettings() {
|
|
|
348
363
|
if (first.enabled !== undefined || first.command !== undefined) {
|
|
349
364
|
// Old format with enabled/command
|
|
350
365
|
if (first.command) {
|
|
351
|
-
settings.hooks[hookName] = [
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
366
|
+
settings.hooks[hookName] = [
|
|
367
|
+
{
|
|
368
|
+
matcher: '',
|
|
369
|
+
hooks: [{ type: 'command', command: first.command }],
|
|
370
|
+
},
|
|
371
|
+
];
|
|
355
372
|
success(`Migrated ${hookName} from old object format`);
|
|
356
373
|
migrated = true;
|
|
357
374
|
}
|
|
358
375
|
} else if (first.matcher === undefined) {
|
|
359
376
|
// Missing matcher
|
|
360
|
-
settings.hooks[hookName] = [
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
377
|
+
settings.hooks[hookName] = [
|
|
378
|
+
{
|
|
379
|
+
matcher: '',
|
|
380
|
+
hooks: first.hooks || [{ type: 'command', command: 'echo "hook"' }],
|
|
381
|
+
},
|
|
382
|
+
];
|
|
364
383
|
success(`Migrated ${hookName} - added matcher`);
|
|
365
384
|
migrated = true;
|
|
366
385
|
}
|
|
@@ -374,7 +393,7 @@ function migrateSettings() {
|
|
|
374
393
|
settings.statusLine = {
|
|
375
394
|
type: 'command',
|
|
376
395
|
command: settings.statusLine,
|
|
377
|
-
padding: 0
|
|
396
|
+
padding: 0,
|
|
378
397
|
};
|
|
379
398
|
success('Migrated statusLine from string format');
|
|
380
399
|
migrated = true;
|
|
@@ -417,7 +436,7 @@ function enableFeature(feature, options = {}) {
|
|
|
417
436
|
ensureDir('.claude');
|
|
418
437
|
ensureDir('scripts');
|
|
419
438
|
|
|
420
|
-
|
|
439
|
+
const settings = readJSON('.claude/settings.json') || {};
|
|
421
440
|
settings.hooks = settings.hooks || {};
|
|
422
441
|
settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
|
|
423
442
|
|
|
@@ -429,31 +448,30 @@ function enableFeature(feature, options = {}) {
|
|
|
429
448
|
if (!copyTemplate(config.script, scriptPath)) {
|
|
430
449
|
// Create minimal version
|
|
431
450
|
if (feature === 'sessionstart') {
|
|
432
|
-
fs.writeFileSync(
|
|
451
|
+
fs.writeFileSync(
|
|
452
|
+
scriptPath,
|
|
453
|
+
`#!/usr/bin/env node\nconsole.log('AgileFlow v${VERSION} loaded');\n`
|
|
454
|
+
);
|
|
433
455
|
} else if (feature === 'precompact') {
|
|
434
456
|
fs.writeFileSync(scriptPath, '#!/bin/bash\necho "PreCompact: preserving context"\n');
|
|
435
|
-
} else if (feature === 'stop') {
|
|
436
|
-
fs.writeFileSync(scriptPath, `#!/bin/bash
|
|
437
|
-
git rev-parse --git-dir > /dev/null 2>&1 || exit 0
|
|
438
|
-
CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
439
|
-
[ "$CHANGES" -gt 0 ] && echo -e "\\n\\033[33m$CHANGES uncommitted change(s)\\033[0m"
|
|
440
|
-
`);
|
|
441
457
|
}
|
|
442
|
-
try {
|
|
458
|
+
try {
|
|
459
|
+
fs.chmodSync(scriptPath, '755');
|
|
460
|
+
} catch {}
|
|
443
461
|
warn(`Created minimal ${config.script}`);
|
|
444
462
|
} else {
|
|
445
463
|
success(`Deployed ${config.script}`);
|
|
446
464
|
}
|
|
447
465
|
|
|
448
466
|
// Configure hook
|
|
449
|
-
const command = config.type === 'node'
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
467
|
+
const command = config.type === 'node' ? `node ${scriptPath}` : `bash ${scriptPath}`;
|
|
468
|
+
|
|
469
|
+
settings.hooks[config.hook] = [
|
|
470
|
+
{
|
|
471
|
+
matcher: '',
|
|
472
|
+
hooks: [{ type: 'command', command }],
|
|
473
|
+
},
|
|
474
|
+
];
|
|
457
475
|
success(`${config.hook} hook enabled`);
|
|
458
476
|
}
|
|
459
477
|
|
|
@@ -470,13 +488,13 @@ CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
|
470
488
|
|
|
471
489
|
// Add to SessionStart hook
|
|
472
490
|
if (settings.hooks.SessionStart?.[0]?.hooks) {
|
|
473
|
-
const hasArchival = settings.hooks.SessionStart[0].hooks.some(
|
|
474
|
-
h
|
|
491
|
+
const hasArchival = settings.hooks.SessionStart[0].hooks.some(h =>
|
|
492
|
+
h.command?.includes('archive-completed-stories')
|
|
475
493
|
);
|
|
476
494
|
if (!hasArchival) {
|
|
477
495
|
settings.hooks.SessionStart[0].hooks.push({
|
|
478
496
|
type: 'command',
|
|
479
|
-
command: 'bash
|
|
497
|
+
command: 'bash scripts/archive-completed-stories.sh --quiet',
|
|
480
498
|
});
|
|
481
499
|
}
|
|
482
500
|
}
|
|
@@ -491,12 +509,17 @@ CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
|
491
509
|
const scriptPath = 'scripts/agileflow-statusline.sh';
|
|
492
510
|
|
|
493
511
|
if (!copyTemplate('agileflow-statusline.sh', scriptPath)) {
|
|
494
|
-
fs.writeFileSync(
|
|
512
|
+
fs.writeFileSync(
|
|
513
|
+
scriptPath,
|
|
514
|
+
`#!/bin/bash
|
|
495
515
|
input=$(cat)
|
|
496
516
|
MODEL=$(echo "$input" | jq -r '.model.display_name // "Claude"')
|
|
497
517
|
echo "[$MODEL] AgileFlow"
|
|
498
|
-
`
|
|
499
|
-
|
|
518
|
+
`
|
|
519
|
+
);
|
|
520
|
+
try {
|
|
521
|
+
fs.chmodSync(scriptPath, '755');
|
|
522
|
+
} catch {}
|
|
500
523
|
warn('Created minimal statusline script');
|
|
501
524
|
} else {
|
|
502
525
|
success('Deployed agileflow-statusline.sh');
|
|
@@ -504,14 +527,31 @@ echo "[$MODEL] AgileFlow"
|
|
|
504
527
|
|
|
505
528
|
settings.statusLine = {
|
|
506
529
|
type: 'command',
|
|
507
|
-
command: 'bash
|
|
508
|
-
padding: 0
|
|
530
|
+
command: 'bash scripts/agileflow-statusline.sh',
|
|
531
|
+
padding: 0,
|
|
509
532
|
};
|
|
510
533
|
success('Status line enabled');
|
|
511
534
|
}
|
|
512
535
|
|
|
536
|
+
// Handle autoupdate (metadata only, no hooks needed)
|
|
537
|
+
if (feature === 'autoupdate') {
|
|
538
|
+
const frequency = options.checkFrequency || 'daily';
|
|
539
|
+
updateMetadata({
|
|
540
|
+
updates: {
|
|
541
|
+
autoUpdate: true,
|
|
542
|
+
checkFrequency: frequency,
|
|
543
|
+
showChangelog: true,
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
success(`Auto-update enabled (check frequency: ${frequency})`);
|
|
547
|
+
info('AgileFlow will automatically update on session start');
|
|
548
|
+
return true; // Skip settings.json write for this feature
|
|
549
|
+
}
|
|
550
|
+
|
|
513
551
|
writeJSON('.claude/settings.json', settings);
|
|
514
|
-
updateMetadata({
|
|
552
|
+
updateMetadata({
|
|
553
|
+
features: { [feature]: { enabled: true, version: VERSION, at: new Date().toISOString() } },
|
|
554
|
+
});
|
|
515
555
|
updateGitignore();
|
|
516
556
|
|
|
517
557
|
return true;
|
|
@@ -556,8 +596,21 @@ function disableFeature(feature) {
|
|
|
556
596
|
success('Status line disabled');
|
|
557
597
|
}
|
|
558
598
|
|
|
599
|
+
// Disable autoupdate
|
|
600
|
+
if (feature === 'autoupdate') {
|
|
601
|
+
updateMetadata({
|
|
602
|
+
updates: {
|
|
603
|
+
autoUpdate: false,
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
success('Auto-update disabled');
|
|
607
|
+
return true; // Skip settings.json write for this feature
|
|
608
|
+
}
|
|
609
|
+
|
|
559
610
|
writeJSON('.claude/settings.json', settings);
|
|
560
|
-
updateMetadata({
|
|
611
|
+
updateMetadata({
|
|
612
|
+
features: { [feature]: { enabled: false, version: VERSION, at: new Date().toISOString() } },
|
|
613
|
+
});
|
|
561
614
|
|
|
562
615
|
return true;
|
|
563
616
|
}
|
|
@@ -586,6 +639,9 @@ function updateMetadata(updates) {
|
|
|
586
639
|
meta.features[key] = { ...meta.features[key], ...value };
|
|
587
640
|
});
|
|
588
641
|
}
|
|
642
|
+
if (updates.updates) {
|
|
643
|
+
meta.updates = { ...meta.updates, ...updates.updates };
|
|
644
|
+
}
|
|
589
645
|
|
|
590
646
|
meta.version = VERSION;
|
|
591
647
|
meta.updated = new Date().toISOString();
|
|
@@ -600,7 +656,7 @@ function updateGitignore() {
|
|
|
600
656
|
'.claude/context.log',
|
|
601
657
|
'.claude/hook.log',
|
|
602
658
|
'.claude/prompt-log.txt',
|
|
603
|
-
'.claude/session.log'
|
|
659
|
+
'.claude/session.log',
|
|
604
660
|
];
|
|
605
661
|
|
|
606
662
|
let content = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
|
|
@@ -718,7 +774,9 @@ function applyProfile(profileName, options = {}) {
|
|
|
718
774
|
|
|
719
775
|
// Enable features
|
|
720
776
|
if (profile.enable) {
|
|
721
|
-
profile.enable.forEach(f =>
|
|
777
|
+
profile.enable.forEach(f =>
|
|
778
|
+
enableFeature(f, { archivalDays: profile.archivalDays || options.archivalDays })
|
|
779
|
+
);
|
|
722
780
|
}
|
|
723
781
|
|
|
724
782
|
// Disable features
|
|
@@ -765,7 +823,7 @@ function printHelp() {
|
|
|
765
823
|
${c.bold}AgileFlow Configure${c.reset} - Manage AgileFlow features
|
|
766
824
|
|
|
767
825
|
${c.cyan}Usage:${c.reset}
|
|
768
|
-
node
|
|
826
|
+
node scripts/agileflow-configure.js [options]
|
|
769
827
|
|
|
770
828
|
${c.cyan}Profiles:${c.reset}
|
|
771
829
|
--profile=full All features (hooks, archival, statusline)
|
|
@@ -777,7 +835,7 @@ ${c.cyan}Feature Control:${c.reset}
|
|
|
777
835
|
--enable=<list> Enable features (comma-separated)
|
|
778
836
|
--disable=<list> Disable features (comma-separated)
|
|
779
837
|
|
|
780
|
-
Features: sessionstart, precompact,
|
|
838
|
+
Features: sessionstart, precompact, archival, statusline
|
|
781
839
|
|
|
782
840
|
${c.cyan}Statusline Components:${c.reset}
|
|
783
841
|
--show=<list> Show statusline components (comma-separated)
|
|
@@ -796,28 +854,28 @@ ${c.cyan}Maintenance:${c.reset}
|
|
|
796
854
|
|
|
797
855
|
${c.cyan}Examples:${c.reset}
|
|
798
856
|
# Quick setup with all features
|
|
799
|
-
node
|
|
857
|
+
node scripts/agileflow-configure.js --profile=full
|
|
800
858
|
|
|
801
859
|
# Enable specific features
|
|
802
|
-
node
|
|
860
|
+
node scripts/agileflow-configure.js --enable=sessionstart,precompact,archival
|
|
803
861
|
|
|
804
862
|
# Disable a feature
|
|
805
|
-
node
|
|
863
|
+
node scripts/agileflow-configure.js --disable=statusline
|
|
806
864
|
|
|
807
865
|
# Show only agileflow branding and context in statusline
|
|
808
|
-
node
|
|
866
|
+
node scripts/agileflow-configure.js --hide=model,story,epic,wip,cost,git
|
|
809
867
|
|
|
810
868
|
# Re-enable git branch in statusline
|
|
811
|
-
node
|
|
869
|
+
node scripts/agileflow-configure.js --show=git
|
|
812
870
|
|
|
813
871
|
# List component status
|
|
814
|
-
node
|
|
872
|
+
node scripts/agileflow-configure.js --components
|
|
815
873
|
|
|
816
874
|
# Fix format issues
|
|
817
|
-
node
|
|
875
|
+
node scripts/agileflow-configure.js --migrate
|
|
818
876
|
|
|
819
877
|
# Check current status
|
|
820
|
-
node
|
|
878
|
+
node scripts/agileflow-configure.js --detect
|
|
821
879
|
`);
|
|
822
880
|
}
|
|
823
881
|
|
|
@@ -842,10 +900,26 @@ function main() {
|
|
|
842
900
|
|
|
843
901
|
args.forEach(arg => {
|
|
844
902
|
if (arg.startsWith('--profile=')) profile = arg.split('=')[1];
|
|
845
|
-
else if (arg.startsWith('--enable='))
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
903
|
+
else if (arg.startsWith('--enable='))
|
|
904
|
+
enable = arg
|
|
905
|
+
.split('=')[1]
|
|
906
|
+
.split(',')
|
|
907
|
+
.map(s => s.trim().toLowerCase());
|
|
908
|
+
else if (arg.startsWith('--disable='))
|
|
909
|
+
disable = arg
|
|
910
|
+
.split('=')[1]
|
|
911
|
+
.split(',')
|
|
912
|
+
.map(s => s.trim().toLowerCase());
|
|
913
|
+
else if (arg.startsWith('--show='))
|
|
914
|
+
show = arg
|
|
915
|
+
.split('=')[1]
|
|
916
|
+
.split(',')
|
|
917
|
+
.map(s => s.trim().toLowerCase());
|
|
918
|
+
else if (arg.startsWith('--hide='))
|
|
919
|
+
hide = arg
|
|
920
|
+
.split('=')[1]
|
|
921
|
+
.split(',')
|
|
922
|
+
.map(s => s.trim().toLowerCase());
|
|
849
923
|
else if (arg.startsWith('--archival-days=')) archivalDays = parseInt(arg.split('=')[1]) || 7;
|
|
850
924
|
else if (arg === '--migrate') migrate = true;
|
|
851
925
|
else if (arg === '--detect' || arg === '--validate') detect = true;
|
|
@@ -13,7 +13,18 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const { execSync } = require('child_process');
|
|
16
|
+
const { execSync, spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
// Session manager path (relative to script location)
|
|
19
|
+
const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
|
|
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
|
+
}
|
|
17
28
|
|
|
18
29
|
// ANSI color codes
|
|
19
30
|
const c = {
|
|
@@ -39,9 +50,16 @@ const c = {
|
|
|
39
50
|
|
|
40
51
|
// Box drawing characters
|
|
41
52
|
const box = {
|
|
42
|
-
tl: '╭',
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
tl: '╭',
|
|
54
|
+
tr: '╮',
|
|
55
|
+
bl: '╰',
|
|
56
|
+
br: '╯',
|
|
57
|
+
h: '─',
|
|
58
|
+
v: '│',
|
|
59
|
+
lT: '├',
|
|
60
|
+
rT: '┤',
|
|
61
|
+
tT: '┬',
|
|
62
|
+
bT: '┴',
|
|
45
63
|
cross: '┼',
|
|
46
64
|
};
|
|
47
65
|
|
|
@@ -68,13 +86,26 @@ function getProjectInfo(rootDir) {
|
|
|
68
86
|
currentStory: null,
|
|
69
87
|
};
|
|
70
88
|
|
|
71
|
-
// Get
|
|
89
|
+
// Get AgileFlow version (check multiple sources in priority order)
|
|
90
|
+
// 1. AgileFlow metadata (installed user projects)
|
|
91
|
+
// 2. packages/cli/package.json (AgileFlow dev project)
|
|
92
|
+
// 3. .agileflow/package.json (fallback)
|
|
72
93
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
94
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
95
|
+
if (fs.existsSync(metadataPath)) {
|
|
96
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
97
|
+
info.version = metadata.version || info.version;
|
|
98
|
+
} else {
|
|
99
|
+
// Dev project: check packages/cli/package.json
|
|
100
|
+
const pkg = JSON.parse(
|
|
101
|
+
fs.readFileSync(path.join(rootDir, 'packages/cli/package.json'), 'utf8')
|
|
102
|
+
);
|
|
103
|
+
info.version = pkg.version || info.version;
|
|
104
|
+
}
|
|
75
105
|
} catch (e) {
|
|
106
|
+
// Fallback: check .agileflow/package.json
|
|
76
107
|
try {
|
|
77
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
|
108
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, '.agileflow/package.json'), 'utf8'));
|
|
78
109
|
info.version = pkg.version || info.version;
|
|
79
110
|
} catch (e2) {}
|
|
80
111
|
}
|
|
@@ -83,7 +114,10 @@ function getProjectInfo(rootDir) {
|
|
|
83
114
|
try {
|
|
84
115
|
info.branch = execSync('git branch --show-current', { cwd: rootDir, encoding: 'utf8' }).trim();
|
|
85
116
|
info.commit = execSync('git rev-parse --short HEAD', { cwd: rootDir, encoding: 'utf8' }).trim();
|
|
86
|
-
info.lastCommit = execSync('git log -1 --format="%s"', {
|
|
117
|
+
info.lastCommit = execSync('git log -1 --format="%s"', {
|
|
118
|
+
cwd: rootDir,
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
}).trim();
|
|
87
121
|
} catch (e) {}
|
|
88
122
|
|
|
89
123
|
// Get status info
|
|
@@ -152,10 +186,10 @@ function runArchival(rootDir) {
|
|
|
152
186
|
if (toArchiveCount > 0) {
|
|
153
187
|
// Run archival
|
|
154
188
|
try {
|
|
155
|
-
execSync('bash
|
|
189
|
+
execSync('bash scripts/archive-completed-stories.sh', {
|
|
156
190
|
cwd: rootDir,
|
|
157
191
|
encoding: 'utf8',
|
|
158
|
-
stdio: 'pipe'
|
|
192
|
+
stdio: 'pipe',
|
|
159
193
|
});
|
|
160
194
|
result.archived = toArchiveCount;
|
|
161
195
|
result.remaining -= toArchiveCount;
|
|
@@ -201,6 +235,60 @@ function clearActiveCommands(rootDir) {
|
|
|
201
235
|
return result;
|
|
202
236
|
}
|
|
203
237
|
|
|
238
|
+
function checkParallelSessions(rootDir) {
|
|
239
|
+
const result = {
|
|
240
|
+
available: false,
|
|
241
|
+
registered: false,
|
|
242
|
+
otherActive: 0,
|
|
243
|
+
currentId: null,
|
|
244
|
+
cleaned: 0,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
// Check if session manager exists
|
|
249
|
+
const managerPath = path.join(rootDir, '.agileflow', 'scripts', 'session-manager.js');
|
|
250
|
+
if (!fs.existsSync(managerPath) && !fs.existsSync(SESSION_MANAGER_PATH)) {
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
result.available = true;
|
|
255
|
+
|
|
256
|
+
// Try to register current session and get status
|
|
257
|
+
const scriptPath = fs.existsSync(managerPath) ? managerPath : SESSION_MANAGER_PATH;
|
|
258
|
+
|
|
259
|
+
// Register this session
|
|
260
|
+
try {
|
|
261
|
+
const registerOutput = execSync(`node "${scriptPath}" register`, {
|
|
262
|
+
cwd: rootDir,
|
|
263
|
+
encoding: 'utf8',
|
|
264
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
265
|
+
});
|
|
266
|
+
const registerData = JSON.parse(registerOutput);
|
|
267
|
+
result.registered = true;
|
|
268
|
+
result.currentId = registerData.id;
|
|
269
|
+
} catch (e) {
|
|
270
|
+
// Registration failed, continue anyway
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Get count of other active sessions
|
|
274
|
+
try {
|
|
275
|
+
const countOutput = execSync(`node "${scriptPath}" count`, {
|
|
276
|
+
cwd: rootDir,
|
|
277
|
+
encoding: 'utf8',
|
|
278
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
279
|
+
});
|
|
280
|
+
const countData = JSON.parse(countOutput);
|
|
281
|
+
result.otherActive = countData.count || 0;
|
|
282
|
+
} catch (e) {
|
|
283
|
+
// Count failed
|
|
284
|
+
}
|
|
285
|
+
} catch (e) {
|
|
286
|
+
// Session system not available
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
204
292
|
function checkPreCompact(rootDir) {
|
|
205
293
|
const result = { configured: false, scriptExists: false, version: null, outdated: false };
|
|
206
294
|
|
|
@@ -253,12 +341,104 @@ function compareVersions(a, b) {
|
|
|
253
341
|
return 0;
|
|
254
342
|
}
|
|
255
343
|
|
|
344
|
+
// Check for updates (async but we'll use sync approach for welcome)
|
|
345
|
+
async function checkUpdates() {
|
|
346
|
+
const result = {
|
|
347
|
+
available: false,
|
|
348
|
+
installed: null,
|
|
349
|
+
latest: null,
|
|
350
|
+
justUpdated: false,
|
|
351
|
+
previousVersion: null,
|
|
352
|
+
autoUpdate: false,
|
|
353
|
+
changelog: [],
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
if (!updateChecker) return result;
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
const updateInfo = await updateChecker.checkForUpdates();
|
|
360
|
+
result.installed = updateInfo.installed;
|
|
361
|
+
result.latest = updateInfo.latest;
|
|
362
|
+
result.available = updateInfo.updateAvailable;
|
|
363
|
+
result.justUpdated = updateInfo.justUpdated;
|
|
364
|
+
result.previousVersion = updateInfo.previousVersion;
|
|
365
|
+
result.autoUpdate = updateInfo.autoUpdate;
|
|
366
|
+
|
|
367
|
+
// If just updated, try to get changelog entries
|
|
368
|
+
if (result.justUpdated && result.installed) {
|
|
369
|
+
result.changelog = getChangelogEntries(result.installed);
|
|
370
|
+
}
|
|
371
|
+
} catch (e) {
|
|
372
|
+
// Silently fail - update check is non-critical
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Parse CHANGELOG.md for entries of a specific version
|
|
379
|
+
function getChangelogEntries(version) {
|
|
380
|
+
const entries = [];
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
// Look for CHANGELOG.md in .agileflow or package location
|
|
384
|
+
const possiblePaths = [
|
|
385
|
+
path.join(__dirname, '..', 'CHANGELOG.md'),
|
|
386
|
+
path.join(__dirname, 'CHANGELOG.md'),
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
let changelogContent = null;
|
|
390
|
+
for (const p of possiblePaths) {
|
|
391
|
+
if (fs.existsSync(p)) {
|
|
392
|
+
changelogContent = fs.readFileSync(p, 'utf8');
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!changelogContent) return entries;
|
|
398
|
+
|
|
399
|
+
// Find the section for this version
|
|
400
|
+
const versionPattern = new RegExp(`## \\[${version}\\].*?\\n([\\s\\S]*?)(?=## \\[|$)`);
|
|
401
|
+
const match = changelogContent.match(versionPattern);
|
|
402
|
+
|
|
403
|
+
if (match) {
|
|
404
|
+
// Extract bullet points from Added/Changed/Fixed sections
|
|
405
|
+
const lines = match[1].split('\n');
|
|
406
|
+
for (const line of lines) {
|
|
407
|
+
const bulletMatch = line.match(/^- (.+)$/);
|
|
408
|
+
if (bulletMatch && entries.length < 3) {
|
|
409
|
+
entries.push(bulletMatch[1]);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} catch (e) {
|
|
414
|
+
// Silently fail
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return entries;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Run auto-update if enabled
|
|
421
|
+
async function runAutoUpdate(rootDir) {
|
|
422
|
+
try {
|
|
423
|
+
console.log(`${c.cyan}Updating AgileFlow...${c.reset}`);
|
|
424
|
+
execSync('npx agileflow update', {
|
|
425
|
+
cwd: rootDir,
|
|
426
|
+
encoding: 'utf8',
|
|
427
|
+
stdio: 'inherit',
|
|
428
|
+
});
|
|
429
|
+
return true;
|
|
430
|
+
} catch (e) {
|
|
431
|
+
console.log(`${c.yellow}Auto-update failed. Run manually: npx agileflow update${c.reset}`);
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
256
436
|
function getFeatureVersions(rootDir) {
|
|
257
437
|
const result = {
|
|
258
438
|
hooks: { version: null, outdated: false },
|
|
259
439
|
archival: { version: null, outdated: false },
|
|
260
440
|
statusline: { version: null, outdated: false },
|
|
261
|
-
precompact: { version: null, outdated: false }
|
|
441
|
+
precompact: { version: null, outdated: false },
|
|
262
442
|
};
|
|
263
443
|
|
|
264
444
|
// Minimum compatible versions for each feature
|
|
@@ -266,7 +446,7 @@ function getFeatureVersions(rootDir) {
|
|
|
266
446
|
hooks: '2.35.0',
|
|
267
447
|
archival: '2.35.0',
|
|
268
448
|
statusline: '2.35.0',
|
|
269
|
-
precompact: '2.40.0'
|
|
449
|
+
precompact: '2.40.0', // Multi-command support
|
|
270
450
|
};
|
|
271
451
|
|
|
272
452
|
try {
|
|
@@ -277,7 +457,8 @@ function getFeatureVersions(rootDir) {
|
|
|
277
457
|
for (const feature of Object.keys(result)) {
|
|
278
458
|
if (metadata.features?.[feature]?.configured_version) {
|
|
279
459
|
result[feature].version = metadata.features[feature].configured_version;
|
|
280
|
-
result[feature].outdated =
|
|
460
|
+
result[feature].outdated =
|
|
461
|
+
compareVersions(result[feature].version, minVersions[feature]) < 0;
|
|
281
462
|
}
|
|
282
463
|
}
|
|
283
464
|
}
|
|
@@ -291,7 +472,8 @@ function pad(str, len, align = 'left') {
|
|
|
291
472
|
const diff = len - stripped.length;
|
|
292
473
|
if (diff <= 0) return str;
|
|
293
474
|
if (align === 'right') return ' '.repeat(diff) + str;
|
|
294
|
-
if (align === 'center')
|
|
475
|
+
if (align === 'center')
|
|
476
|
+
return ' '.repeat(Math.floor(diff / 2)) + str + ' '.repeat(Math.ceil(diff / 2));
|
|
295
477
|
return str + ' '.repeat(diff);
|
|
296
478
|
}
|
|
297
479
|
|
|
@@ -323,7 +505,7 @@ function truncate(str, maxLen, suffix = '..') {
|
|
|
323
505
|
return str.substring(0, cutIndex) + suffix;
|
|
324
506
|
}
|
|
325
507
|
|
|
326
|
-
function formatTable(info, archival, session, precompact) {
|
|
508
|
+
function formatTable(info, archival, session, precompact, parallelSessions, updateInfo = {}) {
|
|
327
509
|
const W = 58; // inner width
|
|
328
510
|
const R = W - 24; // right column width (34 chars)
|
|
329
511
|
const lines = [];
|
|
@@ -336,29 +518,101 @@ function formatTable(info, archival, session, precompact) {
|
|
|
336
518
|
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}`;
|
|
337
519
|
};
|
|
338
520
|
|
|
339
|
-
|
|
521
|
+
// Helper for full-width row (spans both columns)
|
|
522
|
+
const fullRow = (content, color = '') => {
|
|
523
|
+
const contentStr = `${color}${content}${color ? c.reset : ''}`;
|
|
524
|
+
return `${c.dim}${box.v}${c.reset} ${pad(contentStr, W - 1)} ${c.dim}${box.v}${c.reset}`;
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const divider = () =>
|
|
528
|
+
`${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 22)}${box.rT}${c.reset}`;
|
|
529
|
+
const fullDivider = () =>
|
|
530
|
+
`${c.dim}${box.lT}${box.h.repeat(W)}${box.rT}${c.reset}`;
|
|
340
531
|
const topBorder = `${c.dim}${box.tl}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 22)}${box.tr}${c.reset}`;
|
|
341
532
|
const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 22)}${box.br}${c.reset}`;
|
|
342
533
|
|
|
343
|
-
// Header
|
|
344
|
-
const branchColor =
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
534
|
+
// Header with version and optional update indicator
|
|
535
|
+
const branchColor =
|
|
536
|
+
info.branch === 'main' ? c.green : info.branch.startsWith('fix') ? c.red : c.cyan;
|
|
537
|
+
|
|
538
|
+
// Build version string with update status
|
|
539
|
+
let versionStr = `v${info.version}`;
|
|
540
|
+
if (updateInfo.justUpdated && updateInfo.previousVersion) {
|
|
541
|
+
versionStr = `v${info.version} ${c.green}✓${c.reset}${c.dim} (was v${updateInfo.previousVersion})`;
|
|
542
|
+
} else if (updateInfo.available && updateInfo.latest) {
|
|
543
|
+
versionStr = `v${info.version} ${c.yellow}↑${updateInfo.latest}${c.reset}`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Calculate remaining space for branch
|
|
547
|
+
const versionVisibleLen = updateInfo.justUpdated
|
|
548
|
+
? info.version.length + 20 + (updateInfo.previousVersion?.length || 0)
|
|
549
|
+
: updateInfo.available
|
|
550
|
+
? info.version.length + 3 + (updateInfo.latest?.length || 0)
|
|
551
|
+
: info.version.length;
|
|
552
|
+
const maxBranchLen = W - 1 - 15 - versionVisibleLen;
|
|
553
|
+
const branchDisplay =
|
|
554
|
+
info.branch.length > maxBranchLen
|
|
555
|
+
? info.branch.substring(0, Math.max(5, maxBranchLen - 2)) + '..'
|
|
556
|
+
: info.branch;
|
|
557
|
+
|
|
558
|
+
const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}${versionStr}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
|
|
351
559
|
const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 1)} ${c.dim}${box.v}${c.reset}`;
|
|
352
560
|
|
|
353
561
|
lines.push(topBorder);
|
|
354
562
|
lines.push(headerLine);
|
|
563
|
+
|
|
564
|
+
// Show update available notification
|
|
565
|
+
if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
|
|
566
|
+
lines.push(fullDivider());
|
|
567
|
+
lines.push(fullRow(`↑ Update available: v${updateInfo.latest}`, c.yellow));
|
|
568
|
+
lines.push(fullRow(` Run: npx agileflow update`, c.dim));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Show "just updated" changelog
|
|
572
|
+
if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
|
|
573
|
+
lines.push(fullDivider());
|
|
574
|
+
lines.push(fullRow(`What's new in v${info.version}:`, c.green));
|
|
575
|
+
for (const entry of updateInfo.changelog.slice(0, 2)) {
|
|
576
|
+
lines.push(fullRow(`• ${truncate(entry, W - 4)}`, c.dim));
|
|
577
|
+
}
|
|
578
|
+
lines.push(fullRow(`Run /agileflow:whats-new for full changelog`, c.dim));
|
|
579
|
+
}
|
|
580
|
+
|
|
355
581
|
lines.push(divider());
|
|
356
582
|
|
|
357
583
|
// Stories section
|
|
358
|
-
lines.push(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
584
|
+
lines.push(
|
|
585
|
+
row(
|
|
586
|
+
'In Progress',
|
|
587
|
+
info.wipCount > 0 ? `${info.wipCount}` : '0',
|
|
588
|
+
c.dim,
|
|
589
|
+
info.wipCount > 0 ? c.yellow : c.dim
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
lines.push(
|
|
593
|
+
row(
|
|
594
|
+
'Blocked',
|
|
595
|
+
info.blockedCount > 0 ? `${info.blockedCount}` : '0',
|
|
596
|
+
c.dim,
|
|
597
|
+
info.blockedCount > 0 ? c.red : c.dim
|
|
598
|
+
)
|
|
599
|
+
);
|
|
600
|
+
lines.push(
|
|
601
|
+
row(
|
|
602
|
+
'Ready',
|
|
603
|
+
info.readyCount > 0 ? `${info.readyCount}` : '0',
|
|
604
|
+
c.dim,
|
|
605
|
+
info.readyCount > 0 ? c.cyan : c.dim
|
|
606
|
+
)
|
|
607
|
+
);
|
|
608
|
+
lines.push(
|
|
609
|
+
row(
|
|
610
|
+
'Completed',
|
|
611
|
+
info.completedCount > 0 ? `${info.completedCount}` : '0',
|
|
612
|
+
c.dim,
|
|
613
|
+
info.completedCount > 0 ? c.green : c.dim
|
|
614
|
+
)
|
|
615
|
+
);
|
|
362
616
|
|
|
363
617
|
lines.push(divider());
|
|
364
618
|
|
|
@@ -366,16 +620,15 @@ function formatTable(info, archival, session, precompact) {
|
|
|
366
620
|
if (archival.disabled) {
|
|
367
621
|
lines.push(row('Auto-archival', 'disabled', c.dim, c.dim));
|
|
368
622
|
} else {
|
|
369
|
-
const archivalStatus =
|
|
370
|
-
? `archived ${archival.archived} stories`
|
|
371
|
-
|
|
372
|
-
|
|
623
|
+
const archivalStatus =
|
|
624
|
+
archival.archived > 0 ? `archived ${archival.archived} stories` : `nothing to archive`;
|
|
625
|
+
lines.push(
|
|
626
|
+
row('Auto-archival', archivalStatus, c.dim, archival.archived > 0 ? c.green : c.dim)
|
|
627
|
+
);
|
|
373
628
|
}
|
|
374
629
|
|
|
375
630
|
// Session cleanup
|
|
376
|
-
const sessionStatus = session.cleared > 0
|
|
377
|
-
? `cleared ${session.cleared} command(s)`
|
|
378
|
-
: `clean`;
|
|
631
|
+
const sessionStatus = session.cleared > 0 ? `cleared ${session.cleared} command(s)` : `clean`;
|
|
379
632
|
lines.push(row('Session state', sessionStatus, c.dim, session.cleared > 0 ? c.green : c.dim));
|
|
380
633
|
|
|
381
634
|
// PreCompact status with version check
|
|
@@ -396,11 +649,31 @@ function formatTable(info, archival, session, precompact) {
|
|
|
396
649
|
lines.push(row('Context preserve', 'not configured', c.dim, c.dim));
|
|
397
650
|
}
|
|
398
651
|
|
|
652
|
+
// Parallel sessions status
|
|
653
|
+
if (parallelSessions && parallelSessions.available) {
|
|
654
|
+
if (parallelSessions.otherActive > 0) {
|
|
655
|
+
const sessionStr = `⚠️ ${parallelSessions.otherActive} other active`;
|
|
656
|
+
lines.push(row('Sessions', sessionStr, c.dim, c.yellow));
|
|
657
|
+
} else {
|
|
658
|
+
const sessionStr = parallelSessions.currentId
|
|
659
|
+
? `✓ Session ${parallelSessions.currentId} (only)`
|
|
660
|
+
: '✓ Only session';
|
|
661
|
+
lines.push(row('Sessions', sessionStr, c.dim, c.green));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
399
665
|
lines.push(divider());
|
|
400
666
|
|
|
401
667
|
// Current story (if any) - row() auto-truncates
|
|
402
668
|
if (info.currentStory) {
|
|
403
|
-
lines.push(
|
|
669
|
+
lines.push(
|
|
670
|
+
row(
|
|
671
|
+
'Current',
|
|
672
|
+
`${c.blue}${info.currentStory.id}${c.reset}: ${info.currentStory.title}`,
|
|
673
|
+
c.dim,
|
|
674
|
+
''
|
|
675
|
+
)
|
|
676
|
+
);
|
|
404
677
|
} else {
|
|
405
678
|
lines.push(row('Current', 'No active story', c.dim, c.dim));
|
|
406
679
|
}
|
|
@@ -414,14 +687,45 @@ function formatTable(info, archival, session, precompact) {
|
|
|
414
687
|
}
|
|
415
688
|
|
|
416
689
|
// Main
|
|
417
|
-
function main() {
|
|
690
|
+
async function main() {
|
|
418
691
|
const rootDir = getProjectRoot();
|
|
419
692
|
const info = getProjectInfo(rootDir);
|
|
420
693
|
const archival = runArchival(rootDir);
|
|
421
694
|
const session = clearActiveCommands(rootDir);
|
|
422
695
|
const precompact = checkPreCompact(rootDir);
|
|
696
|
+
const parallelSessions = checkParallelSessions(rootDir);
|
|
423
697
|
|
|
424
|
-
|
|
698
|
+
// Check for updates (async, cached)
|
|
699
|
+
let updateInfo = {};
|
|
700
|
+
try {
|
|
701
|
+
updateInfo = await checkUpdates();
|
|
702
|
+
|
|
703
|
+
// If auto-update is enabled and update available, run it
|
|
704
|
+
if (updateInfo.available && updateInfo.autoUpdate) {
|
|
705
|
+
const updated = await runAutoUpdate(rootDir);
|
|
706
|
+
if (updated) {
|
|
707
|
+
// Re-run welcome after update (the new version will show changelog)
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Mark current version as seen to track for next update
|
|
713
|
+
if (updateInfo.justUpdated && updateChecker) {
|
|
714
|
+
updateChecker.markVersionSeen(info.version);
|
|
715
|
+
}
|
|
716
|
+
} catch (e) {
|
|
717
|
+
// Update check failed - continue without it
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
console.log(formatTable(info, archival, session, precompact, parallelSessions, updateInfo));
|
|
721
|
+
|
|
722
|
+
// Show warning and tip if other sessions are active
|
|
723
|
+
if (parallelSessions.otherActive > 0) {
|
|
724
|
+
console.log('');
|
|
725
|
+
console.log(`${c.yellow}⚠️ Other Claude session(s) active in this repo.${c.reset}`);
|
|
726
|
+
console.log(`${c.dim} Run /agileflow:session:status to see all sessions.${c.reset}`);
|
|
727
|
+
console.log(`${c.dim} Run /agileflow:session:new to create isolated workspace.${c.reset}`);
|
|
728
|
+
}
|
|
425
729
|
}
|
|
426
730
|
|
|
427
|
-
main();
|
|
731
|
+
main().catch(console.error);
|
|
@@ -105,15 +105,20 @@ module.exports = {
|
|
|
105
105
|
// Check if CLI itself is still outdated (only if self-update was disabled)
|
|
106
106
|
if (npmLatestVersion && semver.lt(localCliVersion, npmLatestVersion) && !shouldSelfUpdate) {
|
|
107
107
|
console.log();
|
|
108
|
-
warning('Your CLI is outdated!');
|
|
109
|
-
console.log(
|
|
110
|
-
|
|
108
|
+
warning('Your global CLI is outdated!');
|
|
109
|
+
console.log(
|
|
110
|
+
chalk.dim(` You have a global installation at v${localCliVersion}, but v${npmLatestVersion} is available.\n`)
|
|
111
|
+
);
|
|
112
|
+
console.log(chalk.dim(` Options:`));
|
|
113
|
+
console.log(chalk.dim(` 1. Cancel and run: `) + chalk.cyan(`npx agileflow@latest update`));
|
|
114
|
+
console.log(chalk.dim(` 2. Remove global: `) + chalk.cyan(`npm uninstall -g agileflow`) + chalk.dim(` (recommended)`));
|
|
115
|
+
console.log(chalk.dim(` 3. Update global: `) + chalk.cyan(`npm install -g agileflow@latest\n`));
|
|
111
116
|
|
|
112
117
|
const useOutdated = options.force
|
|
113
118
|
? true
|
|
114
119
|
: await confirm('Continue with outdated CLI anyway?');
|
|
115
120
|
if (!useOutdated) {
|
|
116
|
-
console.log(chalk.dim('\nUpdate cancelled\n'));
|
|
121
|
+
console.log(chalk.dim('\nUpdate cancelled. Run: npx agileflow@latest update\n'));
|
|
117
122
|
process.exit(0);
|
|
118
123
|
}
|
|
119
124
|
}
|
|
@@ -209,6 +214,19 @@ module.exports = {
|
|
|
209
214
|
|
|
210
215
|
console.log(chalk.green(`\n✨ Update complete! (${status.version} → ${latestVersion})\n`));
|
|
211
216
|
|
|
217
|
+
// If running from outdated global installation, remind user to update it
|
|
218
|
+
if (
|
|
219
|
+
npmLatestVersion &&
|
|
220
|
+
semver.lt(localCliVersion, npmLatestVersion) &&
|
|
221
|
+
!options.selfUpdated
|
|
222
|
+
) {
|
|
223
|
+
console.log(chalk.yellow('💡 Tip: Your global AgileFlow CLI is outdated.'));
|
|
224
|
+
console.log(chalk.dim(' To avoid this message, either:'));
|
|
225
|
+
console.log(chalk.dim(' • Remove global: npm uninstall -g agileflow (recommended)'));
|
|
226
|
+
console.log(chalk.dim(' • Use npx: npx agileflow@latest update'));
|
|
227
|
+
console.log(chalk.dim(' • Update global: npm install -g agileflow@latest\n'));
|
|
228
|
+
}
|
|
229
|
+
|
|
212
230
|
process.exit(0);
|
|
213
231
|
} catch (err) {
|
|
214
232
|
console.error(chalk.red('Update failed:'), err.message);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# agileflow-stop.sh - Session wrap-up hook
|
|
3
|
-
|
|
4
|
-
# Only run if we're in a git repo
|
|
5
|
-
git rev-parse --git-dir > /dev/null 2>&1 || exit 0
|
|
6
|
-
|
|
7
|
-
# Check for uncommitted changes
|
|
8
|
-
CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
9
|
-
|
|
10
|
-
if [ "$CHANGES" -gt 0 ]; then
|
|
11
|
-
echo ""
|
|
12
|
-
echo -e "\033[0;33m$CHANGES uncommitted change(s)\033[0m"
|
|
13
|
-
fi
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# agileflow-stop.sh - Session wrap-up hook
|
|
3
|
-
|
|
4
|
-
# Only run if we're in a git repo
|
|
5
|
-
git rev-parse --git-dir > /dev/null 2>&1 || exit 0
|
|
6
|
-
|
|
7
|
-
# Check for uncommitted changes
|
|
8
|
-
CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
9
|
-
|
|
10
|
-
if [ "$CHANGES" -gt 0 ]; then
|
|
11
|
-
echo ""
|
|
12
|
-
echo -e "\033[0;33m$CHANGES uncommitted change(s)\033[0m"
|
|
13
|
-
fi
|