specsmd 0.1.57 → 0.1.59
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/lib/dashboard/tui/app.js +136 -3062
- package/lib/dashboard/tui/file-entries.js +382 -0
- package/lib/dashboard/tui/flow-builders.js +991 -0
- package/lib/dashboard/tui/git-builders.js +218 -0
- package/lib/dashboard/tui/helpers.js +236 -0
- package/lib/dashboard/tui/overlays.js +242 -0
- package/lib/dashboard/tui/preview.js +145 -0
- package/lib/dashboard/tui/row-builders.js +794 -0
- package/lib/dashboard/tui/sections.js +45 -0
- package/lib/dashboard/tui/worktree-builders.js +229 -0
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function getSectionOrderForView(view, options = {}) {
|
|
2
|
+
const includeWorktrees = options.includeWorktrees === true;
|
|
3
|
+
const includeOtherWorktrees = options.includeOtherWorktrees === true;
|
|
4
|
+
|
|
5
|
+
if (view === 'intents') {
|
|
6
|
+
return ['intent-status'];
|
|
7
|
+
}
|
|
8
|
+
if (view === 'completed') {
|
|
9
|
+
return ['completed-runs'];
|
|
10
|
+
}
|
|
11
|
+
if (view === 'health') {
|
|
12
|
+
return ['standards', 'stats', 'warnings', 'error-details'];
|
|
13
|
+
}
|
|
14
|
+
if (view === 'git') {
|
|
15
|
+
return ['git-status', 'git-changes', 'git-commits', 'git-diff'];
|
|
16
|
+
}
|
|
17
|
+
const sections = [];
|
|
18
|
+
if (includeWorktrees) {
|
|
19
|
+
sections.push('worktrees');
|
|
20
|
+
}
|
|
21
|
+
sections.push('current-run', 'run-files');
|
|
22
|
+
if (includeOtherWorktrees) {
|
|
23
|
+
sections.push('other-worktrees-active');
|
|
24
|
+
}
|
|
25
|
+
return sections;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
|
|
29
|
+
const order = Array.isArray(availableSections) && availableSections.length > 0
|
|
30
|
+
? availableSections
|
|
31
|
+
: getSectionOrderForView(view);
|
|
32
|
+
if (order.length === 0) {
|
|
33
|
+
return currentSectionKey;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const currentIndex = order.indexOf(currentSectionKey);
|
|
37
|
+
const safeIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
38
|
+
const nextIndex = (safeIndex + direction + order.length) % order.length;
|
|
39
|
+
return order[nextIndex];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
getSectionOrderForView,
|
|
44
|
+
cycleSection
|
|
45
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { clampIndex } = require('./helpers');
|
|
3
|
+
const { truncate } = require('./helpers');
|
|
4
|
+
const { getEffectiveFlow, getCurrentBolt } = require('./flow-builders');
|
|
5
|
+
const { collectAidlcBoltFiles } = require('./file-entries');
|
|
6
|
+
const { collectFireRunFiles } = require('./file-entries');
|
|
7
|
+
|
|
8
|
+
function getDashboardWorktreeMeta(snapshot) {
|
|
9
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const meta = snapshot.dashboardWorktrees;
|
|
13
|
+
if (!meta || typeof meta !== 'object') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const items = Array.isArray(meta.items) ? meta.items : [];
|
|
17
|
+
if (items.length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...meta,
|
|
22
|
+
items
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getWorktreeItems(snapshot) {
|
|
27
|
+
return getDashboardWorktreeMeta(snapshot)?.items || [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getSelectedWorktree(snapshot) {
|
|
31
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
32
|
+
if (!meta) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return meta.items.find((item) => item.id === meta.selectedWorktreeId) || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hasMultipleWorktrees(snapshot) {
|
|
39
|
+
return getWorktreeItems(snapshot).length > 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isSelectedWorktreeMain(snapshot) {
|
|
43
|
+
const selected = getSelectedWorktree(snapshot);
|
|
44
|
+
return Boolean(selected?.isMainBranch);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getWorktreeDisplayName(worktree) {
|
|
48
|
+
if (!worktree || typeof worktree !== 'object') {
|
|
49
|
+
return 'unknown';
|
|
50
|
+
}
|
|
51
|
+
if (typeof worktree.displayBranch === 'string' && worktree.displayBranch.trim() !== '') {
|
|
52
|
+
return worktree.displayBranch;
|
|
53
|
+
}
|
|
54
|
+
if (typeof worktree.branch === 'string' && worktree.branch.trim() !== '') {
|
|
55
|
+
return worktree.branch;
|
|
56
|
+
}
|
|
57
|
+
if (typeof worktree.name === 'string' && worktree.name.trim() !== '') {
|
|
58
|
+
return worktree.name;
|
|
59
|
+
}
|
|
60
|
+
return path.basename(worktree.path || '') || 'unknown';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildWorktreeRows(snapshot, flow) {
|
|
64
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
65
|
+
if (!meta) {
|
|
66
|
+
return [{
|
|
67
|
+
kind: 'info',
|
|
68
|
+
key: 'worktrees:none',
|
|
69
|
+
label: 'No git worktrees detected',
|
|
70
|
+
selectable: false
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
75
|
+
const entityLabel = effectiveFlow === 'aidlc'
|
|
76
|
+
? 'active bolts'
|
|
77
|
+
: (effectiveFlow === 'simple' ? 'active specs' : 'active runs');
|
|
78
|
+
|
|
79
|
+
const rows = [];
|
|
80
|
+
for (const item of meta.items) {
|
|
81
|
+
const currentLabel = item.isSelected ? '[CURRENT] ' : '';
|
|
82
|
+
const mainLabel = item.isMainBranch && !item.detached ? '[MAIN] ' : '';
|
|
83
|
+
const availabilityLabel = item.flowAvailable ? '' : ' (flow unavailable)';
|
|
84
|
+
const statusLabel = item.status === 'loading'
|
|
85
|
+
? ' loading...'
|
|
86
|
+
: (item.status === 'error' ? ' error' : ` ${item.activeCount || 0} ${entityLabel}`);
|
|
87
|
+
const scopeLabel = item.name ? ` (${item.name})` : '';
|
|
88
|
+
|
|
89
|
+
rows.push({
|
|
90
|
+
kind: 'info',
|
|
91
|
+
key: `worktree:item:${item.id}`,
|
|
92
|
+
label: `${currentLabel}${mainLabel}${getWorktreeDisplayName(item)}${scopeLabel}${availabilityLabel}${statusLabel}`,
|
|
93
|
+
color: item.isSelected ? 'green' : (item.flowAvailable ? 'gray' : 'red'),
|
|
94
|
+
bold: item.isSelected,
|
|
95
|
+
selectable: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return rows;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildOtherWorktreeActiveGroups(snapshot, flow) {
|
|
103
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
104
|
+
if (effectiveFlow === 'simple') {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
109
|
+
if (!meta) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const selectedWorktree = getSelectedWorktree(snapshot);
|
|
114
|
+
if (!selectedWorktree || !selectedWorktree.isMainBranch) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const groups = [];
|
|
119
|
+
const otherItems = meta.items.filter((item) => item.id !== meta.selectedWorktreeId);
|
|
120
|
+
for (const item of otherItems) {
|
|
121
|
+
if (!item.flowAvailable || item.status === 'unavailable' || item.status === 'error') {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (effectiveFlow === 'aidlc') {
|
|
126
|
+
const activeBolts = Array.isArray(item.activity?.activeBolts) ? item.activity.activeBolts : [];
|
|
127
|
+
for (const bolt of activeBolts) {
|
|
128
|
+
const stages = Array.isArray(bolt?.stages) ? bolt.stages : [];
|
|
129
|
+
const completedStages = stages.filter((stage) => stage?.status === 'completed').length;
|
|
130
|
+
groups.push({
|
|
131
|
+
key: `other:wt:${item.id}:bolt:${bolt.id}`,
|
|
132
|
+
label: `[WT ${getWorktreeDisplayName(item)}] ${bolt.id} [${bolt.type || 'bolt'}] ${completedStages}/${stages.length || 0} stages`,
|
|
133
|
+
files: collectAidlcBoltFiles(bolt).map((file) => ({ ...file, scope: 'active' }))
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const activeRuns = Array.isArray(item.activity?.activeRuns) ? item.activity.activeRuns : [];
|
|
140
|
+
for (const run of activeRuns) {
|
|
141
|
+
const workItems = Array.isArray(run?.workItems) ? run.workItems : [];
|
|
142
|
+
const completed = workItems.filter((workItem) => workItem?.status === 'completed').length;
|
|
143
|
+
groups.push({
|
|
144
|
+
key: `other:wt:${item.id}:run:${run.id}`,
|
|
145
|
+
label: `[WT ${getWorktreeDisplayName(item)}] ${run.id} [${run.scope || 'single'}] ${completed}/${workItems.length} items`,
|
|
146
|
+
files: collectFireRunFiles(run).map((file) => ({ ...file, scope: 'active' }))
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return groups;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getOtherWorktreeEmptyMessage(snapshot, flow) {
|
|
155
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
156
|
+
if (!hasMultipleWorktrees(snapshot)) {
|
|
157
|
+
return 'No additional worktrees';
|
|
158
|
+
}
|
|
159
|
+
if (!isSelectedWorktreeMain(snapshot)) {
|
|
160
|
+
return 'Switch to main worktree to view active items from other worktrees';
|
|
161
|
+
}
|
|
162
|
+
if (effectiveFlow === 'aidlc') {
|
|
163
|
+
return 'No active bolts in other worktrees';
|
|
164
|
+
}
|
|
165
|
+
if (effectiveFlow === 'simple') {
|
|
166
|
+
return 'No active specs in other worktrees';
|
|
167
|
+
}
|
|
168
|
+
return 'No active runs in other worktrees';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function buildWorktreeOverlayLines(snapshot, selectedIndex, width) {
|
|
172
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
173
|
+
if (!meta) {
|
|
174
|
+
return [{
|
|
175
|
+
text: truncate('No worktrees available', width),
|
|
176
|
+
color: 'gray',
|
|
177
|
+
bold: false
|
|
178
|
+
}];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const items = Array.isArray(meta.items) ? meta.items : [];
|
|
182
|
+
const clampedIndex = clampIndex(selectedIndex, items.length || 1);
|
|
183
|
+
const lines = [{
|
|
184
|
+
text: truncate('Use ↑/↓ and Enter to switch. Esc closes.', width),
|
|
185
|
+
color: 'gray',
|
|
186
|
+
bold: false
|
|
187
|
+
}];
|
|
188
|
+
|
|
189
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
190
|
+
const item = items[index];
|
|
191
|
+
const marker = index === clampedIndex ? '>' : ' ';
|
|
192
|
+
const current = item.isSelected ? '[CURRENT] ' : '';
|
|
193
|
+
const main = item.isMainBranch && !item.detached ? '[MAIN] ' : '';
|
|
194
|
+
const status = item.status === 'loading'
|
|
195
|
+
? 'loading'
|
|
196
|
+
: (item.status === 'error'
|
|
197
|
+
? 'error'
|
|
198
|
+
: (item.flowAvailable ? `${item.activeCount || 0} active` : 'flow unavailable'));
|
|
199
|
+
const pathLabel = item.path ? path.basename(item.path) : 'unknown';
|
|
200
|
+
lines.push({
|
|
201
|
+
text: truncate(`${marker} ${current}${main}${getWorktreeDisplayName(item)} (${pathLabel}) | ${status}`, width),
|
|
202
|
+
color: index === clampedIndex ? 'green' : (item.isSelected ? 'cyan' : 'gray'),
|
|
203
|
+
bold: index === clampedIndex || item.isSelected
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (meta.hasPendingScans) {
|
|
208
|
+
lines.push({
|
|
209
|
+
text: truncate('Background scan in progress for additional worktrees...', width),
|
|
210
|
+
color: 'yellow',
|
|
211
|
+
bold: false
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return lines;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
getDashboardWorktreeMeta,
|
|
220
|
+
getWorktreeItems,
|
|
221
|
+
getSelectedWorktree,
|
|
222
|
+
hasMultipleWorktrees,
|
|
223
|
+
isSelectedWorktreeMain,
|
|
224
|
+
getWorktreeDisplayName,
|
|
225
|
+
buildWorktreeRows,
|
|
226
|
+
buildOtherWorktreeActiveGroups,
|
|
227
|
+
getOtherWorktreeEmptyMessage,
|
|
228
|
+
buildWorktreeOverlayLines
|
|
229
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
|
|
5
5
|
"main": "lib/installer.js",
|
|
6
6
|
"bin": {
|