agileflow 2.72.0 → 2.74.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.
@@ -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);