agileflow 2.72.0 → 2.73.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 +119 -85
- package/src/core/agents/configuration/archival.md +10 -10
- package/src/core/commands/configure.md +68 -32
- package/tools/cli/installers/core/installer.js +3 -11
- package/src/core/templates/agileflow-configure.js +0 -1033
- package/src/core/templates/agileflow-statusline.sh +0 -355
- package/src/core/templates/agileflow-welcome.js +0 -731
- package/src/core/templates/clear-active-command.js +0 -42
- package/src/core/templates/init.sh +0 -76
- package/src/core/templates/precompact-context.sh +0 -123
- package/src/core/templates/resume-session.sh +0 -121
- package/src/core/templates/validate-tokens.sh +0 -73
- package/src/core/templates/worktree-create.sh +0 -111
|
@@ -1,731 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* agileflow-welcome.js - Beautiful SessionStart welcome display
|
|
5
|
-
*
|
|
6
|
-
* Shows a transparent ASCII table with:
|
|
7
|
-
* - Project info (name, version, branch, commit)
|
|
8
|
-
* - Story stats (WIP, blocked, completed)
|
|
9
|
-
* - Archival status
|
|
10
|
-
* - Session cleanup status
|
|
11
|
-
* - Last commit
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
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
|
-
}
|
|
28
|
-
|
|
29
|
-
// ANSI color codes
|
|
30
|
-
const c = {
|
|
31
|
-
reset: '\x1b[0m',
|
|
32
|
-
bold: '\x1b[1m',
|
|
33
|
-
dim: '\x1b[2m',
|
|
34
|
-
|
|
35
|
-
red: '\x1b[31m',
|
|
36
|
-
green: '\x1b[32m',
|
|
37
|
-
yellow: '\x1b[33m',
|
|
38
|
-
blue: '\x1b[34m',
|
|
39
|
-
magenta: '\x1b[35m',
|
|
40
|
-
cyan: '\x1b[36m',
|
|
41
|
-
|
|
42
|
-
brightBlack: '\x1b[90m',
|
|
43
|
-
brightGreen: '\x1b[92m',
|
|
44
|
-
brightYellow: '\x1b[93m',
|
|
45
|
-
brightCyan: '\x1b[96m',
|
|
46
|
-
|
|
47
|
-
// Brand color (#e8683a)
|
|
48
|
-
brand: '\x1b[38;2;232;104;58m',
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Box drawing characters
|
|
52
|
-
const box = {
|
|
53
|
-
tl: '╭',
|
|
54
|
-
tr: '╮',
|
|
55
|
-
bl: '╰',
|
|
56
|
-
br: '╯',
|
|
57
|
-
h: '─',
|
|
58
|
-
v: '│',
|
|
59
|
-
lT: '├',
|
|
60
|
-
rT: '┤',
|
|
61
|
-
tT: '┬',
|
|
62
|
-
bT: '┴',
|
|
63
|
-
cross: '┼',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
function getProjectRoot() {
|
|
67
|
-
let dir = process.cwd();
|
|
68
|
-
while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
|
|
69
|
-
dir = path.dirname(dir);
|
|
70
|
-
}
|
|
71
|
-
return dir !== '/' ? dir : process.cwd();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function getProjectInfo(rootDir) {
|
|
75
|
-
const info = {
|
|
76
|
-
name: 'agileflow',
|
|
77
|
-
version: 'unknown',
|
|
78
|
-
branch: 'unknown',
|
|
79
|
-
commit: 'unknown',
|
|
80
|
-
lastCommit: '',
|
|
81
|
-
wipCount: 0,
|
|
82
|
-
blockedCount: 0,
|
|
83
|
-
completedCount: 0,
|
|
84
|
-
readyCount: 0,
|
|
85
|
-
totalStories: 0,
|
|
86
|
-
currentStory: null,
|
|
87
|
-
};
|
|
88
|
-
|
|
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)
|
|
93
|
-
try {
|
|
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
|
-
}
|
|
105
|
-
} catch (e) {
|
|
106
|
-
// Fallback: check .agileflow/package.json
|
|
107
|
-
try {
|
|
108
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, '.agileflow/package.json'), 'utf8'));
|
|
109
|
-
info.version = pkg.version || info.version;
|
|
110
|
-
} catch (e2) {}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Get git info
|
|
114
|
-
try {
|
|
115
|
-
info.branch = execSync('git branch --show-current', { cwd: rootDir, encoding: 'utf8' }).trim();
|
|
116
|
-
info.commit = execSync('git rev-parse --short HEAD', { cwd: rootDir, encoding: 'utf8' }).trim();
|
|
117
|
-
info.lastCommit = execSync('git log -1 --format="%s"', {
|
|
118
|
-
cwd: rootDir,
|
|
119
|
-
encoding: 'utf8',
|
|
120
|
-
}).trim();
|
|
121
|
-
} catch (e) {}
|
|
122
|
-
|
|
123
|
-
// Get status info
|
|
124
|
-
try {
|
|
125
|
-
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
126
|
-
if (fs.existsSync(statusPath)) {
|
|
127
|
-
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
128
|
-
if (status.stories) {
|
|
129
|
-
for (const [id, story] of Object.entries(status.stories)) {
|
|
130
|
-
info.totalStories++;
|
|
131
|
-
if (story.status === 'in_progress') {
|
|
132
|
-
info.wipCount++;
|
|
133
|
-
if (!info.currentStory) {
|
|
134
|
-
info.currentStory = { id, title: story.title };
|
|
135
|
-
}
|
|
136
|
-
} else if (story.status === 'blocked') {
|
|
137
|
-
info.blockedCount++;
|
|
138
|
-
} else if (story.status === 'completed') {
|
|
139
|
-
info.completedCount++;
|
|
140
|
-
} else if (story.status === 'ready') {
|
|
141
|
-
info.readyCount++;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
} catch (e) {}
|
|
147
|
-
|
|
148
|
-
return info;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function runArchival(rootDir) {
|
|
152
|
-
const result = { ran: false, threshold: 7, archived: 0, remaining: 0 };
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
156
|
-
if (fs.existsSync(metadataPath)) {
|
|
157
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
158
|
-
if (metadata.archival?.enabled === false) {
|
|
159
|
-
result.disabled = true;
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
result.threshold = metadata.archival?.threshold_days || 7;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
166
|
-
if (!fs.existsSync(statusPath)) return result;
|
|
167
|
-
|
|
168
|
-
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
169
|
-
const stories = status.stories || {};
|
|
170
|
-
|
|
171
|
-
const cutoffDate = new Date();
|
|
172
|
-
cutoffDate.setDate(cutoffDate.getDate() - result.threshold);
|
|
173
|
-
|
|
174
|
-
let toArchiveCount = 0;
|
|
175
|
-
for (const [id, story] of Object.entries(stories)) {
|
|
176
|
-
if (story.status === 'completed' && story.completed_at) {
|
|
177
|
-
if (new Date(story.completed_at) < cutoffDate) {
|
|
178
|
-
toArchiveCount++;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
result.ran = true;
|
|
184
|
-
result.remaining = Object.keys(stories).length;
|
|
185
|
-
|
|
186
|
-
if (toArchiveCount > 0) {
|
|
187
|
-
// Run archival
|
|
188
|
-
try {
|
|
189
|
-
execSync('bash scripts/archive-completed-stories.sh', {
|
|
190
|
-
cwd: rootDir,
|
|
191
|
-
encoding: 'utf8',
|
|
192
|
-
stdio: 'pipe',
|
|
193
|
-
});
|
|
194
|
-
result.archived = toArchiveCount;
|
|
195
|
-
result.remaining -= toArchiveCount;
|
|
196
|
-
} catch (e) {}
|
|
197
|
-
}
|
|
198
|
-
} catch (e) {}
|
|
199
|
-
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function clearActiveCommands(rootDir) {
|
|
204
|
-
const result = { ran: false, cleared: 0, commandNames: [] };
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const sessionStatePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
208
|
-
if (!fs.existsSync(sessionStatePath)) return result;
|
|
209
|
-
|
|
210
|
-
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
211
|
-
result.ran = true;
|
|
212
|
-
|
|
213
|
-
if (state.active_commands && state.active_commands.length > 0) {
|
|
214
|
-
result.cleared = state.active_commands.length;
|
|
215
|
-
// Capture command names before clearing
|
|
216
|
-
for (const cmd of state.active_commands) {
|
|
217
|
-
if (cmd.name) result.commandNames.push(cmd.name);
|
|
218
|
-
}
|
|
219
|
-
state.active_commands = [];
|
|
220
|
-
}
|
|
221
|
-
if (state.active_command !== undefined) {
|
|
222
|
-
result.cleared++;
|
|
223
|
-
// Capture single command name
|
|
224
|
-
if (state.active_command.name) {
|
|
225
|
-
result.commandNames.push(state.active_command.name);
|
|
226
|
-
}
|
|
227
|
-
delete state.active_command;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (result.cleared > 0) {
|
|
231
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
232
|
-
}
|
|
233
|
-
} catch (e) {}
|
|
234
|
-
|
|
235
|
-
return result;
|
|
236
|
-
}
|
|
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
|
-
|
|
292
|
-
function checkPreCompact(rootDir) {
|
|
293
|
-
const result = { configured: false, scriptExists: false, version: null, outdated: false };
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
// Check if PreCompact hook is configured in settings
|
|
297
|
-
const settingsPath = path.join(rootDir, '.claude/settings.json');
|
|
298
|
-
if (fs.existsSync(settingsPath)) {
|
|
299
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
300
|
-
if (settings.hooks?.PreCompact?.length > 0) {
|
|
301
|
-
result.configured = true;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Check if the script exists
|
|
306
|
-
const scriptPath = path.join(rootDir, 'scripts/precompact-context.sh');
|
|
307
|
-
if (fs.existsSync(scriptPath)) {
|
|
308
|
-
result.scriptExists = true;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Check configured version from metadata
|
|
312
|
-
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
313
|
-
if (fs.existsSync(metadataPath)) {
|
|
314
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
315
|
-
if (metadata.features?.precompact?.configured_version) {
|
|
316
|
-
result.version = metadata.features.precompact.configured_version;
|
|
317
|
-
// PreCompact v2.40.0+ has multi-command support
|
|
318
|
-
result.outdated = compareVersions(result.version, '2.40.0') < 0;
|
|
319
|
-
} else if (result.configured) {
|
|
320
|
-
// Hook exists but no version tracked = definitely outdated
|
|
321
|
-
result.outdated = true;
|
|
322
|
-
result.version = 'unknown';
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
} catch (e) {}
|
|
326
|
-
|
|
327
|
-
return result;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Compare semantic versions: returns -1 if a < b, 0 if equal, 1 if a > b
|
|
331
|
-
function compareVersions(a, b) {
|
|
332
|
-
if (!a || !b) return 0;
|
|
333
|
-
const partsA = a.split('.').map(Number);
|
|
334
|
-
const partsB = b.split('.').map(Number);
|
|
335
|
-
for (let i = 0; i < 3; i++) {
|
|
336
|
-
const numA = partsA[i] || 0;
|
|
337
|
-
const numB = partsB[i] || 0;
|
|
338
|
-
if (numA < numB) return -1;
|
|
339
|
-
if (numA > numB) return 1;
|
|
340
|
-
}
|
|
341
|
-
return 0;
|
|
342
|
-
}
|
|
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
|
-
|
|
436
|
-
function getFeatureVersions(rootDir) {
|
|
437
|
-
const result = {
|
|
438
|
-
hooks: { version: null, outdated: false },
|
|
439
|
-
archival: { version: null, outdated: false },
|
|
440
|
-
statusline: { version: null, outdated: false },
|
|
441
|
-
precompact: { version: null, outdated: false },
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
// Minimum compatible versions for each feature
|
|
445
|
-
const minVersions = {
|
|
446
|
-
hooks: '2.35.0',
|
|
447
|
-
archival: '2.35.0',
|
|
448
|
-
statusline: '2.35.0',
|
|
449
|
-
precompact: '2.40.0', // Multi-command support
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
try {
|
|
453
|
-
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
454
|
-
if (fs.existsSync(metadataPath)) {
|
|
455
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
456
|
-
|
|
457
|
-
for (const feature of Object.keys(result)) {
|
|
458
|
-
if (metadata.features?.[feature]?.configured_version) {
|
|
459
|
-
result[feature].version = metadata.features[feature].configured_version;
|
|
460
|
-
result[feature].outdated =
|
|
461
|
-
compareVersions(result[feature].version, minVersions[feature]) < 0;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
} catch (e) {}
|
|
466
|
-
|
|
467
|
-
return result;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function pad(str, len, align = 'left') {
|
|
471
|
-
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
472
|
-
const diff = len - stripped.length;
|
|
473
|
-
if (diff <= 0) return str;
|
|
474
|
-
if (align === 'right') return ' '.repeat(diff) + str;
|
|
475
|
-
if (align === 'center')
|
|
476
|
-
return ' '.repeat(Math.floor(diff / 2)) + str + ' '.repeat(Math.ceil(diff / 2));
|
|
477
|
-
return str + ' '.repeat(diff);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Truncate string to max length, respecting ANSI codes
|
|
481
|
-
function truncate(str, maxLen, suffix = '..') {
|
|
482
|
-
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
483
|
-
if (stripped.length <= maxLen) return str;
|
|
484
|
-
|
|
485
|
-
// Find position in original string that corresponds to maxLen - suffix.length visible chars
|
|
486
|
-
const targetLen = maxLen - suffix.length;
|
|
487
|
-
let visibleCount = 0;
|
|
488
|
-
let cutIndex = 0;
|
|
489
|
-
let inEscape = false;
|
|
490
|
-
|
|
491
|
-
for (let i = 0; i < str.length; i++) {
|
|
492
|
-
if (str[i] === '\x1b') {
|
|
493
|
-
inEscape = true;
|
|
494
|
-
} else if (inEscape && str[i] === 'm') {
|
|
495
|
-
inEscape = false;
|
|
496
|
-
} else if (!inEscape) {
|
|
497
|
-
visibleCount++;
|
|
498
|
-
if (visibleCount >= targetLen) {
|
|
499
|
-
cutIndex = i + 1;
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return str.substring(0, cutIndex) + suffix;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function formatTable(info, archival, session, precompact, parallelSessions, updateInfo = {}) {
|
|
509
|
-
const W = 58; // inner width
|
|
510
|
-
const R = W - 24; // right column width (34 chars)
|
|
511
|
-
const lines = [];
|
|
512
|
-
|
|
513
|
-
// Helper to create a row (auto-truncates right content to fit)
|
|
514
|
-
const row = (left, right, leftColor = '', rightColor = '') => {
|
|
515
|
-
const leftStr = `${leftColor}${left}${leftColor ? c.reset : ''}`;
|
|
516
|
-
const rightTrunc = truncate(right, R);
|
|
517
|
-
const rightStr = `${rightColor}${rightTrunc}${rightColor ? c.reset : ''}`;
|
|
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}`;
|
|
519
|
-
};
|
|
520
|
-
|
|
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}`;
|
|
531
|
-
const topBorder = `${c.dim}${box.tl}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 22)}${box.tr}${c.reset}`;
|
|
532
|
-
const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 22)}${box.br}${c.reset}`;
|
|
533
|
-
|
|
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}`;
|
|
559
|
-
const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 1)} ${c.dim}${box.v}${c.reset}`;
|
|
560
|
-
|
|
561
|
-
lines.push(topBorder);
|
|
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
|
-
|
|
581
|
-
lines.push(divider());
|
|
582
|
-
|
|
583
|
-
// Stories section
|
|
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
|
-
);
|
|
616
|
-
|
|
617
|
-
lines.push(divider());
|
|
618
|
-
|
|
619
|
-
// Archival section
|
|
620
|
-
if (archival.disabled) {
|
|
621
|
-
lines.push(row('Auto-archival', 'disabled', c.dim, c.dim));
|
|
622
|
-
} else {
|
|
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
|
-
);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Session cleanup
|
|
631
|
-
const sessionStatus = session.cleared > 0 ? `cleared ${session.cleared} command(s)` : `clean`;
|
|
632
|
-
lines.push(row('Session state', sessionStatus, c.dim, session.cleared > 0 ? c.green : c.dim));
|
|
633
|
-
|
|
634
|
-
// PreCompact status with version check
|
|
635
|
-
if (precompact.configured && precompact.scriptExists) {
|
|
636
|
-
if (precompact.outdated) {
|
|
637
|
-
const verStr = precompact.version ? ` (v${precompact.version})` : '';
|
|
638
|
-
lines.push(row('Context preserve', `outdated${verStr}`, c.dim, c.yellow));
|
|
639
|
-
} else if (session.commandNames && session.commandNames.length > 0) {
|
|
640
|
-
// Show the preserved command names
|
|
641
|
-
const cmdDisplay = session.commandNames.map(n => `/agileflow:${n}`).join(', ');
|
|
642
|
-
lines.push(row('Context preserve', cmdDisplay, c.dim, c.green));
|
|
643
|
-
} else {
|
|
644
|
-
lines.push(row('Context preserve', 'nothing to compact', c.dim, c.dim));
|
|
645
|
-
}
|
|
646
|
-
} else if (precompact.configured) {
|
|
647
|
-
lines.push(row('Context preserve', 'script missing', c.dim, c.yellow));
|
|
648
|
-
} else {
|
|
649
|
-
lines.push(row('Context preserve', 'not configured', c.dim, c.dim));
|
|
650
|
-
}
|
|
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
|
-
|
|
665
|
-
lines.push(divider());
|
|
666
|
-
|
|
667
|
-
// Current story (if any) - row() auto-truncates
|
|
668
|
-
if (info.currentStory) {
|
|
669
|
-
lines.push(
|
|
670
|
-
row(
|
|
671
|
-
'Current',
|
|
672
|
-
`${c.blue}${info.currentStory.id}${c.reset}: ${info.currentStory.title}`,
|
|
673
|
-
c.dim,
|
|
674
|
-
''
|
|
675
|
-
)
|
|
676
|
-
);
|
|
677
|
-
} else {
|
|
678
|
-
lines.push(row('Current', 'No active story', c.dim, c.dim));
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Last commit - row() auto-truncates
|
|
682
|
-
lines.push(row('Last commit', `${info.commit} ${info.lastCommit}`, c.dim, c.dim));
|
|
683
|
-
|
|
684
|
-
lines.push(bottomBorder);
|
|
685
|
-
|
|
686
|
-
return lines.join('\n');
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// Main
|
|
690
|
-
async function main() {
|
|
691
|
-
const rootDir = getProjectRoot();
|
|
692
|
-
const info = getProjectInfo(rootDir);
|
|
693
|
-
const archival = runArchival(rootDir);
|
|
694
|
-
const session = clearActiveCommands(rootDir);
|
|
695
|
-
const precompact = checkPreCompact(rootDir);
|
|
696
|
-
const parallelSessions = checkParallelSessions(rootDir);
|
|
697
|
-
|
|
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
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
main().catch(console.error);
|