agileflow 2.90.4 → 2.90.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.90.5] - 2026-01-17
11
+
12
+ ### Added
13
+ - Restore static dashboard command
14
+
10
15
  ## [2.90.4] - 2026-01-17
11
16
 
12
17
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.90.4",
3
+ "version": "2.90.5",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -41,9 +41,7 @@ async function main() {
41
41
  };
42
42
 
43
43
  // Render the dashboard
44
- const { waitUntilExit } = render(
45
- React.createElement(Dashboard, { onAction: handleAction })
46
- );
44
+ const { waitUntilExit } = render(React.createElement(Dashboard, { onAction: handleAction }));
47
45
 
48
46
  // Wait for exit
49
47
  await waitUntilExit();
@@ -372,19 +372,27 @@ class SimpleTUI {
372
372
 
373
373
  // Header
374
374
  const header = `─ SESSIONS ─`;
375
- lines.push(`${ANSI.cyan}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`);
375
+ lines.push(
376
+ `${ANSI.cyan}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`
377
+ );
376
378
 
377
379
  if (sessions.length === 0) {
378
- lines.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad('No active sessions', innerWidth)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
380
+ lines.push(
381
+ `${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad('No active sessions', innerWidth)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
382
+ );
379
383
  } else {
380
384
  const maxSessions = Math.floor((panelHeight - 2) / 2);
381
385
  for (const session of sessions.slice(0, maxSessions)) {
382
386
  const indicator = session.current ? `${ANSI.green}▶` : ' ';
383
387
  const name = `Session ${session.id}${session.is_main ? ' [main]' : ''}`;
384
- lines.push(`${ANSI.cyan}│${ANSI.reset}${indicator} ${ANSI.bold}${pad(name, innerWidth - 2)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
388
+ lines.push(
389
+ `${ANSI.cyan}│${ANSI.reset}${indicator} ${ANSI.bold}${pad(name, innerWidth - 2)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
390
+ );
385
391
 
386
392
  const info = `${session.branch || '?'}${session.story ? ' / ' + session.story : ''}`;
387
- lines.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad(info, innerWidth - 1)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
393
+ lines.push(
394
+ `${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad(info, innerWidth - 1)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
395
+ );
388
396
  }
389
397
  }
390
398
 
@@ -405,12 +413,20 @@ class SimpleTUI {
405
413
 
406
414
  // Header
407
415
  const header = `─ OUTPUT ─`;
408
- lines.push(`${ANSI.green}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`);
416
+ lines.push(
417
+ `${ANSI.green}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`
418
+ );
409
419
 
410
420
  // Combine events and messages
411
421
  const allMessages = [
412
422
  ...agentEvents.map(e => ({
413
- time: e.timestamp ? new Date(e.timestamp).toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' }) : '',
423
+ time: e.timestamp
424
+ ? new Date(e.timestamp).toLocaleTimeString('en-US', {
425
+ hour12: false,
426
+ hour: '2-digit',
427
+ minute: '2-digit',
428
+ })
429
+ : '',
414
430
  agent: (e.agent || 'unknown').replace('agileflow-', ''),
415
431
  msg: e.message || (e.event === 'iteration' ? `Iter ${e.iter}` : e.event || ''),
416
432
  })),
@@ -422,15 +438,20 @@ class SimpleTUI {
422
438
  ].slice(-(panelHeight - 2));
423
439
 
424
440
  if (allMessages.length === 0) {
425
- lines.push(`${ANSI.green}│${ANSI.reset} ${ANSI.dim}${pad('Waiting for activity...', innerWidth)}${ANSI.reset}${ANSI.green}│${ANSI.reset}`);
441
+ lines.push(
442
+ `${ANSI.green}│${ANSI.reset} ${ANSI.dim}${pad('Waiting for activity...', innerWidth)}${ANSI.reset}${ANSI.green}│${ANSI.reset}`
443
+ );
426
444
  } else {
427
445
  for (const msg of allMessages) {
428
446
  const prefix = `${msg.time} [${msg.agent}] `;
429
447
  const maxMsgLen = Math.max(10, innerWidth - prefix.length - 1);
430
- const truncatedMsg = msg.msg.length > maxMsgLen ? msg.msg.slice(0, maxMsgLen - 2) + '..' : msg.msg;
448
+ const truncatedMsg =
449
+ msg.msg.length > maxMsgLen ? msg.msg.slice(0, maxMsgLen - 2) + '..' : msg.msg;
431
450
  const line = `${ANSI.dim}${msg.time}${ANSI.reset} [${ANSI.cyan}${msg.agent}${ANSI.reset}] ${truncatedMsg}`;
432
451
  const cleanLen = prefix.length + truncatedMsg.length;
433
- lines.push(`${ANSI.green}│${ANSI.reset} ${line}${' '.repeat(Math.max(0, innerWidth - cleanLen - 1))}${ANSI.green}│${ANSI.reset}`);
452
+ lines.push(
453
+ `${ANSI.green}│${ANSI.reset} ${line}${' '.repeat(Math.max(0, innerWidth - cleanLen - 1))}${ANSI.green}│${ANSI.reset}`
454
+ );
434
455
  }
435
456
  }
436
457
 
@@ -450,16 +471,23 @@ class SimpleTUI {
450
471
 
451
472
  // Loop status
452
473
  if (loopStatus.active) {
453
- const statusIcon = loopStatus.paused ? `${ANSI.yellow}⏸${ANSI.reset}` : `${ANSI.green}▶${ANSI.reset}`;
454
- const bar = progressBar(loopStatus.iteration, loopStatus.maxIterations, Math.min(15, Math.floor(width / 6)));
474
+ const statusIcon = loopStatus.paused
475
+ ? `${ANSI.yellow}⏸${ANSI.reset}`
476
+ : `${ANSI.green}▶${ANSI.reset}`;
477
+ const bar = progressBar(
478
+ loopStatus.iteration,
479
+ loopStatus.maxIterations,
480
+ Math.min(15, Math.floor(width / 6))
481
+ );
455
482
  const info = `${loopStatus.epic || ''}${loopStatus.currentStory ? ' / ' + loopStatus.currentStory : ''}`;
456
483
  output.push(`${statusIcon} ${ANSI.bold}${info}${ANSI.reset} ${bar}`);
457
484
  }
458
485
 
459
486
  // Key bindings - compact on narrow terminals
460
- const keys = width >= 70
461
- ? `${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions${ANSI.reset}`
462
- : `${ANSI.dim}Q S P R T 1-9${ANSI.reset}`;
487
+ const keys =
488
+ width >= 70
489
+ ? `${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions${ANSI.reset}`
490
+ : `${ANSI.dim}Q S P R T 1-9${ANSI.reset}`;
463
491
  const version = `${ANSI.cyan}v2.90.3${ANSI.reset}`;
464
492
  output.push(`${keys} ${version}`);
465
493
  }
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * AgileFlow Dashboard
5
+ *
6
+ * Static dashboard view - prints once and exits.
7
+ * No continuous refresh, no screen clearing, scrollable output.
8
+ *
9
+ * Usage: npx agileflow start
10
+ */
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const { c: colors } = require('../../../lib/colors');
15
+
16
+ function showHeader(version) {
17
+ console.log('');
18
+ console.log(
19
+ `${colors.orange}${colors.bold} ╔═══════════════════════════════════════════╗${colors.reset}`
20
+ );
21
+ console.log(
22
+ `${colors.orange}${colors.bold} ║ AgileFlow Dashboard ║${colors.reset}`
23
+ );
24
+ console.log(
25
+ `${colors.orange}${colors.bold} ╚═══════════════════════════════════════════╝${colors.reset}`
26
+ );
27
+ if (version) {
28
+ console.log(`${colors.dim} v${version}${colors.reset}`);
29
+ }
30
+ console.log('');
31
+ }
32
+
33
+ async function loadStatus() {
34
+ const statusPath = path.join(process.cwd(), 'docs', '09-agents', 'status.json');
35
+
36
+ if (!fs.existsSync(statusPath)) {
37
+ return null;
38
+ }
39
+
40
+ try {
41
+ const content = fs.readFileSync(statusPath, 'utf8');
42
+ return JSON.parse(content);
43
+ } catch (err) {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function loadLoopStatus() {
49
+ const loopPath = path.join(process.cwd(), 'docs', '09-agents', 'loop-status.json');
50
+
51
+ if (!fs.existsSync(loopPath)) {
52
+ return null;
53
+ }
54
+
55
+ try {
56
+ const content = fs.readFileSync(loopPath, 'utf8');
57
+ return JSON.parse(content);
58
+ } catch (err) {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ function getStatusColor(status) {
64
+ switch (status) {
65
+ case 'completed':
66
+ case 'done':
67
+ return colors.green;
68
+ case 'in_progress':
69
+ case 'in-progress':
70
+ return colors.yellow;
71
+ case 'blocked':
72
+ return colors.red;
73
+ case 'ready':
74
+ return colors.cyan;
75
+ default:
76
+ return colors.dim;
77
+ }
78
+ }
79
+
80
+ function getStatusSymbol(status) {
81
+ switch (status) {
82
+ case 'completed':
83
+ case 'done':
84
+ return '✓';
85
+ case 'in_progress':
86
+ case 'in-progress':
87
+ return '▶';
88
+ case 'blocked':
89
+ return '✗';
90
+ case 'ready':
91
+ return '○';
92
+ default:
93
+ return '·';
94
+ }
95
+ }
96
+
97
+ function formatStory(story, compact = false) {
98
+ const statusColor = getStatusColor(story.status);
99
+ const symbol = getStatusSymbol(story.status);
100
+ const id = story.id || story.story_id || 'Unknown';
101
+ const title = story.title || story.summary || 'Untitled';
102
+ const status = (story.status || 'unknown').replace(/_/g, ' ');
103
+ const owner = story.owner || '-';
104
+
105
+ if (compact) {
106
+ const truncTitle = title.length > 35 ? title.substring(0, 32) + '...' : title;
107
+ return ` ${statusColor}${symbol}${colors.reset} ${colors.bold}${id}${colors.reset} ${truncTitle}`;
108
+ }
109
+
110
+ return ` ${statusColor}${symbol}${colors.reset} ${colors.bold}${id}${colors.reset} ${title.substring(0, 50)}${title.length > 50 ? '...' : ''}
111
+ ${statusColor}${status}${colors.reset} ${colors.dim}│ Owner: ${owner}${colors.reset}`;
112
+ }
113
+
114
+ function drawProgressBar(percent, width = 20) {
115
+ const filled = Math.round((percent / 100) * width);
116
+ const empty = width - filled;
117
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
118
+
119
+ let color = colors.red;
120
+ if (percent >= 80) color = colors.green;
121
+ else if (percent >= 50) color = colors.yellow;
122
+ else if (percent >= 20) color = colors.cyan;
123
+
124
+ return `${color}${bar}${colors.reset} ${percent}%`;
125
+ }
126
+
127
+ async function showDashboard(options = {}) {
128
+ const version = options.version;
129
+ const compact = options.compact || false;
130
+
131
+ showHeader(version);
132
+
133
+ const status = await loadStatus();
134
+ const loopStatus = loadLoopStatus();
135
+
136
+ if (!status) {
137
+ console.log(
138
+ `${colors.dim} No status.json found. Run /agileflow:story to create stories.${colors.reset}`
139
+ );
140
+ console.log('');
141
+ return;
142
+ }
143
+
144
+ // Count stories by status
145
+ const stories = Object.values(status).filter(
146
+ s => s && typeof s === 'object' && (s.id || s.story_id)
147
+ );
148
+ const counts = {
149
+ in_progress: stories.filter(s => ['in_progress', 'in-progress'].includes(s.status)).length,
150
+ blocked: stories.filter(s => s.status === 'blocked').length,
151
+ ready: stories.filter(s => s.status === 'ready').length,
152
+ completed: stories.filter(s => ['completed', 'done'].includes(s.status)).length,
153
+ };
154
+
155
+ const total = stories.length;
156
+ const completionPct = total > 0 ? Math.round((counts.completed / total) * 100) : 0;
157
+
158
+ // Summary Section
159
+ console.log(`${colors.bold} SUMMARY${colors.reset}`);
160
+ console.log(` ${'─'.repeat(45)}`);
161
+ console.log(
162
+ ` ${colors.yellow}● In Progress:${colors.reset} ${counts.in_progress} ${colors.red}● Blocked:${colors.reset} ${counts.blocked} ${colors.cyan}● Ready:${colors.reset} ${counts.ready} ${colors.green}● Done:${colors.reset} ${counts.completed}`
163
+ );
164
+ console.log(` Progress: ${drawProgressBar(completionPct)}`);
165
+ console.log('');
166
+
167
+ // Loop Status (if active)
168
+ if (loopStatus && loopStatus.state && loopStatus.state !== 'idle') {
169
+ const stateColor =
170
+ loopStatus.state === 'running'
171
+ ? colors.green
172
+ : loopStatus.state === 'paused'
173
+ ? colors.yellow
174
+ : colors.dim;
175
+ console.log(`${colors.bold} ACTIVE LOOP${colors.reset}`);
176
+ console.log(` ${'─'.repeat(45)}`);
177
+ console.log(
178
+ ` ${stateColor}●${colors.reset} ${loopStatus.epic || '-'} / ${loopStatus.story || '-'}`
179
+ );
180
+ if (loopStatus.progress !== undefined) {
181
+ console.log(` Progress: ${drawProgressBar(loopStatus.progress)}`);
182
+ }
183
+ console.log('');
184
+ }
185
+
186
+ // In Progress Stories
187
+ const inProgressStories = stories.filter(s => ['in_progress', 'in-progress'].includes(s.status));
188
+ if (inProgressStories.length > 0) {
189
+ console.log(`${colors.bold} ${colors.yellow}IN PROGRESS${colors.reset}`);
190
+ console.log(` ${'─'.repeat(45)}`);
191
+ inProgressStories.forEach(story => {
192
+ console.log(formatStory(story, compact));
193
+ });
194
+ console.log('');
195
+ }
196
+
197
+ // Blocked Stories
198
+ const blockedStories = stories.filter(s => s.status === 'blocked');
199
+ if (blockedStories.length > 0) {
200
+ console.log(`${colors.bold} ${colors.red}BLOCKED${colors.reset}`);
201
+ console.log(` ${'─'.repeat(45)}`);
202
+ blockedStories.forEach(story => {
203
+ console.log(formatStory(story, compact));
204
+ });
205
+ console.log('');
206
+ }
207
+
208
+ // Ready Stories (up to 5)
209
+ const readyStories = stories.filter(s => s.status === 'ready').slice(0, 5);
210
+ if (readyStories.length > 0) {
211
+ const moreReady = counts.ready > 5 ? ` ${colors.dim}(+${counts.ready - 5} more)${colors.reset}` : '';
212
+ console.log(`${colors.bold} ${colors.cyan}READY${colors.reset}${moreReady}`);
213
+ console.log(` ${'─'.repeat(45)}`);
214
+ readyStories.forEach(story => {
215
+ console.log(formatStory(story, compact));
216
+ });
217
+ console.log('');
218
+ }
219
+
220
+ // Completed Stories (last 3)
221
+ const completedStories = stories
222
+ .filter(s => ['completed', 'done'].includes(s.status))
223
+ .slice(-3)
224
+ .reverse();
225
+ if (completedStories.length > 0) {
226
+ const moreCompleted =
227
+ counts.completed > 3 ? ` ${colors.dim}(+${counts.completed - 3} more)${colors.reset}` : '';
228
+ console.log(`${colors.bold} ${colors.green}RECENTLY COMPLETED${colors.reset}${moreCompleted}`);
229
+ console.log(` ${'─'.repeat(45)}`);
230
+ completedStories.forEach(story => {
231
+ console.log(formatStory(story, compact));
232
+ });
233
+ console.log('');
234
+ }
235
+
236
+ // Footer
237
+ console.log(`${colors.dim} ─────────────────────────────────────────────${colors.reset}`);
238
+ console.log(`${colors.dim} /agileflow:board Interactive kanban view${colors.reset}`);
239
+ console.log(`${colors.dim} /agileflow:story:list Full story listing${colors.reset}`);
240
+ console.log(`${colors.dim} npx agileflow tui Live updating dashboard${colors.reset}`);
241
+ console.log('');
242
+ }
243
+
244
+ async function main(options = {}) {
245
+ const args = process.argv.slice(2);
246
+
247
+ // Check for help flag
248
+ if (args.includes('--help') || args.includes('-h')) {
249
+ console.log('');
250
+ console.log(`${colors.bold}AgileFlow Dashboard${colors.reset}`);
251
+ console.log('');
252
+ console.log(`${colors.bold}Usage:${colors.reset}`);
253
+ console.log(' npx agileflow start Show dashboard (static)');
254
+ console.log(' npx agileflow start --compact Compact view');
255
+ console.log(' npx agileflow start --help Show this help');
256
+ console.log('');
257
+ console.log(`${colors.bold}See also:${colors.reset}`);
258
+ console.log(' npx agileflow tui Live updating dashboard');
259
+ console.log('');
260
+ return;
261
+ }
262
+
263
+ const compact = args.includes('--compact') || args.includes('-c');
264
+
265
+ // Get version from package.json
266
+ let version;
267
+ try {
268
+ const pkgPath = path.join(__dirname, '..', '..', '..', 'package.json');
269
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
270
+ version = pkg.version;
271
+ } catch (e) {
272
+ version = null;
273
+ }
274
+
275
+ await showDashboard({ version, compact, ...options });
276
+ }
277
+
278
+ // Run directly if executed as script
279
+ if (require.main === module) {
280
+ main().catch(err => {
281
+ console.error(`${colors.red}Error:${colors.reset}`, err.message);
282
+ process.exit(1);
283
+ });
284
+ }
285
+
286
+ module.exports = {
287
+ name: 'start',
288
+ description: 'Show project dashboard (static view)',
289
+ action: main,
290
+ };