agileflow 2.90.7 → 2.92.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +818 -0
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate-names.js +3 -3
  22. package/lib/validate.js +116 -52
  23. package/package.json +4 -1
  24. package/scripts/af +34 -0
  25. package/scripts/agent-loop.js +63 -9
  26. package/scripts/agileflow-configure.js +2 -2
  27. package/scripts/agileflow-welcome.js +435 -23
  28. package/scripts/archive-completed-stories.sh +57 -11
  29. package/scripts/claude-tmux.sh +102 -0
  30. package/scripts/damage-control-bash.js +3 -70
  31. package/scripts/damage-control-edit.js +3 -20
  32. package/scripts/damage-control-write.js +3 -20
  33. package/scripts/dependency-check.js +310 -0
  34. package/scripts/get-env.js +11 -4
  35. package/scripts/lib/configure-detect.js +23 -1
  36. package/scripts/lib/configure-features.js +43 -2
  37. package/scripts/lib/context-formatter.js +771 -0
  38. package/scripts/lib/context-loader.js +699 -0
  39. package/scripts/lib/damage-control-utils.js +107 -0
  40. package/scripts/lib/json-utils.sh +162 -0
  41. package/scripts/lib/state-migrator.js +353 -0
  42. package/scripts/lib/story-state-machine.js +437 -0
  43. package/scripts/obtain-context.js +118 -1048
  44. package/scripts/pre-push-check.sh +46 -0
  45. package/scripts/precompact-context.sh +36 -11
  46. package/scripts/query-codebase.js +538 -0
  47. package/scripts/ralph-loop.js +5 -5
  48. package/scripts/session-manager.js +220 -42
  49. package/scripts/spawn-parallel.js +651 -0
  50. package/scripts/tui/blessed/data/watcher.js +180 -0
  51. package/scripts/tui/blessed/index.js +244 -0
  52. package/scripts/tui/blessed/panels/output.js +101 -0
  53. package/scripts/tui/blessed/panels/sessions.js +150 -0
  54. package/scripts/tui/blessed/panels/trace.js +97 -0
  55. package/scripts/tui/blessed/ui/help.js +77 -0
  56. package/scripts/tui/blessed/ui/screen.js +52 -0
  57. package/scripts/tui/blessed/ui/statusbar.js +47 -0
  58. package/scripts/tui/blessed/ui/tabbar.js +99 -0
  59. package/scripts/tui/index.js +38 -30
  60. package/scripts/validators/README.md +143 -0
  61. package/scripts/validators/component-validator.js +239 -0
  62. package/scripts/validators/json-schema-validator.js +186 -0
  63. package/scripts/validators/markdown-validator.js +152 -0
  64. package/scripts/validators/migration-validator.js +129 -0
  65. package/scripts/validators/security-validator.js +380 -0
  66. package/scripts/validators/story-format-validator.js +197 -0
  67. package/scripts/validators/test-result-validator.js +114 -0
  68. package/scripts/validators/workflow-validator.js +247 -0
  69. package/src/core/agents/accessibility.md +6 -0
  70. package/src/core/agents/adr-writer.md +6 -0
  71. package/src/core/agents/analytics.md +6 -0
  72. package/src/core/agents/api.md +6 -0
  73. package/src/core/agents/ci.md +6 -0
  74. package/src/core/agents/codebase-query.md +261 -0
  75. package/src/core/agents/compliance.md +6 -0
  76. package/src/core/agents/configuration-damage-control.md +6 -0
  77. package/src/core/agents/configuration-visual-e2e.md +6 -0
  78. package/src/core/agents/database.md +10 -0
  79. package/src/core/agents/datamigration.md +6 -0
  80. package/src/core/agents/design.md +6 -0
  81. package/src/core/agents/devops.md +6 -0
  82. package/src/core/agents/documentation.md +6 -0
  83. package/src/core/agents/epic-planner.md +6 -0
  84. package/src/core/agents/integrations.md +6 -0
  85. package/src/core/agents/mentor.md +6 -0
  86. package/src/core/agents/mobile.md +6 -0
  87. package/src/core/agents/monitoring.md +6 -0
  88. package/src/core/agents/multi-expert.md +6 -0
  89. package/src/core/agents/performance.md +6 -0
  90. package/src/core/agents/product.md +6 -0
  91. package/src/core/agents/qa.md +6 -0
  92. package/src/core/agents/readme-updater.md +6 -0
  93. package/src/core/agents/refactor.md +6 -0
  94. package/src/core/agents/research.md +6 -0
  95. package/src/core/agents/security.md +6 -0
  96. package/src/core/agents/testing.md +10 -0
  97. package/src/core/agents/ui.md +6 -0
  98. package/src/core/commands/adr.md +114 -0
  99. package/src/core/commands/agent.md +120 -0
  100. package/src/core/commands/assign.md +145 -0
  101. package/src/core/commands/audit.md +401 -0
  102. package/src/core/commands/babysit.md +32 -5
  103. package/src/core/commands/board.md +1 -0
  104. package/src/core/commands/changelog.md +118 -0
  105. package/src/core/commands/configure.md +42 -6
  106. package/src/core/commands/diagnose.md +114 -0
  107. package/src/core/commands/epic.md +205 -1
  108. package/src/core/commands/handoff.md +128 -0
  109. package/src/core/commands/help.md +76 -0
  110. package/src/core/commands/metrics.md +1 -0
  111. package/src/core/commands/pr.md +96 -0
  112. package/src/core/commands/research/analyze.md +1 -0
  113. package/src/core/commands/research/ask.md +2 -0
  114. package/src/core/commands/research/import.md +1 -0
  115. package/src/core/commands/research/list.md +2 -0
  116. package/src/core/commands/research/synthesize.md +584 -0
  117. package/src/core/commands/research/view.md +2 -0
  118. package/src/core/commands/roadmap/analyze.md +400 -0
  119. package/src/core/commands/session/new.md +113 -6
  120. package/src/core/commands/session/spawn.md +197 -0
  121. package/src/core/commands/sprint.md +22 -0
  122. package/src/core/commands/status.md +200 -1
  123. package/src/core/commands/story/list.md +9 -9
  124. package/src/core/commands/story/view.md +1 -0
  125. package/src/core/commands/story.md +143 -4
  126. package/src/core/experts/codebase-query/expertise.yaml +190 -0
  127. package/src/core/experts/codebase-query/question.md +73 -0
  128. package/src/core/experts/codebase-query/self-improve.md +105 -0
  129. package/src/core/templates/agileflow-metadata.json +55 -2
  130. package/src/core/templates/plan-template.md +125 -0
  131. package/src/core/templates/story-lifecycle.md +213 -0
  132. package/src/core/templates/story-template.md +4 -0
  133. package/src/core/templates/tdd-test-template.js +241 -0
  134. package/tools/cli/commands/setup.js +86 -0
  135. package/tools/cli/installers/core/installer.js +94 -0
  136. package/tools/cli/installers/ide/_base-ide.js +20 -11
  137. package/tools/cli/installers/ide/codex.js +29 -47
  138. package/tools/cli/lib/config-manager.js +17 -2
  139. package/tools/cli/lib/content-transformer.js +271 -0
  140. package/tools/cli/lib/error-handler.js +14 -22
  141. package/tools/cli/lib/ide-error-factory.js +421 -0
  142. package/tools/cli/lib/ide-health-monitor.js +364 -0
  143. package/tools/cli/lib/ide-registry.js +114 -1
  144. package/tools/cli/lib/ui.js +14 -25
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const chokidar = require('chokidar');
6
+
7
+ // Import centralized path utilities
8
+ const { getStatusPath, getBusLogPath, getSessionStatePath } = require('../../../lib/paths');
9
+
10
+ /**
11
+ * DataWatcher - watches status.json and log files for changes
12
+ * Triggers refresh callback when data updates
13
+ */
14
+ class DataWatcher {
15
+ constructor(state, onUpdate) {
16
+ this.state = state;
17
+ this.onUpdate = onUpdate;
18
+ this.watcher = null;
19
+ this.cwd = process.cwd();
20
+
21
+ // Paths to watch (using centralized path utilities)
22
+ this.statusPath = getStatusPath(this.cwd);
23
+ this.logPath = getBusLogPath(this.cwd);
24
+ this.sessionStatePath = getSessionStatePath(this.cwd);
25
+ }
26
+
27
+ start() {
28
+ // Initial load
29
+ this.loadStatus();
30
+ this.loadLogs();
31
+ this.loadSessionState();
32
+
33
+ // Set up file watcher
34
+ const watchPaths = [this.statusPath, this.logPath, this.sessionStatePath].filter(p => {
35
+ const dir = path.dirname(p);
36
+ return fs.existsSync(dir);
37
+ });
38
+
39
+ if (watchPaths.length === 0) {
40
+ // No paths to watch yet, check periodically
41
+ this.pollInterval = setInterval(() => {
42
+ this.loadStatus();
43
+ this.loadLogs();
44
+ this.loadSessionState();
45
+ this.onUpdate();
46
+ }, 2000);
47
+ return;
48
+ }
49
+
50
+ this.watcher = chokidar.watch(watchPaths, {
51
+ persistent: true,
52
+ ignoreInitial: true,
53
+ usePolling: false,
54
+ awaitWriteFinish: {
55
+ stabilityThreshold: 100,
56
+ pollInterval: 50,
57
+ },
58
+ });
59
+
60
+ this.watcher.on('change', filePath => {
61
+ if (filePath.endsWith('status.json')) {
62
+ this.loadStatus();
63
+ } else if (filePath.endsWith('log.jsonl')) {
64
+ this.loadLogs();
65
+ } else if (filePath.endsWith('session-state.json')) {
66
+ this.loadSessionState();
67
+ }
68
+ this.onUpdate();
69
+ });
70
+
71
+ this.watcher.on('error', err => {
72
+ // Silently handle watcher errors
73
+ });
74
+ }
75
+
76
+ loadStatus() {
77
+ try {
78
+ if (fs.existsSync(this.statusPath)) {
79
+ const content = fs.readFileSync(this.statusPath, 'utf8');
80
+ const data = JSON.parse(content);
81
+
82
+ // Extract stories with in_progress status as "sessions"
83
+ const stories = data.stories || [];
84
+ this.state.sessions = stories
85
+ .filter(s => s.status === 'in_progress')
86
+ .map((s, i) => ({
87
+ id: `S-${i + 1}`,
88
+ story: s.id || s.title || 'Unknown',
89
+ status: 'active',
90
+ duration: this.formatDuration(s.started_at),
91
+ progress: s.progress || '--',
92
+ }));
93
+
94
+ // If no active stories, show all stories summary
95
+ if (this.state.sessions.length === 0) {
96
+ const ready = stories.filter(s => s.status === 'ready').length;
97
+ const completed = stories.filter(s => s.status === 'completed').length;
98
+ this.state.sessions = [
99
+ {
100
+ id: '--',
101
+ story: `${ready} ready, ${completed} completed`,
102
+ status: 'idle',
103
+ duration: '--',
104
+ progress: '--',
105
+ },
106
+ ];
107
+ }
108
+ }
109
+ } catch (err) {
110
+ // Ignore parse errors
111
+ }
112
+ }
113
+
114
+ loadLogs() {
115
+ try {
116
+ if (fs.existsSync(this.logPath)) {
117
+ const content = fs.readFileSync(this.logPath, 'utf8');
118
+ const lines = content.trim().split('\n').filter(Boolean);
119
+
120
+ // Take last 100 lines
121
+ this.state.logs = lines.slice(-100).map(line => {
122
+ try {
123
+ return JSON.parse(line);
124
+ } catch {
125
+ return { message: line, level: 'info' };
126
+ }
127
+ });
128
+ }
129
+ } catch (err) {
130
+ // Ignore read errors
131
+ }
132
+ }
133
+
134
+ loadSessionState() {
135
+ try {
136
+ if (fs.existsSync(this.sessionStatePath)) {
137
+ const content = fs.readFileSync(this.sessionStatePath, 'utf8');
138
+ const data = JSON.parse(content);
139
+
140
+ // Extract traces from active commands
141
+ if (data.active_commands) {
142
+ this.state.traces = data.active_commands.map((cmd, i) => ({
143
+ action: cmd.command || cmd,
144
+ status: 'running',
145
+ duration: '--',
146
+ details: cmd.args || '',
147
+ }));
148
+ }
149
+ }
150
+ } catch (err) {
151
+ // Ignore errors
152
+ }
153
+ }
154
+
155
+ formatDuration(startedAt) {
156
+ if (!startedAt) return '--';
157
+ try {
158
+ const start = new Date(startedAt);
159
+ const now = new Date();
160
+ const diff = Math.floor((now - start) / 1000);
161
+
162
+ if (diff < 60) return `${diff}s`;
163
+ if (diff < 3600) return `${Math.floor(diff / 60)}m`;
164
+ return `${Math.floor(diff / 3600)}h ${Math.floor((diff % 3600) / 60)}m`;
165
+ } catch {
166
+ return '--';
167
+ }
168
+ }
169
+
170
+ stop() {
171
+ if (this.watcher) {
172
+ this.watcher.close();
173
+ }
174
+ if (this.pollInterval) {
175
+ clearInterval(this.pollInterval);
176
+ }
177
+ }
178
+ }
179
+
180
+ module.exports = DataWatcher;
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * AgileFlow TUI - Terminal User Interface
6
+ *
7
+ * Full-screen, flicker-free dashboard with tabs and live updates.
8
+ *
9
+ * Key bindings:
10
+ * 1-3 Switch tabs (Sessions, Output, Trace)
11
+ * Tab Next tab
12
+ * j/k Navigate items
13
+ * r Refresh data
14
+ * ?/h Toggle help overlay
15
+ * q Quit TUI
16
+ */
17
+
18
+ const blessed = require('blessed');
19
+
20
+ // UI components
21
+ const createScreen = require('./ui/screen');
22
+ const createTabBar = require('./ui/tabbar');
23
+ const createStatusBar = require('./ui/statusbar');
24
+ const createHelpOverlay = require('./ui/help');
25
+
26
+ // Panels
27
+ const createSessionsPanel = require('./panels/sessions');
28
+ const createOutputPanel = require('./panels/output');
29
+ const createTracePanel = require('./panels/trace');
30
+
31
+ // Data
32
+ const DataWatcher = require('./data/watcher');
33
+
34
+ class AgileFlowTUI {
35
+ constructor() {
36
+ this.state = {
37
+ activeTab: 0,
38
+ tabs: ['Sessions', 'Output', 'Trace'],
39
+ sessions: [],
40
+ logs: [],
41
+ traces: [],
42
+ showHelp: false,
43
+ };
44
+ this.components = {};
45
+ this.watcher = null;
46
+ this.screen = null;
47
+ }
48
+
49
+ init() {
50
+ // Create screen with flicker-free rendering
51
+ this.screen = createScreen();
52
+
53
+ // Wrapper to pass screen to panels (they expect grid.screen)
54
+ const gridLike = { screen: this.screen };
55
+
56
+ // Create UI components
57
+ this.components.tabBar = createTabBar(this.screen, this.state);
58
+ this.components.statusBar = createStatusBar(this.screen, this.state);
59
+ this.components.help = createHelpOverlay(this.screen, this.state);
60
+
61
+ // Create panels (pass screen wrapper)
62
+ this.components.sessions = createSessionsPanel(gridLike, this.state);
63
+ this.components.output = createOutputPanel(gridLike, this.state);
64
+ this.components.trace = createTracePanel(gridLike, this.state);
65
+
66
+ // Hide all panels initially
67
+ this.components.sessions.hide();
68
+ this.components.output.hide();
69
+ this.components.trace.hide();
70
+
71
+ // Set up keybindings
72
+ this.setupKeys();
73
+
74
+ // Start data watcher for live updates
75
+ this.watcher = new DataWatcher(this.state, () => this.refresh());
76
+ this.watcher.start();
77
+
78
+ // Show initial tab
79
+ this.switchTab(0);
80
+
81
+ // Initial render
82
+ this.screen.render();
83
+ }
84
+
85
+ setupKeys() {
86
+ // Quit
87
+ this.screen.key(['q', 'C-c'], () => {
88
+ this.cleanup();
89
+ process.exit(0);
90
+ });
91
+
92
+ // Tab switching with number keys
93
+ this.screen.key(['1', '2', '3'], ch => {
94
+ if (this.components.help.isVisible()) return;
95
+ this.switchTab(parseInt(ch) - 1);
96
+ });
97
+
98
+ // Tab switching with Tab key
99
+ this.screen.key(['tab'], () => {
100
+ if (this.components.help.isVisible()) return;
101
+ this.switchTab((this.state.activeTab + 1) % this.state.tabs.length);
102
+ });
103
+
104
+ // Shift+Tab for reverse tab
105
+ this.screen.key(['S-tab'], () => {
106
+ if (this.components.help.isVisible()) return;
107
+ const prev = (this.state.activeTab - 1 + this.state.tabs.length) % this.state.tabs.length;
108
+ this.switchTab(prev);
109
+ });
110
+
111
+ // Help overlay
112
+ this.screen.key(['?', 'h'], () => {
113
+ this.state.showHelp = !this.state.showHelp;
114
+ this.components.help.toggle();
115
+ this.screen.render();
116
+ });
117
+
118
+ // Refresh
119
+ this.screen.key(['r'], () => {
120
+ if (this.components.help.isVisible()) return;
121
+ this.components.statusBar.setStatus('{yellow-fg}Refreshing...{/}');
122
+ this.screen.render();
123
+
124
+ // Reload data
125
+ if (this.watcher) {
126
+ this.watcher.loadStatus();
127
+ this.watcher.loadLogs();
128
+ this.watcher.loadSessionState();
129
+ }
130
+
131
+ setTimeout(() => {
132
+ this.refresh();
133
+ this.components.statusBar.resetHints();
134
+ this.screen.render();
135
+ }, 100);
136
+ });
137
+
138
+ // Navigation (j/k)
139
+ this.screen.key(['j', 'down'], () => {
140
+ if (this.components.help.isVisible()) return;
141
+ if (this.state.activeTab === 0 && this.components.sessions.selectNext) {
142
+ this.components.sessions.selectNext();
143
+ this.screen.render();
144
+ }
145
+ });
146
+
147
+ this.screen.key(['k', 'up'], () => {
148
+ if (this.components.help.isVisible()) return;
149
+ if (this.state.activeTab === 0 && this.components.sessions.selectPrev) {
150
+ this.components.sessions.selectPrev();
151
+ this.screen.render();
152
+ }
153
+ });
154
+
155
+ // Escape to close help
156
+ this.screen.key(['escape'], () => {
157
+ if (this.components.help.isVisible()) {
158
+ this.components.help.hide();
159
+ this.screen.render();
160
+ }
161
+ });
162
+ }
163
+
164
+ switchTab(index) {
165
+ if (index < 0 || index >= this.state.tabs.length) return;
166
+
167
+ this.state.activeTab = index;
168
+
169
+ // Hide all panels
170
+ this.components.sessions.hide();
171
+ this.components.output.hide();
172
+ this.components.trace.hide();
173
+
174
+ // Show and focus active panel
175
+ switch (index) {
176
+ case 0:
177
+ this.components.sessions.show();
178
+ this.components.sessions.focus();
179
+ break;
180
+ case 1:
181
+ this.components.output.show();
182
+ this.components.output.focus();
183
+ break;
184
+ case 2:
185
+ this.components.trace.show();
186
+ this.components.trace.focus();
187
+ break;
188
+ }
189
+
190
+ // Update tab bar highlighting
191
+ this.components.tabBar.setTab(index);
192
+
193
+ // Render changes
194
+ this.screen.render();
195
+ }
196
+
197
+ refresh() {
198
+ // Update panels with current state
199
+ this.components.sessions.setData(this.state.sessions);
200
+ this.components.output.setData(this.state.logs);
201
+ this.components.trace.setData(this.state.traces);
202
+
203
+ // Re-render
204
+ this.screen.render();
205
+ }
206
+
207
+ cleanup() {
208
+ // Stop watching files
209
+ if (this.watcher) {
210
+ this.watcher.stop();
211
+ }
212
+
213
+ // Destroy screen
214
+ if (this.screen) {
215
+ this.screen.destroy();
216
+ }
217
+ }
218
+
219
+ run() {
220
+ try {
221
+ this.init();
222
+ } catch (err) {
223
+ console.error('Failed to initialize TUI:', err.message);
224
+ console.error('');
225
+ console.error('Try running: npx agileflow tui --fallback');
226
+ process.exit(1);
227
+ }
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Main entry point
233
+ */
234
+ function main() {
235
+ const tui = new AgileFlowTUI();
236
+ tui.run();
237
+ }
238
+
239
+ // Run if executed directly
240
+ if (require.main === module) {
241
+ main();
242
+ }
243
+
244
+ module.exports = { AgileFlowTUI, main };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const blessed = require('blessed');
4
+
5
+ /**
6
+ * Create the output panel showing log stream
7
+ */
8
+ module.exports = function createOutputPanel(grid, state) {
9
+ const box = blessed.box({
10
+ parent: grid.screen,
11
+ top: 3,
12
+ left: 0,
13
+ width: '100%',
14
+ height: '100%-4',
15
+ label: ' {green-fg}{bold}Output{/bold}{/green-fg} ',
16
+ tags: true,
17
+ border: {
18
+ type: 'line',
19
+ fg: 'green',
20
+ },
21
+ style: {
22
+ fg: 'white',
23
+ bg: 'black',
24
+ border: { fg: 'green' },
25
+ },
26
+ scrollable: true,
27
+ alwaysScroll: true,
28
+ scrollbar: {
29
+ ch: '│',
30
+ style: { fg: 'green' },
31
+ },
32
+ keys: true,
33
+ vi: true,
34
+ mouse: true,
35
+ });
36
+
37
+ let logs = [];
38
+
39
+ function render() {
40
+ if (logs.length === 0) {
41
+ box.setContent(`
42
+ {gray-fg}Waiting for output...{/}
43
+
44
+ {green-fg}What shows here:{/}
45
+ • Agent activity logs
46
+ • Command execution output
47
+ • Tool call results
48
+ • Error messages
49
+
50
+ {gray-fg}Logs are read from docs/09-agents/bus/log.jsonl{/}
51
+ `);
52
+ return;
53
+ }
54
+
55
+ let lines = [];
56
+ logs.forEach(entry => {
57
+ const time = entry.timestamp
58
+ ? new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false })
59
+ : '--:--:--';
60
+
61
+ const level = entry.level || 'info';
62
+ let levelTag = '{gray-fg}INFO{/}';
63
+ if (level === 'error') levelTag = '{red-fg}ERRO{/}';
64
+ else if (level === 'warn' || level === 'warning') levelTag = '{yellow-fg}WARN{/}';
65
+ else if (level === 'debug') levelTag = '{gray-fg}DEBG{/}';
66
+ else if (level === 'success') levelTag = '{green-fg}DONE{/}';
67
+
68
+ const msg = entry.message || (typeof entry === 'string' ? entry : JSON.stringify(entry));
69
+ lines.push(` {gray-fg}${time}{/} ${levelTag} ${msg}`);
70
+ });
71
+
72
+ box.setContent(lines.join('\n'));
73
+ box.setScrollPerc(100); // Auto-scroll to bottom
74
+ }
75
+
76
+ return {
77
+ element: box,
78
+ show() {
79
+ box.show();
80
+ },
81
+ hide() {
82
+ box.hide();
83
+ },
84
+ focus() {
85
+ box.focus();
86
+ },
87
+ setData(data) {
88
+ logs = data || [];
89
+ render();
90
+ },
91
+ addLine(text, level = 'info') {
92
+ logs.push({ message: text, level, timestamp: new Date().toISOString() });
93
+ if (logs.length > 200) logs.shift();
94
+ render();
95
+ },
96
+ clear() {
97
+ logs = [];
98
+ render();
99
+ },
100
+ };
101
+ };
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const blessed = require('blessed');
4
+
5
+ /**
6
+ * Create the sessions panel with a better styled list
7
+ */
8
+ module.exports = function createSessionsPanel(grid, state) {
9
+ // Use a list instead of table for better control
10
+ const box = blessed.box({
11
+ parent: grid.screen,
12
+ top: 3,
13
+ left: 0,
14
+ width: '100%',
15
+ height: '100%-4',
16
+ label: ' {cyan-fg}{bold}Sessions{/bold}{/cyan-fg} ',
17
+ tags: true,
18
+ border: {
19
+ type: 'line',
20
+ fg: 'cyan',
21
+ },
22
+ style: {
23
+ fg: 'white',
24
+ bg: 'black',
25
+ border: { fg: 'cyan' },
26
+ },
27
+ scrollable: true,
28
+ alwaysScroll: true,
29
+ scrollbar: {
30
+ ch: '│',
31
+ style: { fg: 'cyan' },
32
+ },
33
+ keys: true,
34
+ vi: true,
35
+ });
36
+
37
+ // Header row
38
+ const header = blessed.box({
39
+ parent: box,
40
+ top: 0,
41
+ left: 0,
42
+ width: '100%-2',
43
+ height: 1,
44
+ content:
45
+ '{bold}{cyan-fg} ID Story Status Time{/}{/}',
46
+ tags: true,
47
+ style: {
48
+ bg: '#222222',
49
+ },
50
+ });
51
+
52
+ // Divider
53
+ blessed.box({
54
+ parent: box,
55
+ top: 1,
56
+ left: 0,
57
+ width: '100%-2',
58
+ height: 1,
59
+ content: ' ────────── ───────────────────────────────── ──────────── ──────',
60
+ style: {
61
+ fg: 'gray',
62
+ },
63
+ });
64
+
65
+ // Content area
66
+ const content = blessed.box({
67
+ parent: box,
68
+ top: 2,
69
+ left: 0,
70
+ width: '100%-2',
71
+ height: '100%-3',
72
+ tags: true,
73
+ style: {
74
+ fg: 'white',
75
+ },
76
+ });
77
+
78
+ let sessions = [];
79
+ let selectedIndex = 0;
80
+
81
+ function render() {
82
+ if (sessions.length === 0) {
83
+ content.setContent(`
84
+ {gray-fg}No active sessions{/}
85
+
86
+ {cyan-fg}Quick Start:{/}
87
+ • Run {bold}/agileflow:start{/} to begin a session
88
+ • Use {bold}/agileflow:loop{/} for autonomous mode
89
+ • Press {bold}?{/} for help
90
+ `);
91
+ return;
92
+ }
93
+
94
+ let lines = [];
95
+ sessions.forEach((s, i) => {
96
+ const selected = i === selectedIndex;
97
+ const prefix = selected ? '{inverse}' : '';
98
+ const suffix = selected ? '{/inverse}' : '';
99
+
100
+ // Status with colors
101
+ let statusText = s.status || 'unknown';
102
+ if (statusText === 'active') statusText = '{green-fg}● active{/}';
103
+ else if (statusText === 'running') statusText = '{green-fg}● running{/}';
104
+ else if (statusText === 'paused') statusText = '{yellow-fg}◉ paused{/}';
105
+ else if (statusText === 'idle') statusText = '{gray-fg}○ idle{/}';
106
+ else if (statusText === 'error') statusText = '{red-fg}✖ error{/}';
107
+ else statusText = `{gray-fg}○ ${statusText}{/}`;
108
+
109
+ const id = (s.id || '--').padEnd(10);
110
+ const story = (s.story || 'No story').substring(0, 34).padEnd(34);
111
+ const time = (s.duration || '--').padStart(6);
112
+
113
+ lines.push(`${prefix} ${id} ${story} ${statusText.padEnd(22)} ${time}${suffix}`);
114
+ });
115
+
116
+ content.setContent(lines.join('\n'));
117
+ }
118
+
119
+ return {
120
+ element: box,
121
+ show() {
122
+ box.show();
123
+ },
124
+ hide() {
125
+ box.hide();
126
+ },
127
+ focus() {
128
+ box.focus();
129
+ },
130
+ setData(data) {
131
+ sessions = data || [];
132
+ render();
133
+ },
134
+ getSelected() {
135
+ return selectedIndex;
136
+ },
137
+ selectNext() {
138
+ if (selectedIndex < sessions.length - 1) {
139
+ selectedIndex++;
140
+ render();
141
+ }
142
+ },
143
+ selectPrev() {
144
+ if (selectedIndex > 0) {
145
+ selectedIndex--;
146
+ render();
147
+ }
148
+ },
149
+ };
150
+ };