specsmd 0.1.50 → 0.1.52
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/git/changes.js +330 -0
- package/lib/dashboard/index.js +3 -1
- package/lib/dashboard/tui/app.js +236 -44
- package/lib/dashboard/tui/store.js +8 -2
- package/package.json +1 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
function runGit(args, cwd, options = {}) {
|
|
6
|
+
const acceptedStatuses = Array.isArray(options.acceptedStatuses) && options.acceptedStatuses.length > 0
|
|
7
|
+
? options.acceptedStatuses
|
|
8
|
+
: [0];
|
|
9
|
+
const result = spawnSync('git', args, {
|
|
10
|
+
cwd,
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
maxBuffer: 10 * 1024 * 1024
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (result.error) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
error: result.error.message || String(result.error),
|
|
19
|
+
stdout: '',
|
|
20
|
+
stderr: ''
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof result.status === 'number' && !acceptedStatuses.includes(result.status)) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: String(result.stderr || '').trim() || `git exited with code ${result.status}`,
|
|
28
|
+
stdout: String(result.stdout || ''),
|
|
29
|
+
stderr: String(result.stderr || '')
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
error: null,
|
|
36
|
+
stdout: String(result.stdout || ''),
|
|
37
|
+
stderr: String(result.stderr || '')
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findGitRoot(worktreePath) {
|
|
42
|
+
if (typeof worktreePath !== 'string' || worktreePath.trim() === '') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const probe = runGit(['rev-parse', '--show-toplevel'], worktreePath);
|
|
47
|
+
if (!probe.ok) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const root = probe.stdout.trim();
|
|
52
|
+
return root === '' ? null : root;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseBranchSummary(line) {
|
|
56
|
+
const raw = String(line || '').replace(/^##\s*/, '').trim();
|
|
57
|
+
if (raw === '') {
|
|
58
|
+
return {
|
|
59
|
+
branch: '(unknown)',
|
|
60
|
+
upstream: null,
|
|
61
|
+
ahead: 0,
|
|
62
|
+
behind: 0,
|
|
63
|
+
detached: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (raw.startsWith('HEAD ')) {
|
|
68
|
+
return {
|
|
69
|
+
branch: '(detached)',
|
|
70
|
+
upstream: null,
|
|
71
|
+
ahead: 0,
|
|
72
|
+
behind: 0,
|
|
73
|
+
detached: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const [branchPart, trackingPartRaw] = raw.split(/\s+/, 2);
|
|
78
|
+
let branch = branchPart;
|
|
79
|
+
let upstream = null;
|
|
80
|
+
if (branch.includes('...')) {
|
|
81
|
+
const [name, remote] = branch.split('...', 2);
|
|
82
|
+
branch = name || '(unknown)';
|
|
83
|
+
upstream = remote || null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const trackingPart = typeof trackingPartRaw === 'string' ? trackingPartRaw : '';
|
|
87
|
+
const aheadMatch = trackingPart.match(/ahead\s+(\d+)/);
|
|
88
|
+
const behindMatch = trackingPart.match(/behind\s+(\d+)/);
|
|
89
|
+
const ahead = aheadMatch ? Number.parseInt(aheadMatch[1], 10) : 0;
|
|
90
|
+
const behind = behindMatch ? Number.parseInt(behindMatch[1], 10) : 0;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
branch: branch || '(unknown)',
|
|
94
|
+
upstream,
|
|
95
|
+
ahead: Number.isFinite(ahead) ? ahead : 0,
|
|
96
|
+
behind: Number.isFinite(behind) ? behind : 0,
|
|
97
|
+
detached: false
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseStatusEntry(line, repoRoot) {
|
|
102
|
+
const raw = String(line || '');
|
|
103
|
+
if (raw.trim() === '' || raw.startsWith('## ')) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const code = raw.slice(0, 2);
|
|
108
|
+
const statusX = code.charAt(0);
|
|
109
|
+
const statusY = code.charAt(1);
|
|
110
|
+
const remainder = raw.length > 3 ? raw.slice(3) : '';
|
|
111
|
+
|
|
112
|
+
let relativePath = remainder.trim();
|
|
113
|
+
if (relativePath.includes(' -> ')) {
|
|
114
|
+
const parts = relativePath.split(' -> ');
|
|
115
|
+
relativePath = parts[parts.length - 1].trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const absolutePath = relativePath === ''
|
|
119
|
+
? ''
|
|
120
|
+
: path.join(repoRoot, relativePath);
|
|
121
|
+
const isUntracked = code === '??';
|
|
122
|
+
const isConflicted = statusX === 'U'
|
|
123
|
+
|| statusY === 'U'
|
|
124
|
+
|| code === 'AA'
|
|
125
|
+
|| code === 'DD';
|
|
126
|
+
const isStaged = !isUntracked && statusX !== ' ';
|
|
127
|
+
const isUnstaged = !isUntracked && statusY !== ' ';
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
code,
|
|
131
|
+
statusX,
|
|
132
|
+
statusY,
|
|
133
|
+
relativePath,
|
|
134
|
+
absolutePath,
|
|
135
|
+
staged: isStaged,
|
|
136
|
+
unstaged: isUnstaged,
|
|
137
|
+
untracked: isUntracked,
|
|
138
|
+
conflicted: isConflicted
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildBucketItem(entry, bucket) {
|
|
143
|
+
return {
|
|
144
|
+
key: `${bucket}:${entry.relativePath}`,
|
|
145
|
+
bucket,
|
|
146
|
+
code: entry.code,
|
|
147
|
+
path: entry.absolutePath,
|
|
148
|
+
relativePath: entry.relativePath,
|
|
149
|
+
label: entry.relativePath
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function listGitChanges(worktreePath) {
|
|
154
|
+
const gitRoot = findGitRoot(worktreePath);
|
|
155
|
+
if (!gitRoot) {
|
|
156
|
+
return {
|
|
157
|
+
available: false,
|
|
158
|
+
rootPath: null,
|
|
159
|
+
branch: '(not a git repo)',
|
|
160
|
+
upstream: null,
|
|
161
|
+
ahead: 0,
|
|
162
|
+
behind: 0,
|
|
163
|
+
detached: false,
|
|
164
|
+
clean: true,
|
|
165
|
+
counts: {
|
|
166
|
+
total: 0,
|
|
167
|
+
staged: 0,
|
|
168
|
+
unstaged: 0,
|
|
169
|
+
untracked: 0,
|
|
170
|
+
conflicted: 0
|
|
171
|
+
},
|
|
172
|
+
staged: [],
|
|
173
|
+
unstaged: [],
|
|
174
|
+
untracked: [],
|
|
175
|
+
conflicted: [],
|
|
176
|
+
generatedAt: new Date().toISOString()
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const statusResult = runGit(
|
|
181
|
+
['-c', 'color.ui=false', '-c', 'core.quotepath=false', 'status', '--porcelain', '--branch', '--untracked-files=all'],
|
|
182
|
+
gitRoot
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (!statusResult.ok) {
|
|
186
|
+
return {
|
|
187
|
+
available: false,
|
|
188
|
+
rootPath: gitRoot,
|
|
189
|
+
branch: '(status unavailable)',
|
|
190
|
+
upstream: null,
|
|
191
|
+
ahead: 0,
|
|
192
|
+
behind: 0,
|
|
193
|
+
detached: false,
|
|
194
|
+
clean: true,
|
|
195
|
+
counts: {
|
|
196
|
+
total: 0,
|
|
197
|
+
staged: 0,
|
|
198
|
+
unstaged: 0,
|
|
199
|
+
untracked: 0,
|
|
200
|
+
conflicted: 0
|
|
201
|
+
},
|
|
202
|
+
staged: [],
|
|
203
|
+
unstaged: [],
|
|
204
|
+
untracked: [],
|
|
205
|
+
conflicted: [],
|
|
206
|
+
error: statusResult.error,
|
|
207
|
+
generatedAt: new Date().toISOString()
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const lines = statusResult.stdout.split(/\r?\n/).filter(Boolean);
|
|
212
|
+
const branchInfo = parseBranchSummary(lines[0] || '');
|
|
213
|
+
|
|
214
|
+
const staged = [];
|
|
215
|
+
const unstaged = [];
|
|
216
|
+
const untracked = [];
|
|
217
|
+
const conflicted = [];
|
|
218
|
+
|
|
219
|
+
for (const line of lines.slice(1)) {
|
|
220
|
+
const entry = parseStatusEntry(line, gitRoot);
|
|
221
|
+
if (!entry || entry.relativePath === '') {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (entry.conflicted) {
|
|
225
|
+
conflicted.push(buildBucketItem(entry, 'conflicted'));
|
|
226
|
+
}
|
|
227
|
+
if (entry.staged) {
|
|
228
|
+
staged.push(buildBucketItem(entry, 'staged'));
|
|
229
|
+
}
|
|
230
|
+
if (entry.unstaged) {
|
|
231
|
+
unstaged.push(buildBucketItem(entry, 'unstaged'));
|
|
232
|
+
}
|
|
233
|
+
if (entry.untracked) {
|
|
234
|
+
untracked.push(buildBucketItem(entry, 'untracked'));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const uniqueCount = new Set([
|
|
239
|
+
...staged.map((item) => item.relativePath),
|
|
240
|
+
...unstaged.map((item) => item.relativePath),
|
|
241
|
+
...untracked.map((item) => item.relativePath),
|
|
242
|
+
...conflicted.map((item) => item.relativePath)
|
|
243
|
+
]).size;
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
available: true,
|
|
247
|
+
rootPath: gitRoot,
|
|
248
|
+
branch: branchInfo.branch,
|
|
249
|
+
upstream: branchInfo.upstream,
|
|
250
|
+
ahead: branchInfo.ahead,
|
|
251
|
+
behind: branchInfo.behind,
|
|
252
|
+
detached: branchInfo.detached,
|
|
253
|
+
clean: uniqueCount === 0,
|
|
254
|
+
counts: {
|
|
255
|
+
total: uniqueCount,
|
|
256
|
+
staged: staged.length,
|
|
257
|
+
unstaged: unstaged.length,
|
|
258
|
+
untracked: untracked.length,
|
|
259
|
+
conflicted: conflicted.length
|
|
260
|
+
},
|
|
261
|
+
staged,
|
|
262
|
+
unstaged,
|
|
263
|
+
untracked,
|
|
264
|
+
conflicted,
|
|
265
|
+
generatedAt: new Date().toISOString()
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function readUntrackedFileDiff(repoRoot, absolutePath) {
|
|
270
|
+
const exists = typeof absolutePath === 'string' && absolutePath !== '' && fs.existsSync(absolutePath);
|
|
271
|
+
if (!exists) {
|
|
272
|
+
return '';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const result = runGit(
|
|
276
|
+
['-c', 'color.ui=always', '--no-pager', 'diff', '--no-index', '--', '/dev/null', absolutePath],
|
|
277
|
+
repoRoot,
|
|
278
|
+
{ acceptedStatuses: [0, 1] }
|
|
279
|
+
);
|
|
280
|
+
if (!result.ok) {
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
return result.stdout;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function loadGitDiffPreview(changeEntry) {
|
|
287
|
+
const bucket = typeof changeEntry?.bucket === 'string' ? changeEntry.bucket : 'unstaged';
|
|
288
|
+
const repoRoot = typeof changeEntry?.repoRoot === 'string'
|
|
289
|
+
? changeEntry.repoRoot
|
|
290
|
+
: (typeof changeEntry?.workspacePath === 'string' ? findGitRoot(changeEntry.workspacePath) : null);
|
|
291
|
+
const relativePath = typeof changeEntry?.relativePath === 'string' ? changeEntry.relativePath : '';
|
|
292
|
+
const absolutePath = typeof changeEntry?.path === 'string' ? changeEntry.path : '';
|
|
293
|
+
|
|
294
|
+
if (!repoRoot) {
|
|
295
|
+
return '[git] repository is unavailable for preview.';
|
|
296
|
+
}
|
|
297
|
+
if (relativePath === '') {
|
|
298
|
+
return '[git] no file selected.';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (bucket === 'untracked') {
|
|
302
|
+
const rawDiff = readUntrackedFileDiff(repoRoot, absolutePath);
|
|
303
|
+
if (rawDiff.trim() !== '') {
|
|
304
|
+
return rawDiff;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const args = ['-c', 'color.ui=always', '--no-pager', 'diff'];
|
|
309
|
+
if (bucket === 'staged') {
|
|
310
|
+
args.push('--cached');
|
|
311
|
+
}
|
|
312
|
+
args.push('--', relativePath);
|
|
313
|
+
|
|
314
|
+
const result = runGit(args, repoRoot);
|
|
315
|
+
if (!result.ok) {
|
|
316
|
+
return `[git] unable to load diff: ${result.error}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const output = result.stdout.trim();
|
|
320
|
+
if (output === '') {
|
|
321
|
+
return '[git] no diff output for this file.';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return result.stdout;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = {
|
|
328
|
+
listGitChanges,
|
|
329
|
+
loadGitDiffPreview
|
|
330
|
+
};
|
package/lib/dashboard/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const { parseSimpleDashboard } = require('./simple/parser');
|
|
|
8
8
|
const { formatDashboardText } = require('./tui/renderer');
|
|
9
9
|
const { createDashboardApp } = require('./tui/app');
|
|
10
10
|
const { discoverGitWorktrees, pickWorktree, pathExistsAsDirectory } = require('./git/worktrees');
|
|
11
|
+
const { listGitChanges } = require('./git/changes');
|
|
11
12
|
|
|
12
13
|
function parseRefreshMs(raw) {
|
|
13
14
|
const parsed = Number.parseInt(String(raw || '1000'), 10);
|
|
@@ -602,7 +603,8 @@ async function runFlowDashboard(options, flow, availableFlows = []) {
|
|
|
602
603
|
snapshot: {
|
|
603
604
|
...selectedResult.snapshot,
|
|
604
605
|
workspacePath: selectedWorktree?.path || selectedResult.snapshot?.workspacePath || workspacePath,
|
|
605
|
-
dashboardWorktrees: envelope
|
|
606
|
+
dashboardWorktrees: envelope,
|
|
607
|
+
gitChanges: listGitChanges(selectedWorktree?.path || workspacePath)
|
|
606
608
|
}
|
|
607
609
|
};
|
|
608
610
|
};
|
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -5,6 +5,7 @@ const stringWidthModule = require('string-width');
|
|
|
5
5
|
const sliceAnsiModule = require('slice-ansi');
|
|
6
6
|
const { createWatchRuntime } = require('../runtime/watch-runtime');
|
|
7
7
|
const { createInitialUIState } = require('./store');
|
|
8
|
+
const { loadGitDiffPreview } = require('../git/changes');
|
|
8
9
|
|
|
9
10
|
const stringWidth = typeof stringWidthModule === 'function'
|
|
10
11
|
? stringWidthModule
|
|
@@ -64,6 +65,7 @@ function resolveIconSet() {
|
|
|
64
65
|
runs: '[R]',
|
|
65
66
|
overview: '[O]',
|
|
66
67
|
health: '[H]',
|
|
68
|
+
git: '[G]',
|
|
67
69
|
runFile: '*',
|
|
68
70
|
activeFile: '>',
|
|
69
71
|
groupCollapsed: '>',
|
|
@@ -74,6 +76,7 @@ function resolveIconSet() {
|
|
|
74
76
|
runs: '',
|
|
75
77
|
overview: '',
|
|
76
78
|
health: '',
|
|
79
|
+
git: '',
|
|
77
80
|
runFile: '',
|
|
78
81
|
activeFile: '',
|
|
79
82
|
groupCollapsed: '',
|
|
@@ -1120,7 +1123,47 @@ function getPanelTitles(flow, snapshot) {
|
|
|
1120
1123
|
files: 'Run Files',
|
|
1121
1124
|
pending: 'Pending Queue',
|
|
1122
1125
|
completed: 'Recent Completed Runs',
|
|
1123
|
-
otherWorktrees: 'Other Worktrees: Active Runs'
|
|
1126
|
+
otherWorktrees: 'Other Worktrees: Active Runs',
|
|
1127
|
+
git: 'Git Changes'
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function getGitChangesSnapshot(snapshot) {
|
|
1132
|
+
const gitChanges = snapshot?.gitChanges;
|
|
1133
|
+
if (!gitChanges || typeof gitChanges !== 'object') {
|
|
1134
|
+
return {
|
|
1135
|
+
available: false,
|
|
1136
|
+
branch: '(unavailable)',
|
|
1137
|
+
upstream: null,
|
|
1138
|
+
ahead: 0,
|
|
1139
|
+
behind: 0,
|
|
1140
|
+
counts: {
|
|
1141
|
+
total: 0,
|
|
1142
|
+
staged: 0,
|
|
1143
|
+
unstaged: 0,
|
|
1144
|
+
untracked: 0,
|
|
1145
|
+
conflicted: 0
|
|
1146
|
+
},
|
|
1147
|
+
staged: [],
|
|
1148
|
+
unstaged: [],
|
|
1149
|
+
untracked: [],
|
|
1150
|
+
conflicted: [],
|
|
1151
|
+
clean: true
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
return {
|
|
1155
|
+
...gitChanges,
|
|
1156
|
+
counts: gitChanges.counts || {
|
|
1157
|
+
total: 0,
|
|
1158
|
+
staged: 0,
|
|
1159
|
+
unstaged: 0,
|
|
1160
|
+
untracked: 0,
|
|
1161
|
+
conflicted: 0
|
|
1162
|
+
},
|
|
1163
|
+
staged: Array.isArray(gitChanges.staged) ? gitChanges.staged : [],
|
|
1164
|
+
unstaged: Array.isArray(gitChanges.unstaged) ? gitChanges.unstaged : [],
|
|
1165
|
+
untracked: Array.isArray(gitChanges.untracked) ? gitChanges.untracked : [],
|
|
1166
|
+
conflicted: Array.isArray(gitChanges.conflicted) ? gitChanges.conflicted : []
|
|
1124
1167
|
};
|
|
1125
1168
|
}
|
|
1126
1169
|
|
|
@@ -1300,6 +1343,9 @@ function getSectionOrderForView(view, options = {}) {
|
|
|
1300
1343
|
if (view === 'health') {
|
|
1301
1344
|
return ['standards', 'stats', 'warnings', 'error-details'];
|
|
1302
1345
|
}
|
|
1346
|
+
if (view === 'git') {
|
|
1347
|
+
return ['git-changes'];
|
|
1348
|
+
}
|
|
1303
1349
|
const sections = [];
|
|
1304
1350
|
if (includeWorktrees) {
|
|
1305
1351
|
sections.push('worktrees');
|
|
@@ -1640,6 +1686,10 @@ function formatScope(scope) {
|
|
|
1640
1686
|
if (scope === 'upcoming') return 'UPNEXT';
|
|
1641
1687
|
if (scope === 'completed') return 'DONE';
|
|
1642
1688
|
if (scope === 'intent') return 'INTENT';
|
|
1689
|
+
if (scope === 'staged') return 'STAGED';
|
|
1690
|
+
if (scope === 'unstaged') return 'UNSTAGED';
|
|
1691
|
+
if (scope === 'untracked') return 'UNTRACKED';
|
|
1692
|
+
if (scope === 'conflicted') return 'CONFLICT';
|
|
1643
1693
|
return 'FILE';
|
|
1644
1694
|
}
|
|
1645
1695
|
|
|
@@ -2054,9 +2104,15 @@ function collectAidlcIntentContextFiles(snapshot, intentId) {
|
|
|
2054
2104
|
}
|
|
2055
2105
|
|
|
2056
2106
|
function filterExistingFiles(files) {
|
|
2057
|
-
return (Array.isArray(files) ? files : []).filter((file) =>
|
|
2058
|
-
file
|
|
2059
|
-
|
|
2107
|
+
return (Array.isArray(files) ? files : []).filter((file) => {
|
|
2108
|
+
if (!file || typeof file.path !== 'string' || typeof file.label !== 'string') {
|
|
2109
|
+
return false;
|
|
2110
|
+
}
|
|
2111
|
+
if (file.allowMissing === true) {
|
|
2112
|
+
return true;
|
|
2113
|
+
}
|
|
2114
|
+
return fileExists(file.path);
|
|
2115
|
+
});
|
|
2060
2116
|
}
|
|
2061
2117
|
|
|
2062
2118
|
function buildPendingGroups(snapshot, flow) {
|
|
@@ -2191,6 +2247,49 @@ function buildCompletedGroups(snapshot, flow) {
|
|
|
2191
2247
|
return groups;
|
|
2192
2248
|
}
|
|
2193
2249
|
|
|
2250
|
+
function buildGitChangeGroups(snapshot) {
|
|
2251
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
2252
|
+
|
|
2253
|
+
if (!git.available) {
|
|
2254
|
+
return [];
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
const makeFiles = (items, scope) => items.map((item) => ({
|
|
2258
|
+
label: item.relativePath,
|
|
2259
|
+
path: item.path || path.join(git.rootPath || snapshot?.workspacePath || '', item.relativePath || ''),
|
|
2260
|
+
scope,
|
|
2261
|
+
allowMissing: true,
|
|
2262
|
+
previewType: 'git-diff',
|
|
2263
|
+
repoRoot: git.rootPath || snapshot?.workspacePath || '',
|
|
2264
|
+
relativePath: item.relativePath || '',
|
|
2265
|
+
bucket: item.bucket || scope
|
|
2266
|
+
}));
|
|
2267
|
+
|
|
2268
|
+
const groups = [];
|
|
2269
|
+
groups.push({
|
|
2270
|
+
key: 'git:staged',
|
|
2271
|
+
label: `staged (${git.counts.staged || 0})`,
|
|
2272
|
+
files: makeFiles(git.staged, 'staged')
|
|
2273
|
+
});
|
|
2274
|
+
groups.push({
|
|
2275
|
+
key: 'git:unstaged',
|
|
2276
|
+
label: `unstaged (${git.counts.unstaged || 0})`,
|
|
2277
|
+
files: makeFiles(git.unstaged, 'unstaged')
|
|
2278
|
+
});
|
|
2279
|
+
groups.push({
|
|
2280
|
+
key: 'git:untracked',
|
|
2281
|
+
label: `untracked (${git.counts.untracked || 0})`,
|
|
2282
|
+
files: makeFiles(git.untracked, 'untracked')
|
|
2283
|
+
});
|
|
2284
|
+
groups.push({
|
|
2285
|
+
key: 'git:conflicted',
|
|
2286
|
+
label: `conflicts (${git.counts.conflicted || 0})`,
|
|
2287
|
+
files: makeFiles(git.conflicted, 'conflicted')
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
return groups;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2194
2293
|
function toExpandableRows(groups, emptyLabel, expandedGroups) {
|
|
2195
2294
|
if (!Array.isArray(groups) || groups.length === 0) {
|
|
2196
2295
|
return [{
|
|
@@ -2221,12 +2320,16 @@ function toExpandableRows(groups, emptyLabel, expandedGroups) {
|
|
|
2221
2320
|
for (let index = 0; index < files.length; index += 1) {
|
|
2222
2321
|
const file = files[index];
|
|
2223
2322
|
rows.push({
|
|
2224
|
-
kind: 'file',
|
|
2323
|
+
kind: file.previewType === 'git-diff' ? 'git-file' : 'file',
|
|
2225
2324
|
key: `${group.key}:file:${file.path}:${index}`,
|
|
2226
2325
|
label: file.label,
|
|
2227
2326
|
path: file.path,
|
|
2228
2327
|
scope: file.scope || 'file',
|
|
2229
|
-
selectable: true
|
|
2328
|
+
selectable: true,
|
|
2329
|
+
previewType: file.previewType,
|
|
2330
|
+
repoRoot: file.repoRoot,
|
|
2331
|
+
relativePath: file.relativePath,
|
|
2332
|
+
bucket: file.bucket
|
|
2230
2333
|
});
|
|
2231
2334
|
}
|
|
2232
2335
|
}
|
|
@@ -2261,7 +2364,7 @@ function buildInteractiveRowsLines(rows, selectedIndex, icons, width, isFocusedS
|
|
|
2261
2364
|
};
|
|
2262
2365
|
}
|
|
2263
2366
|
|
|
2264
|
-
if (row.kind === 'file') {
|
|
2367
|
+
if (row.kind === 'file' || row.kind === 'git-file') {
|
|
2265
2368
|
const scope = row.scope ? `[${formatScope(row.scope)}] ` : '';
|
|
2266
2369
|
return {
|
|
2267
2370
|
text: truncate(`${cursor} ${icons.runFile} ${scope}${row.label}`, width),
|
|
@@ -2298,13 +2401,17 @@ function getSelectedRow(rows, selectedIndex) {
|
|
|
2298
2401
|
}
|
|
2299
2402
|
|
|
2300
2403
|
function rowToFileEntry(row) {
|
|
2301
|
-
if (!row || row.kind !== 'file' || typeof row.path !== 'string') {
|
|
2404
|
+
if (!row || (row.kind !== 'file' && row.kind !== 'git-file') || typeof row.path !== 'string') {
|
|
2302
2405
|
return null;
|
|
2303
2406
|
}
|
|
2304
2407
|
return {
|
|
2305
2408
|
label: row.label || path.basename(row.path),
|
|
2306
2409
|
path: row.path,
|
|
2307
|
-
scope: row.scope || 'file'
|
|
2410
|
+
scope: row.scope || 'file',
|
|
2411
|
+
previewType: row.previewType,
|
|
2412
|
+
repoRoot: row.repoRoot,
|
|
2413
|
+
relativePath: row.relativePath,
|
|
2414
|
+
bucket: row.bucket
|
|
2308
2415
|
};
|
|
2309
2416
|
}
|
|
2310
2417
|
|
|
@@ -2401,9 +2508,9 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2401
2508
|
const isSimple = String(flow || '').toLowerCase() === 'simple';
|
|
2402
2509
|
const activeLabel = isAidlc ? 'active bolt' : (isSimple ? 'active spec' : 'active run');
|
|
2403
2510
|
|
|
2404
|
-
const parts = ['1/2/3/4 tabs', 'g/G sections'];
|
|
2511
|
+
const parts = ['1/2/3/4/5 tabs', 'g/G sections'];
|
|
2405
2512
|
|
|
2406
|
-
if (view === 'runs' || view === 'intents' || view === 'completed' || view === 'health') {
|
|
2513
|
+
if (view === 'runs' || view === 'intents' || view === 'completed' || view === 'health' || view === 'git') {
|
|
2407
2514
|
if (previewOpen) {
|
|
2408
2515
|
parts.push('tab pane', '↑/↓ nav/scroll', 'v/space close');
|
|
2409
2516
|
} else {
|
|
@@ -2415,9 +2522,6 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2415
2522
|
parts.push('b worktrees', 'u others');
|
|
2416
2523
|
}
|
|
2417
2524
|
parts.push('a current', 'f files');
|
|
2418
|
-
if (hasWorktrees) {
|
|
2419
|
-
parts.push('w worktree');
|
|
2420
|
-
}
|
|
2421
2525
|
}
|
|
2422
2526
|
parts.push(`tab1 ${activeLabel}`);
|
|
2423
2527
|
|
|
@@ -2448,7 +2552,7 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2448
2552
|
{ text: 'Global', color: 'cyan', bold: true },
|
|
2449
2553
|
'q or Ctrl+C quit',
|
|
2450
2554
|
'r refresh snapshot',
|
|
2451
|
-
`1 active ${itemLabel} | 2 intents | 3 completed ${itemPlural} | 4 standards/health`,
|
|
2555
|
+
`1 active ${itemLabel} | 2 intents | 3 completed ${itemPlural} | 4 standards/health | 5 git changes`,
|
|
2452
2556
|
'g next section | G previous section',
|
|
2453
2557
|
'h/? toggle this shortcuts overlay',
|
|
2454
2558
|
'esc close overlays (help/preview/fullscreen)',
|
|
@@ -2457,7 +2561,6 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2457
2561
|
...(hasWorktrees ? ['b focus worktrees section', 'u focus other-worktrees section'] : []),
|
|
2458
2562
|
`a focus active ${itemLabel}`,
|
|
2459
2563
|
`f focus ${itemLabel} files`,
|
|
2460
|
-
...(hasWorktrees ? ['w open worktree switcher'] : []),
|
|
2461
2564
|
'up/down or j/k move selection',
|
|
2462
2565
|
'enter expand/collapse selected folder row',
|
|
2463
2566
|
'v or space preview selected file',
|
|
@@ -2487,6 +2590,9 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2487
2590
|
{ text: 'Tab 4 Standards/Health', color: 'magenta', bold: true },
|
|
2488
2591
|
`s standards | t stats | w warnings${showErrorSection ? ' | e errors' : ''}`,
|
|
2489
2592
|
{ text: '', color: undefined, bold: false },
|
|
2593
|
+
{ text: 'Tab 5 Git Changes', color: 'yellow', bold: true },
|
|
2594
|
+
'select changed files and preview diffs',
|
|
2595
|
+
{ text: '', color: undefined, bold: false },
|
|
2490
2596
|
{ text: `Current view: ${String(view || 'runs').toUpperCase()}`, color: 'gray', bold: false }
|
|
2491
2597
|
);
|
|
2492
2598
|
|
|
@@ -2605,20 +2711,27 @@ function buildPreviewLines(fileEntry, width, scrollOffset, options = {}) {
|
|
|
2605
2711
|
return [{ text: truncate('No file selected', width), color: 'gray', bold: false }];
|
|
2606
2712
|
}
|
|
2607
2713
|
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2714
|
+
const isGitPreview = fileEntry.previewType === 'git-diff';
|
|
2715
|
+
let rawLines = [];
|
|
2716
|
+
if (isGitPreview) {
|
|
2717
|
+
const diffText = loadGitDiffPreview(fileEntry);
|
|
2718
|
+
rawLines = String(diffText || '').split(/\r?\n/);
|
|
2719
|
+
} else {
|
|
2720
|
+
let content;
|
|
2721
|
+
try {
|
|
2722
|
+
content = fs.readFileSync(fileEntry.path, 'utf8');
|
|
2723
|
+
} catch (error) {
|
|
2724
|
+
return [{
|
|
2725
|
+
text: truncate(`Unable to read ${fileEntry.label || fileEntry.path}: ${error.message}`, width),
|
|
2726
|
+
color: 'red',
|
|
2727
|
+
bold: false
|
|
2728
|
+
}];
|
|
2729
|
+
}
|
|
2730
|
+
rawLines = String(content).split(/\r?\n/);
|
|
2617
2731
|
}
|
|
2618
2732
|
|
|
2619
|
-
const rawLines = String(content).split(/\r?\n/);
|
|
2620
2733
|
const headLine = {
|
|
2621
|
-
text: truncate(
|
|
2734
|
+
text: truncate(`${isGitPreview ? 'diff' : 'file'}: ${fileEntry.path}`, width),
|
|
2622
2735
|
color: 'cyan',
|
|
2623
2736
|
bold: true
|
|
2624
2737
|
};
|
|
@@ -2629,7 +2742,34 @@ function buildPreviewLines(fileEntry, width, scrollOffset, options = {}) {
|
|
|
2629
2742
|
|
|
2630
2743
|
const highlighted = cappedLines.map((rawLine, index) => {
|
|
2631
2744
|
const prefixedLine = `${String(index + 1).padStart(4, ' ')} | ${rawLine}`;
|
|
2632
|
-
|
|
2745
|
+
let color;
|
|
2746
|
+
let bold;
|
|
2747
|
+
let togglesCodeBlock = false;
|
|
2748
|
+
|
|
2749
|
+
if (isGitPreview) {
|
|
2750
|
+
if (rawLine.startsWith('+++ ') || rawLine.startsWith('--- ') || rawLine.startsWith('diff --git')) {
|
|
2751
|
+
color = 'cyan';
|
|
2752
|
+
bold = true;
|
|
2753
|
+
} else if (rawLine.startsWith('@@')) {
|
|
2754
|
+
color = 'magenta';
|
|
2755
|
+
bold = true;
|
|
2756
|
+
} else if (rawLine.startsWith('+')) {
|
|
2757
|
+
color = 'green';
|
|
2758
|
+
bold = false;
|
|
2759
|
+
} else if (rawLine.startsWith('-')) {
|
|
2760
|
+
color = 'red';
|
|
2761
|
+
bold = false;
|
|
2762
|
+
} else {
|
|
2763
|
+
color = undefined;
|
|
2764
|
+
bold = false;
|
|
2765
|
+
}
|
|
2766
|
+
} else {
|
|
2767
|
+
const markdownStyle = colorizeMarkdownLine(rawLine, inCodeBlock);
|
|
2768
|
+
color = markdownStyle.color;
|
|
2769
|
+
bold = markdownStyle.bold;
|
|
2770
|
+
togglesCodeBlock = markdownStyle.togglesCodeBlock;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2633
2773
|
if (togglesCodeBlock) {
|
|
2634
2774
|
inCodeBlock = !inCodeBlock;
|
|
2635
2775
|
}
|
|
@@ -2776,7 +2916,8 @@ function createDashboardApp(deps) {
|
|
|
2776
2916
|
{ id: 'runs', label: `1 ${icons.runs} ${primaryLabel}` },
|
|
2777
2917
|
{ id: 'intents', label: `2 ${icons.overview} INTENTS` },
|
|
2778
2918
|
{ id: 'completed', label: `3 ${icons.runs} ${completedLabel}` },
|
|
2779
|
-
{ id: 'health', label: `4 ${icons.health} STANDARDS/HEALTH` }
|
|
2919
|
+
{ id: 'health', label: `4 ${icons.health} STANDARDS/HEALTH` },
|
|
2920
|
+
{ id: 'git', label: `5 ${icons.git} GIT CHANGES` }
|
|
2780
2921
|
];
|
|
2781
2922
|
const maxWidth = Math.max(8, Math.floor(width));
|
|
2782
2923
|
const segments = [];
|
|
@@ -2906,7 +3047,8 @@ function createDashboardApp(deps) {
|
|
|
2906
3047
|
runs: 'current-run',
|
|
2907
3048
|
intents: 'intent-status',
|
|
2908
3049
|
completed: 'completed-runs',
|
|
2909
|
-
health: 'standards'
|
|
3050
|
+
health: 'standards',
|
|
3051
|
+
git: 'git-changes'
|
|
2910
3052
|
});
|
|
2911
3053
|
const [selectionBySection, setSelectionBySection] = useState({
|
|
2912
3054
|
worktrees: 0,
|
|
@@ -2918,7 +3060,8 @@ function createDashboardApp(deps) {
|
|
|
2918
3060
|
standards: 0,
|
|
2919
3061
|
stats: 0,
|
|
2920
3062
|
warnings: 0,
|
|
2921
|
-
'error-details': 0
|
|
3063
|
+
'error-details': 0,
|
|
3064
|
+
'git-changes': 0
|
|
2922
3065
|
});
|
|
2923
3066
|
const [expandedGroups, setExpandedGroups] = useState({});
|
|
2924
3067
|
const [previewTarget, setPreviewTarget] = useState(null);
|
|
@@ -2960,9 +3103,7 @@ function createDashboardApp(deps) {
|
|
|
2960
3103
|
const previewVisibleRows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
2961
3104
|
const showErrorPanelForSections = Boolean(error) && previewVisibleRows >= 18;
|
|
2962
3105
|
const worktreeSectionEnabled = hasMultipleWorktrees(snapshot);
|
|
2963
|
-
const otherWorktreesSectionEnabled = worktreeSectionEnabled
|
|
2964
|
-
&& isSelectedWorktreeMain(snapshot)
|
|
2965
|
-
&& getEffectiveFlow(activeFlow, snapshot) !== 'simple';
|
|
3106
|
+
const otherWorktreesSectionEnabled = worktreeSectionEnabled;
|
|
2966
3107
|
|
|
2967
3108
|
const getAvailableSections = useCallback((viewId) => {
|
|
2968
3109
|
const base = getSectionOrderForView(viewId, {
|
|
@@ -3074,6 +3215,37 @@ function createDashboardApp(deps) {
|
|
|
3074
3215
|
'No error details'
|
|
3075
3216
|
)
|
|
3076
3217
|
: toLoadingRows('Loading error details...', 'error-loading');
|
|
3218
|
+
const gitRows = shouldHydrateSecondaryTabs
|
|
3219
|
+
? (() => {
|
|
3220
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
3221
|
+
const tracking = git.upstream
|
|
3222
|
+
? `${git.upstream} (${git.ahead > 0 ? `ahead ${git.ahead}` : 'ahead 0'}, ${git.behind > 0 ? `behind ${git.behind}` : 'behind 0'})`
|
|
3223
|
+
: 'no upstream';
|
|
3224
|
+
const headerRows = [{
|
|
3225
|
+
kind: 'info',
|
|
3226
|
+
key: 'git:branch',
|
|
3227
|
+
label: git.available
|
|
3228
|
+
? `branch ${git.branch}${git.detached ? ' [detached]' : ''} | ${tracking}`
|
|
3229
|
+
: 'git: repository unavailable in selected worktree',
|
|
3230
|
+
color: git.available ? 'cyan' : 'red',
|
|
3231
|
+
bold: true,
|
|
3232
|
+
selectable: false
|
|
3233
|
+
}, {
|
|
3234
|
+
kind: 'info',
|
|
3235
|
+
key: 'git:counts',
|
|
3236
|
+
label: `changes ${git.counts.total || 0} | staged ${git.counts.staged || 0} | unstaged ${git.counts.unstaged || 0} | untracked ${git.counts.untracked || 0} | conflicts ${git.counts.conflicted || 0}`,
|
|
3237
|
+
color: 'gray',
|
|
3238
|
+
bold: false,
|
|
3239
|
+
selectable: false
|
|
3240
|
+
}];
|
|
3241
|
+
const groups = toExpandableRows(
|
|
3242
|
+
buildGitChangeGroups(snapshot),
|
|
3243
|
+
git.available ? 'Working tree clean' : 'No git changes',
|
|
3244
|
+
expandedGroups
|
|
3245
|
+
);
|
|
3246
|
+
return [...headerRows, ...groups];
|
|
3247
|
+
})()
|
|
3248
|
+
: toLoadingRows('Loading git changes...', 'git-loading');
|
|
3077
3249
|
|
|
3078
3250
|
const rowsBySection = {
|
|
3079
3251
|
worktrees: worktreeRows,
|
|
@@ -3085,7 +3257,8 @@ function createDashboardApp(deps) {
|
|
|
3085
3257
|
standards: standardsRows,
|
|
3086
3258
|
stats: statsRows,
|
|
3087
3259
|
warnings: warningsRows,
|
|
3088
|
-
'error-details': errorDetailsRows
|
|
3260
|
+
'error-details': errorDetailsRows,
|
|
3261
|
+
'git-changes': gitRows
|
|
3089
3262
|
};
|
|
3090
3263
|
const worktreeItems = getWorktreeItems(snapshot);
|
|
3091
3264
|
const selectedWorktree = getSelectedWorktree(snapshot);
|
|
@@ -3275,9 +3448,8 @@ function createDashboardApp(deps) {
|
|
|
3275
3448
|
return;
|
|
3276
3449
|
}
|
|
3277
3450
|
|
|
3278
|
-
if (
|
|
3279
|
-
|
|
3280
|
-
setWorktreeOverlayOpen(true);
|
|
3451
|
+
if (input === '5') {
|
|
3452
|
+
setUi((previous) => ({ ...previous, view: 'git' }));
|
|
3281
3453
|
setPaneFocus('main');
|
|
3282
3454
|
return;
|
|
3283
3455
|
}
|
|
@@ -3304,13 +3476,15 @@ function createDashboardApp(deps) {
|
|
|
3304
3476
|
standards: 0,
|
|
3305
3477
|
stats: 0,
|
|
3306
3478
|
warnings: 0,
|
|
3307
|
-
'error-details': 0
|
|
3479
|
+
'error-details': 0,
|
|
3480
|
+
'git-changes': 0
|
|
3308
3481
|
});
|
|
3309
3482
|
setSectionFocus({
|
|
3310
3483
|
runs: 'current-run',
|
|
3311
3484
|
intents: 'intent-status',
|
|
3312
3485
|
completed: 'completed-runs',
|
|
3313
|
-
health: 'standards'
|
|
3486
|
+
health: 'standards',
|
|
3487
|
+
git: 'git-changes'
|
|
3314
3488
|
});
|
|
3315
3489
|
setOverviewIntentFilter('next');
|
|
3316
3490
|
setExpandedGroups({});
|
|
@@ -3345,13 +3519,15 @@ function createDashboardApp(deps) {
|
|
|
3345
3519
|
standards: 0,
|
|
3346
3520
|
stats: 0,
|
|
3347
3521
|
warnings: 0,
|
|
3348
|
-
'error-details': 0
|
|
3522
|
+
'error-details': 0,
|
|
3523
|
+
'git-changes': 0
|
|
3349
3524
|
});
|
|
3350
3525
|
setSectionFocus({
|
|
3351
3526
|
runs: 'current-run',
|
|
3352
3527
|
intents: 'intent-status',
|
|
3353
3528
|
completed: 'completed-runs',
|
|
3354
|
-
health: 'standards'
|
|
3529
|
+
health: 'standards',
|
|
3530
|
+
git: 'git-changes'
|
|
3355
3531
|
});
|
|
3356
3532
|
setOverviewIntentFilter('next');
|
|
3357
3533
|
setExpandedGroups({});
|
|
@@ -3455,6 +3631,11 @@ function createDashboardApp(deps) {
|
|
|
3455
3631
|
setSectionFocus((previous) => ({ ...previous, health: 'error-details' }));
|
|
3456
3632
|
return;
|
|
3457
3633
|
}
|
|
3634
|
+
} else if (ui.view === 'git') {
|
|
3635
|
+
if (input === 'd') {
|
|
3636
|
+
setSectionFocus((previous) => ({ ...previous, git: 'git-changes' }));
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3458
3639
|
}
|
|
3459
3640
|
|
|
3460
3641
|
if (key.escape) {
|
|
@@ -3730,7 +3911,9 @@ function createDashboardApp(deps) {
|
|
|
3730
3911
|
});
|
|
3731
3912
|
|
|
3732
3913
|
runtime.start();
|
|
3733
|
-
const fallbackIntervalMs =
|
|
3914
|
+
const fallbackIntervalMs = ui.view === 'git'
|
|
3915
|
+
? Math.max(refreshMs, 1000)
|
|
3916
|
+
: Math.max(refreshMs, 5000);
|
|
3734
3917
|
const interval = setInterval(() => {
|
|
3735
3918
|
void refresh();
|
|
3736
3919
|
}, fallbackIntervalMs);
|
|
@@ -3739,7 +3922,7 @@ function createDashboardApp(deps) {
|
|
|
3739
3922
|
clearInterval(interval);
|
|
3740
3923
|
void runtime.close();
|
|
3741
3924
|
};
|
|
3742
|
-
}, [watchEnabled, refreshMs, refresh, rootPath, workspacePath, resolveRootPathForFlow, resolveRootPathsForFlow, activeFlow, worktreeWatchSignature, selectedWorktreeId]);
|
|
3925
|
+
}, [watchEnabled, refreshMs, refresh, rootPath, workspacePath, resolveRootPathForFlow, resolveRootPathsForFlow, activeFlow, ui.view, worktreeWatchSignature, selectedWorktreeId]);
|
|
3743
3926
|
|
|
3744
3927
|
useEffect(() => {
|
|
3745
3928
|
if (!stdout || typeof stdout.write !== 'function') {
|
|
@@ -3898,6 +4081,15 @@ function createDashboardApp(deps) {
|
|
|
3898
4081
|
borderColor: 'red'
|
|
3899
4082
|
});
|
|
3900
4083
|
}
|
|
4084
|
+
} else if (ui.view === 'git') {
|
|
4085
|
+
panelCandidates = [
|
|
4086
|
+
{
|
|
4087
|
+
key: 'git-changes',
|
|
4088
|
+
title: panelTitles.git || 'Git Changes',
|
|
4089
|
+
lines: sectionLines['git-changes'],
|
|
4090
|
+
borderColor: 'yellow'
|
|
4091
|
+
}
|
|
4092
|
+
];
|
|
3901
4093
|
} else {
|
|
3902
4094
|
panelCandidates = [];
|
|
3903
4095
|
if (worktreeSectionEnabled) {
|
|
@@ -15,12 +15,15 @@ function cycleView(current) {
|
|
|
15
15
|
if (current === 'completed') {
|
|
16
16
|
return 'health';
|
|
17
17
|
}
|
|
18
|
+
if (current === 'health') {
|
|
19
|
+
return 'git';
|
|
20
|
+
}
|
|
18
21
|
return 'runs';
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function cycleViewBackward(current) {
|
|
22
25
|
if (current === 'runs') {
|
|
23
|
-
return '
|
|
26
|
+
return 'git';
|
|
24
27
|
}
|
|
25
28
|
if (current === 'intents') {
|
|
26
29
|
return 'runs';
|
|
@@ -28,7 +31,10 @@ function cycleViewBackward(current) {
|
|
|
28
31
|
if (current === 'completed') {
|
|
29
32
|
return 'intents';
|
|
30
33
|
}
|
|
31
|
-
|
|
34
|
+
if (current === 'health') {
|
|
35
|
+
return 'completed';
|
|
36
|
+
}
|
|
37
|
+
return 'health';
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
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": {
|