agileflow 2.89.3 → 2.90.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/lib/placeholder-registry.js +617 -0
  3. package/lib/smart-json-file.js +228 -1
  4. package/lib/table-formatter.js +519 -0
  5. package/lib/transient-status.js +374 -0
  6. package/lib/ui-manager.js +612 -0
  7. package/lib/validate-args.js +213 -0
  8. package/lib/validate-names.js +143 -0
  9. package/lib/validate-paths.js +434 -0
  10. package/lib/validate.js +37 -737
  11. package/package.json +3 -1
  12. package/scripts/check-update.js +17 -3
  13. package/scripts/lib/sessionRegistry.js +678 -0
  14. package/scripts/session-manager.js +77 -10
  15. package/scripts/tui/App.js +151 -0
  16. package/scripts/tui/index.js +31 -0
  17. package/scripts/tui/lib/crashRecovery.js +304 -0
  18. package/scripts/tui/lib/eventStream.js +309 -0
  19. package/scripts/tui/lib/keyboard.js +261 -0
  20. package/scripts/tui/lib/loopControl.js +371 -0
  21. package/scripts/tui/panels/OutputPanel.js +242 -0
  22. package/scripts/tui/panels/SessionPanel.js +170 -0
  23. package/scripts/tui/panels/TracePanel.js +298 -0
  24. package/scripts/tui/simple-tui.js +390 -0
  25. package/tools/cli/commands/config.js +7 -31
  26. package/tools/cli/commands/doctor.js +28 -39
  27. package/tools/cli/commands/list.js +47 -35
  28. package/tools/cli/commands/status.js +20 -38
  29. package/tools/cli/commands/tui.js +59 -0
  30. package/tools/cli/commands/uninstall.js +12 -39
  31. package/tools/cli/installers/core/installer.js +13 -0
  32. package/tools/cli/lib/command-context.js +382 -0
  33. package/tools/cli/lib/config-manager.js +394 -0
  34. package/tools/cli/lib/ide-registry.js +186 -0
  35. package/tools/cli/lib/npm-utils.js +17 -3
  36. package/tools/cli/lib/self-update.js +148 -0
  37. package/tools/cli/lib/validation-middleware.js +491 -0
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ const React = require('react');
4
+ const { Box, Text } = require('ink');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ // Get session-manager functions
9
+ let getSessions;
10
+ try {
11
+ const sessionManager = require('../../session-manager');
12
+ getSessions = sessionManager.getSessions;
13
+ } catch (e) {
14
+ // Fallback if module not found
15
+ getSessions = () => ({ sessions: [], cleaned: 0 });
16
+ }
17
+
18
+ /**
19
+ * Get thread type color
20
+ */
21
+ function getThreadColor(threadType) {
22
+ const colors = {
23
+ base: 'green',
24
+ parallel: 'cyan',
25
+ chained: 'yellow',
26
+ fusion: 'magenta',
27
+ big: 'blue',
28
+ long: 'white',
29
+ };
30
+ return colors[threadType] || 'gray';
31
+ }
32
+
33
+ /**
34
+ * Get status color
35
+ */
36
+ function getStatusColor(active, current) {
37
+ if (current) return 'green';
38
+ if (active) return 'cyan';
39
+ return 'gray';
40
+ }
41
+
42
+ /**
43
+ * Format session for display
44
+ */
45
+ function formatSession(session) {
46
+ const { id, branch, story, nickname, thread_type, active, current, is_main } = session;
47
+
48
+ // Build display name
49
+ let displayName = `Session ${id}`;
50
+ if (nickname) displayName += ` "${nickname}"`;
51
+ if (is_main) displayName += ' [main]';
52
+
53
+ return {
54
+ displayName,
55
+ branch: branch || 'unknown',
56
+ story: story || 'none',
57
+ threadType: thread_type || 'base',
58
+ active,
59
+ current,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * SessionRow component - displays a single session
65
+ */
66
+ function SessionRow({ session }) {
67
+ const fmt = formatSession(session);
68
+ const statusColor = getStatusColor(fmt.active, fmt.current);
69
+ const threadColor = getThreadColor(fmt.threadType);
70
+
71
+ // Current session indicator
72
+ const indicator = fmt.current ? '▶' : ' ';
73
+ const indicatorColor = fmt.current ? 'green' : 'gray';
74
+
75
+ return React.createElement(
76
+ Box,
77
+ { flexDirection: 'column', marginBottom: 1 },
78
+ // Session name row
79
+ React.createElement(
80
+ Box,
81
+ null,
82
+ React.createElement(Text, { color: indicatorColor }, indicator + ' '),
83
+ React.createElement(Text, { bold: true, color: statusColor }, fmt.displayName),
84
+ React.createElement(Text, { dimColor: true }, ' ['),
85
+ React.createElement(Text, { color: threadColor }, fmt.threadType),
86
+ React.createElement(Text, { dimColor: true }, ']')
87
+ ),
88
+ // Branch and story
89
+ React.createElement(
90
+ Box,
91
+ { paddingLeft: 3 },
92
+ React.createElement(Text, { dimColor: true }, 'Branch: '),
93
+ React.createElement(Text, { color: 'cyan' }, fmt.branch)
94
+ ),
95
+ React.createElement(
96
+ Box,
97
+ { paddingLeft: 3 },
98
+ React.createElement(Text, { dimColor: true }, 'Story: '),
99
+ React.createElement(Text, { color: fmt.story === 'none' ? 'gray' : 'yellow' }, fmt.story)
100
+ )
101
+ );
102
+ }
103
+
104
+ /**
105
+ * SessionPanel component - displays all sessions
106
+ */
107
+ function SessionPanel({ refreshInterval = 5000 }) {
108
+ const [sessions, setSessions] = React.useState([]);
109
+ const [lastUpdate, setLastUpdate] = React.useState(new Date());
110
+
111
+ // Load sessions on mount and on interval
112
+ React.useEffect(() => {
113
+ function loadSessions() {
114
+ try {
115
+ const result = getSessions();
116
+ setSessions(result.sessions || []);
117
+ setLastUpdate(new Date());
118
+ } catch (e) {
119
+ // Silently handle errors
120
+ setSessions([]);
121
+ }
122
+ }
123
+
124
+ // Initial load
125
+ loadSessions();
126
+
127
+ // Refresh periodically
128
+ const interval = setInterval(loadSessions, refreshInterval);
129
+
130
+ return () => clearInterval(interval);
131
+ }, [refreshInterval]);
132
+
133
+ // Empty state
134
+ if (sessions.length === 0) {
135
+ return React.createElement(
136
+ Box,
137
+ { flexDirection: 'column', borderStyle: 'single', borderColor: 'gray', padding: 1 },
138
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'SESSIONS'),
139
+ React.createElement(Text, { dimColor: true, italic: true }, 'No active sessions')
140
+ );
141
+ }
142
+
143
+ return React.createElement(
144
+ Box,
145
+ { flexDirection: 'column', borderStyle: 'single', borderColor: 'gray', padding: 1 },
146
+ // Header
147
+ React.createElement(
148
+ Box,
149
+ { marginBottom: 1 },
150
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'SESSIONS'),
151
+ React.createElement(Text, { dimColor: true }, ` (${sessions.length})`)
152
+ ),
153
+ // Session list
154
+ ...sessions.map((session, index) =>
155
+ React.createElement(SessionRow, { key: session.id || index, session })
156
+ ),
157
+ // Footer with refresh info
158
+ React.createElement(
159
+ Box,
160
+ { marginTop: 1 },
161
+ React.createElement(
162
+ Text,
163
+ { dimColor: true, italic: true },
164
+ `Updated: ${lastUpdate.toLocaleTimeString()}`
165
+ )
166
+ )
167
+ );
168
+ }
169
+
170
+ module.exports = { SessionPanel, SessionRow, formatSession, getThreadColor };
@@ -0,0 +1,298 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Trace Panel - Quality gate and iteration tracking
5
+ *
6
+ * Shows active agent loops with:
7
+ * - Current gate (tests, coverage, lint, types, visual)
8
+ * - Iteration count and progress
9
+ * - Pass/fail status with color coding
10
+ * - Progress bars for coverage
11
+ */
12
+
13
+ const React = require('react');
14
+ const { Box, Text } = require('ink');
15
+ const { getLoopStatus } = require('../lib/loopControl');
16
+
17
+ /**
18
+ * Quality gate definitions
19
+ */
20
+ const QUALITY_GATES = {
21
+ tests: { name: 'Tests', color: 'green', icon: '✓' },
22
+ coverage: { name: 'Coverage', color: 'magenta', icon: '%' },
23
+ lint: { name: 'Lint', color: 'yellow', icon: '◆' },
24
+ types: { name: 'Types', color: 'blue', icon: '⬡' },
25
+ visual: { name: 'Visual', color: 'cyan', icon: '◉' },
26
+ };
27
+
28
+ /**
29
+ * Get status color
30
+ */
31
+ function getStatusColor(status) {
32
+ switch (status) {
33
+ case 'passed':
34
+ case 'complete':
35
+ case true:
36
+ return 'green';
37
+ case 'failed':
38
+ case 'error':
39
+ case false:
40
+ return 'red';
41
+ case 'running':
42
+ case 'in_progress':
43
+ return 'cyan';
44
+ case 'pending':
45
+ case 'waiting':
46
+ return 'gray';
47
+ default:
48
+ return 'white';
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Get status symbol
54
+ */
55
+ function getStatusSymbol(status) {
56
+ switch (status) {
57
+ case 'passed':
58
+ case 'complete':
59
+ case true:
60
+ return '✓';
61
+ case 'failed':
62
+ case 'error':
63
+ case false:
64
+ return '✗';
65
+ case 'running':
66
+ case 'in_progress':
67
+ return '↻';
68
+ case 'pending':
69
+ case 'waiting':
70
+ return '○';
71
+ case 'paused':
72
+ return '⏸';
73
+ default:
74
+ return '•';
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Progress bar component
80
+ */
81
+ function ProgressBar({ value, max = 100, width = 20, fillChar = '█', emptyChar = '░' }) {
82
+ const percent = Math.min(100, Math.max(0, (value / max) * 100));
83
+ const filled = Math.round((percent / 100) * width);
84
+ const empty = width - filled;
85
+
86
+ const filledStr = fillChar.repeat(filled);
87
+ const emptyStr = emptyChar.repeat(empty);
88
+
89
+ // Color based on progress
90
+ let color = 'red';
91
+ if (percent >= 80) color = 'green';
92
+ else if (percent >= 50) color = 'yellow';
93
+
94
+ return React.createElement(
95
+ Box,
96
+ { flexDirection: 'row' },
97
+ React.createElement(Text, { color }, filledStr),
98
+ React.createElement(Text, { dimColor: true }, emptyStr),
99
+ React.createElement(Text, { dimColor: true }, ` ${percent.toFixed(0)}%`)
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Quality gate row component
105
+ */
106
+ function GateRow({ gate, status, value }) {
107
+ const gateConfig = QUALITY_GATES[gate] || { name: gate, color: 'white', icon: '•' };
108
+ const statusColor = getStatusColor(status);
109
+ const statusSymbol = getStatusSymbol(status);
110
+
111
+ return React.createElement(
112
+ Box,
113
+ { flexDirection: 'row', marginLeft: 2 },
114
+ React.createElement(Text, { color: gateConfig.color }, `${gateConfig.icon} `),
115
+ React.createElement(Text, { color: statusColor }, `${statusSymbol} `),
116
+ React.createElement(Text, null, `${gateConfig.name}`),
117
+ value !== undefined && React.createElement(Text, { dimColor: true }, `: ${value}`)
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Active loop trace component
123
+ */
124
+ function LoopTrace({ loopStatus }) {
125
+ if (!loopStatus || !loopStatus.active) {
126
+ return React.createElement(Text, { dimColor: true, italic: true }, 'No active loop');
127
+ }
128
+
129
+ const {
130
+ epic,
131
+ currentStory,
132
+ iteration,
133
+ maxIterations,
134
+ paused,
135
+ coverageMode,
136
+ coverageThreshold,
137
+ coverageCurrent,
138
+ visualMode,
139
+ } = loopStatus;
140
+
141
+ // Build gates list
142
+ const gates = [];
143
+
144
+ // Tests always present
145
+ gates.push({
146
+ gate: 'tests',
147
+ status: 'running',
148
+ value: null,
149
+ });
150
+
151
+ // Coverage if enabled
152
+ if (coverageMode) {
153
+ gates.push({
154
+ gate: 'coverage',
155
+ status: coverageCurrent >= coverageThreshold ? 'passed' : 'running',
156
+ value: `${coverageCurrent?.toFixed(1) || 0}% / ${coverageThreshold}%`,
157
+ });
158
+ }
159
+
160
+ // Visual if enabled
161
+ if (visualMode) {
162
+ gates.push({
163
+ gate: 'visual',
164
+ status: 'pending',
165
+ value: null,
166
+ });
167
+ }
168
+
169
+ return React.createElement(
170
+ Box,
171
+ { flexDirection: 'column' },
172
+ // Loop header
173
+ React.createElement(
174
+ Box,
175
+ { flexDirection: 'row', marginBottom: 1 },
176
+ React.createElement(
177
+ Text,
178
+ { bold: true, color: paused ? 'yellow' : 'green' },
179
+ paused ? '⏸ PAUSED' : '▶ RUNNING'
180
+ ),
181
+ React.createElement(Text, { dimColor: true }, ` • ${epic}`)
182
+ ),
183
+ // Current story
184
+ React.createElement(
185
+ Box,
186
+ { flexDirection: 'row' },
187
+ React.createElement(Text, { dimColor: true }, 'Story: '),
188
+ React.createElement(Text, { color: 'cyan' }, currentStory || 'none')
189
+ ),
190
+ // Iteration progress
191
+ React.createElement(
192
+ Box,
193
+ { flexDirection: 'row', marginY: 1 },
194
+ React.createElement(Text, { dimColor: true }, 'Iteration: '),
195
+ React.createElement(ProgressBar, {
196
+ value: iteration || 0,
197
+ max: maxIterations || 20,
198
+ width: 15,
199
+ }),
200
+ React.createElement(Text, { dimColor: true }, ` ${iteration || 0}/${maxIterations || 20}`)
201
+ ),
202
+ // Coverage progress (if enabled)
203
+ coverageMode &&
204
+ React.createElement(
205
+ Box,
206
+ { flexDirection: 'row', marginBottom: 1 },
207
+ React.createElement(Text, { dimColor: true }, 'Coverage: '),
208
+ React.createElement(ProgressBar, { value: coverageCurrent || 0, max: 100, width: 15 })
209
+ ),
210
+ // Quality gates
211
+ React.createElement(
212
+ Box,
213
+ { flexDirection: 'column', marginTop: 1 },
214
+ React.createElement(Text, { dimColor: true }, 'Quality Gates:'),
215
+ gates.map((g, i) => React.createElement(GateRow, { key: `gate-${i}`, ...g }))
216
+ )
217
+ );
218
+ }
219
+
220
+ /**
221
+ * Main Trace Panel component
222
+ */
223
+ function TracePanel({ visible = true, refreshInterval = 2000 }) {
224
+ const [loopStatus, setLoopStatus] = React.useState(null);
225
+
226
+ React.useEffect(() => {
227
+ if (!visible) return;
228
+
229
+ const loadStatus = () => {
230
+ try {
231
+ const status = getLoopStatus();
232
+ setLoopStatus(status);
233
+ } catch (e) {
234
+ // Ignore errors
235
+ }
236
+ };
237
+
238
+ loadStatus();
239
+ const interval = setInterval(loadStatus, refreshInterval);
240
+
241
+ return () => clearInterval(interval);
242
+ }, [visible, refreshInterval]);
243
+
244
+ if (!visible) {
245
+ return null;
246
+ }
247
+
248
+ return React.createElement(
249
+ Box,
250
+ {
251
+ flexDirection: 'column',
252
+ borderStyle: 'single',
253
+ borderColor: 'blue',
254
+ padding: 1,
255
+ },
256
+ // Header
257
+ React.createElement(
258
+ Box,
259
+ { marginBottom: 1 },
260
+ React.createElement(Text, { bold: true, color: 'blue' }, 'AGENT TRACE'),
261
+ React.createElement(Text, { dimColor: true }, ' (T to toggle)')
262
+ ),
263
+ // Loop trace
264
+ React.createElement(LoopTrace, { loopStatus })
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Compact trace indicator (for status bar)
270
+ */
271
+ function CompactTrace({ loopStatus }) {
272
+ if (!loopStatus || !loopStatus.active) {
273
+ return React.createElement(Text, { dimColor: true }, 'Loop: inactive');
274
+ }
275
+
276
+ const { iteration, maxIterations, paused, currentStory } = loopStatus;
277
+ const statusIcon = paused ? '⏸' : '▶';
278
+ const statusColor = paused ? 'yellow' : 'green';
279
+
280
+ return React.createElement(
281
+ Box,
282
+ { flexDirection: 'row' },
283
+ React.createElement(Text, { color: statusColor }, statusIcon),
284
+ React.createElement(Text, null, ` ${currentStory || '?'} `),
285
+ React.createElement(Text, { dimColor: true }, `[${iteration || 0}/${maxIterations || 20}]`)
286
+ );
287
+ }
288
+
289
+ module.exports = {
290
+ TracePanel,
291
+ LoopTrace,
292
+ GateRow,
293
+ ProgressBar,
294
+ CompactTrace,
295
+ getStatusColor,
296
+ getStatusSymbol,
297
+ QUALITY_GATES,
298
+ };