agileflow 2.90.5 → 2.90.6
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 +5 -0
- package/package.json +1 -1
- package/tools/cli/commands/tui.js +279 -51
- package/tools/cli/commands/start.js +0 -290
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,59 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* AgileFlow
|
|
4
|
+
* AgileFlow TUI Dashboard
|
|
5
|
+
*
|
|
6
|
+
* Static dashboard view - prints once and exits.
|
|
7
|
+
* No continuous refresh, no screen clearing, scrollable output.
|
|
3
8
|
*
|
|
4
|
-
*
|
|
9
|
+
* Usage: npx agileflow tui
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
10
|
-
|
|
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 =
|
|
212
|
+
counts.ready > 5 ? ` ${colors.dim}(+${counts.ready - 5} more)${colors.reset}` : '';
|
|
213
|
+
console.log(`${colors.bold} ${colors.cyan}READY${colors.reset}${moreReady}`);
|
|
214
|
+
console.log(` ${'─'.repeat(45)}`);
|
|
215
|
+
readyStories.forEach(story => {
|
|
216
|
+
console.log(formatStory(story, compact));
|
|
217
|
+
});
|
|
218
|
+
console.log('');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Completed Stories (last 3)
|
|
222
|
+
const completedStories = stories
|
|
223
|
+
.filter(s => ['completed', 'done'].includes(s.status))
|
|
224
|
+
.slice(-3)
|
|
225
|
+
.reverse();
|
|
226
|
+
if (completedStories.length > 0) {
|
|
227
|
+
const moreCompleted =
|
|
228
|
+
counts.completed > 3 ? ` ${colors.dim}(+${counts.completed - 3} more)${colors.reset}` : '';
|
|
229
|
+
console.log(`${colors.bold} ${colors.green}RECENTLY COMPLETED${colors.reset}${moreCompleted}`);
|
|
230
|
+
console.log(` ${'─'.repeat(45)}`);
|
|
231
|
+
completedStories.forEach(story => {
|
|
232
|
+
console.log(formatStory(story, compact));
|
|
233
|
+
});
|
|
234
|
+
console.log('');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Footer
|
|
238
|
+
console.log(`${colors.dim} ─────────────────────────────────────────────${colors.reset}`);
|
|
239
|
+
console.log(`${colors.dim} /agileflow:board Interactive kanban view${colors.reset}`);
|
|
240
|
+
console.log(`${colors.dim} /agileflow:story:list Full story listing${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 TUI Dashboard${colors.reset}`);
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(`${colors.bold}Usage:${colors.reset}`);
|
|
253
|
+
console.log(' npx agileflow tui Show dashboard');
|
|
254
|
+
console.log(' npx agileflow tui --compact Compact view');
|
|
255
|
+
console.log(' npx agileflow tui --help Show this help');
|
|
256
|
+
console.log('');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const compact = args.includes('--compact') || args.includes('-c');
|
|
261
|
+
|
|
262
|
+
// Get version from package.json
|
|
263
|
+
let version;
|
|
264
|
+
try {
|
|
265
|
+
const pkgPath = path.join(__dirname, '..', '..', '..', 'package.json');
|
|
266
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
267
|
+
version = pkg.version;
|
|
268
|
+
} catch (e) {
|
|
269
|
+
version = null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
await showDashboard({ version, compact, ...options });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Run directly if executed as script
|
|
276
|
+
if (require.main === module) {
|
|
277
|
+
main().catch(err => {
|
|
278
|
+
console.error(`${colors.red}Error:${colors.reset}`, err.message);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
11
282
|
|
|
12
283
|
module.exports = {
|
|
13
284
|
name: 'tui',
|
|
14
|
-
description: '
|
|
15
|
-
|
|
16
|
-
action: async options => {
|
|
17
|
-
try {
|
|
18
|
-
const directory = path.resolve(options.directory || '.');
|
|
19
|
-
|
|
20
|
-
// Check if AgileFlow is installed
|
|
21
|
-
const agileflowDir = path.join(directory, '.agileflow');
|
|
22
|
-
if (!(await fs.pathExists(agileflowDir))) {
|
|
23
|
-
console.error(chalk.red('Error:'), 'AgileFlow is not installed in this directory');
|
|
24
|
-
console.log(chalk.dim(`Run 'npx agileflow setup' first\n`));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Find the TUI script
|
|
29
|
-
const tuiScript = path.join(__dirname, '../../..', 'scripts', 'tui', 'index.js');
|
|
30
|
-
|
|
31
|
-
if (!(await fs.pathExists(tuiScript))) {
|
|
32
|
-
console.error(chalk.red('Error:'), 'TUI script not found');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Spawn the TUI process with inherited stdio for interactive mode
|
|
37
|
-
const child = spawn('node', [tuiScript], {
|
|
38
|
-
cwd: directory,
|
|
39
|
-
stdio: 'inherit',
|
|
40
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
child.on('error', err => {
|
|
44
|
-
console.error(chalk.red('Error launching TUI:'), err.message);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
child.on('exit', code => {
|
|
49
|
-
process.exit(code || 0);
|
|
50
|
-
});
|
|
51
|
-
} catch (err) {
|
|
52
|
-
console.error(chalk.red('Error:'), err.message);
|
|
53
|
-
if (process.env.DEBUG) {
|
|
54
|
-
console.error(err.stack);
|
|
55
|
-
}
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
},
|
|
285
|
+
description: 'Show project dashboard',
|
|
286
|
+
action: main,
|
|
59
287
|
};
|
|
@@ -1,290 +0,0 @@
|
|
|
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
|
-
};
|