specsmd 0.1.53 → 0.1.54
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 +286 -30
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -1172,6 +1172,215 @@ function getGitChangesSnapshot(snapshot) {
|
|
|
1172
1172
|
};
|
|
1173
1173
|
}
|
|
1174
1174
|
|
|
1175
|
+
function readGitCommandLines(repoRoot, args, options = {}) {
|
|
1176
|
+
if (typeof repoRoot !== 'string' || repoRoot.trim() === '' || !Array.isArray(args) || args.length === 0) {
|
|
1177
|
+
return [];
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const acceptedStatuses = Array.isArray(options.acceptedStatuses) && options.acceptedStatuses.length > 0
|
|
1181
|
+
? options.acceptedStatuses
|
|
1182
|
+
: [0];
|
|
1183
|
+
|
|
1184
|
+
const result = spawnSync('git', args, {
|
|
1185
|
+
cwd: repoRoot,
|
|
1186
|
+
encoding: 'utf8',
|
|
1187
|
+
maxBuffer: 8 * 1024 * 1024
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
if (result.error) {
|
|
1191
|
+
return [];
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (typeof result.status === 'number' && !acceptedStatuses.includes(result.status)) {
|
|
1195
|
+
return [];
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const lines = String(result.stdout || '')
|
|
1199
|
+
.split(/\r?\n/)
|
|
1200
|
+
.map((line) => line.trim())
|
|
1201
|
+
.filter(Boolean);
|
|
1202
|
+
|
|
1203
|
+
const limit = Number.isFinite(options.limit) ? Math.max(1, Math.floor(options.limit)) : null;
|
|
1204
|
+
if (limit == null || lines.length <= limit) {
|
|
1205
|
+
return lines;
|
|
1206
|
+
}
|
|
1207
|
+
return lines.slice(0, limit);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function buildGitStatusPanelLines(snapshot) {
|
|
1211
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1212
|
+
if (!git.available) {
|
|
1213
|
+
return [{
|
|
1214
|
+
text: 'Repository unavailable in selected worktree',
|
|
1215
|
+
color: 'red',
|
|
1216
|
+
bold: true
|
|
1217
|
+
}];
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const tracking = git.upstream
|
|
1221
|
+
? `${git.upstream} (${git.ahead > 0 ? `ahead ${git.ahead}` : 'ahead 0'}, ${git.behind > 0 ? `behind ${git.behind}` : 'behind 0'})`
|
|
1222
|
+
: 'no upstream';
|
|
1223
|
+
|
|
1224
|
+
return [
|
|
1225
|
+
{
|
|
1226
|
+
text: `branch: ${git.branch}${git.detached ? ' [detached]' : ''}`,
|
|
1227
|
+
color: 'green',
|
|
1228
|
+
bold: true
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
text: `tracking: ${tracking}`,
|
|
1232
|
+
color: 'gray',
|
|
1233
|
+
bold: false
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
text: `changes: ${git.counts.total || 0} total`,
|
|
1237
|
+
color: 'gray',
|
|
1238
|
+
bold: false
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
text: `staged ${git.counts.staged || 0} | unstaged ${git.counts.unstaged || 0}`,
|
|
1242
|
+
color: 'yellow',
|
|
1243
|
+
bold: false
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
text: `untracked ${git.counts.untracked || 0} | conflicts ${git.counts.conflicted || 0}`,
|
|
1247
|
+
color: 'yellow',
|
|
1248
|
+
bold: false
|
|
1249
|
+
}
|
|
1250
|
+
];
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function buildGitBranchesPanelLines(snapshot) {
|
|
1254
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1255
|
+
if (!git.available) {
|
|
1256
|
+
return [{
|
|
1257
|
+
text: 'No branch list (git unavailable)',
|
|
1258
|
+
color: 'gray',
|
|
1259
|
+
bold: false
|
|
1260
|
+
}];
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const branchLines = readGitCommandLines(git.rootPath, [
|
|
1264
|
+
'-c',
|
|
1265
|
+
'color.ui=false',
|
|
1266
|
+
'for-each-ref',
|
|
1267
|
+
'--sort=-committerdate',
|
|
1268
|
+
'--format=%(if)%(HEAD)%(then)*%(else) %(end) %(refname:short)',
|
|
1269
|
+
'refs/heads'
|
|
1270
|
+
], { limit: 8 });
|
|
1271
|
+
|
|
1272
|
+
const lines = [{
|
|
1273
|
+
text: 'Local branches · Remotes · Tags',
|
|
1274
|
+
color: 'cyan',
|
|
1275
|
+
bold: true
|
|
1276
|
+
}];
|
|
1277
|
+
|
|
1278
|
+
if (branchLines.length === 0) {
|
|
1279
|
+
lines.push({
|
|
1280
|
+
text: 'No local branches found',
|
|
1281
|
+
color: 'gray',
|
|
1282
|
+
bold: false
|
|
1283
|
+
});
|
|
1284
|
+
return lines;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
for (const line of branchLines) {
|
|
1288
|
+
const isCurrent = line.startsWith('*');
|
|
1289
|
+
lines.push({
|
|
1290
|
+
text: line,
|
|
1291
|
+
color: isCurrent ? 'green' : 'gray',
|
|
1292
|
+
bold: isCurrent
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return lines;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function buildGitCommitsPanelLines(snapshot) {
|
|
1300
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1301
|
+
if (!git.available) {
|
|
1302
|
+
return [{
|
|
1303
|
+
text: 'No commit log (git unavailable)',
|
|
1304
|
+
color: 'gray',
|
|
1305
|
+
bold: false
|
|
1306
|
+
}];
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const reflogLines = readGitCommandLines(git.rootPath, [
|
|
1310
|
+
'-c',
|
|
1311
|
+
'color.ui=false',
|
|
1312
|
+
'reflog',
|
|
1313
|
+
'--date=relative',
|
|
1314
|
+
'--pretty=format:%h %gs'
|
|
1315
|
+
], { limit: 8 });
|
|
1316
|
+
|
|
1317
|
+
const lines = [{
|
|
1318
|
+
text: 'Commits · Reflog',
|
|
1319
|
+
color: 'cyan',
|
|
1320
|
+
bold: true
|
|
1321
|
+
}];
|
|
1322
|
+
|
|
1323
|
+
if (reflogLines.length === 0) {
|
|
1324
|
+
lines.push({
|
|
1325
|
+
text: 'No reflog entries',
|
|
1326
|
+
color: 'gray',
|
|
1327
|
+
bold: false
|
|
1328
|
+
});
|
|
1329
|
+
return lines;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
lines.push(...reflogLines.map((line) => ({
|
|
1333
|
+
text: line,
|
|
1334
|
+
color: 'gray',
|
|
1335
|
+
bold: false
|
|
1336
|
+
})));
|
|
1337
|
+
return lines;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function buildGitStashPanelLines(snapshot) {
|
|
1341
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1342
|
+
if (!git.available) {
|
|
1343
|
+
return [{
|
|
1344
|
+
text: 'No stash info (git unavailable)',
|
|
1345
|
+
color: 'gray',
|
|
1346
|
+
bold: false
|
|
1347
|
+
}];
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const stashLines = readGitCommandLines(git.rootPath, [
|
|
1351
|
+
'-c',
|
|
1352
|
+
'color.ui=false',
|
|
1353
|
+
'stash',
|
|
1354
|
+
'list',
|
|
1355
|
+
'--pretty=format:%gd %s'
|
|
1356
|
+
], {
|
|
1357
|
+
acceptedStatuses: [0],
|
|
1358
|
+
limit: 4
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
const lines = [{
|
|
1362
|
+
text: 'Stash',
|
|
1363
|
+
color: 'cyan',
|
|
1364
|
+
bold: true
|
|
1365
|
+
}];
|
|
1366
|
+
|
|
1367
|
+
if (stashLines.length === 0) {
|
|
1368
|
+
lines.push({
|
|
1369
|
+
text: 'No stashes',
|
|
1370
|
+
color: 'gray',
|
|
1371
|
+
bold: false
|
|
1372
|
+
});
|
|
1373
|
+
return lines;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
lines.push(...stashLines.map((line) => ({
|
|
1377
|
+
text: line,
|
|
1378
|
+
color: 'gray',
|
|
1379
|
+
bold: false
|
|
1380
|
+
})));
|
|
1381
|
+
return lines;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1175
1384
|
function getDashboardWorktreeMeta(snapshot) {
|
|
1176
1385
|
if (!snapshot || typeof snapshot !== 'object') {
|
|
1177
1386
|
return null;
|
|
@@ -2420,6 +2629,21 @@ function rowToFileEntry(row) {
|
|
|
2420
2629
|
};
|
|
2421
2630
|
}
|
|
2422
2631
|
|
|
2632
|
+
function firstFileEntryFromRows(rows) {
|
|
2633
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
2634
|
+
return null;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
for (const row of rows) {
|
|
2638
|
+
const entry = rowToFileEntry(row);
|
|
2639
|
+
if (entry) {
|
|
2640
|
+
return entry;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
return null;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2423
2647
|
function rowToWorktreeId(row) {
|
|
2424
2648
|
if (!row || typeof row.key !== 'string') {
|
|
2425
2649
|
return null;
|
|
@@ -3277,32 +3501,11 @@ function createDashboardApp(deps) {
|
|
|
3277
3501
|
const gitRows = shouldHydrateSecondaryTabs
|
|
3278
3502
|
? (() => {
|
|
3279
3503
|
const git = getGitChangesSnapshot(snapshot);
|
|
3280
|
-
|
|
3281
|
-
? `${git.upstream} (${git.ahead > 0 ? `ahead ${git.ahead}` : 'ahead 0'}, ${git.behind > 0 ? `behind ${git.behind}` : 'behind 0'})`
|
|
3282
|
-
: 'no upstream';
|
|
3283
|
-
const headerRows = [{
|
|
3284
|
-
kind: 'info',
|
|
3285
|
-
key: 'git:branch',
|
|
3286
|
-
label: git.available
|
|
3287
|
-
? `branch ${git.branch}${git.detached ? ' [detached]' : ''} | ${tracking}`
|
|
3288
|
-
: 'git: repository unavailable in selected worktree',
|
|
3289
|
-
color: git.available ? 'cyan' : 'red',
|
|
3290
|
-
bold: true,
|
|
3291
|
-
selectable: false
|
|
3292
|
-
}, {
|
|
3293
|
-
kind: 'info',
|
|
3294
|
-
key: 'git:counts',
|
|
3295
|
-
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}`,
|
|
3296
|
-
color: 'gray',
|
|
3297
|
-
bold: false,
|
|
3298
|
-
selectable: false
|
|
3299
|
-
}];
|
|
3300
|
-
const groups = toExpandableRows(
|
|
3504
|
+
return toExpandableRows(
|
|
3301
3505
|
buildGitChangeGroups(snapshot),
|
|
3302
3506
|
git.available ? 'Working tree clean' : 'No git changes',
|
|
3303
3507
|
expandedGroups
|
|
3304
3508
|
);
|
|
3305
|
-
return [...headerRows, ...groups];
|
|
3306
3509
|
})()
|
|
3307
3510
|
: toLoadingRows('Loading git changes...', 'git-loading');
|
|
3308
3511
|
|
|
@@ -3338,6 +3541,9 @@ function createDashboardApp(deps) {
|
|
|
3338
3541
|
const focusedIndex = selectionBySection[focusedSection] || 0;
|
|
3339
3542
|
const selectedFocusedRow = getSelectedRow(focusedRows, focusedIndex);
|
|
3340
3543
|
const selectedFocusedFile = rowToFileEntry(selectedFocusedRow);
|
|
3544
|
+
const selectedGitRow = getSelectedRow(gitRows, selectionBySection['git-changes'] || 0);
|
|
3545
|
+
const selectedGitFile = rowToFileEntry(selectedGitRow);
|
|
3546
|
+
const firstGitFile = firstFileEntryFromRows(gitRows);
|
|
3341
3547
|
|
|
3342
3548
|
const refresh = useCallback(async (overrideSelectedWorktreeId = null) => {
|
|
3343
3549
|
const now = new Date().toISOString();
|
|
@@ -4066,6 +4272,16 @@ function createDashboardApp(deps) {
|
|
|
4066
4272
|
fullDocument: overlayPreviewOpen
|
|
4067
4273
|
})
|
|
4068
4274
|
: [];
|
|
4275
|
+
const gitInlineDiffTarget = selectedGitFile || firstGitFile || previewTarget || null;
|
|
4276
|
+
const gitInlineDiffLines = ui.view === 'git'
|
|
4277
|
+
? buildPreviewLines(gitInlineDiffTarget, compactWidth, previewOpen && paneFocus === 'preview' ? previewScroll : 0, {
|
|
4278
|
+
fullDocument: false
|
|
4279
|
+
})
|
|
4280
|
+
: [];
|
|
4281
|
+
const gitStatusPanelLines = ui.view === 'git' ? buildGitStatusPanelLines(snapshot) : [];
|
|
4282
|
+
const gitBranchesPanelLines = ui.view === 'git' ? buildGitBranchesPanelLines(snapshot) : [];
|
|
4283
|
+
const gitCommitsPanelLines = ui.view === 'git' ? buildGitCommitsPanelLines(snapshot) : [];
|
|
4284
|
+
const gitStashPanelLines = ui.view === 'git' ? buildGitStashPanelLines(snapshot) : [];
|
|
4069
4285
|
|
|
4070
4286
|
const shortcutsOverlayLines = buildHelpOverlayLines({
|
|
4071
4287
|
view: ui.view,
|
|
@@ -4173,11 +4389,41 @@ function createDashboardApp(deps) {
|
|
|
4173
4389
|
}
|
|
4174
4390
|
} else if (ui.view === 'git') {
|
|
4175
4391
|
panelCandidates = [
|
|
4392
|
+
{
|
|
4393
|
+
key: 'git-status',
|
|
4394
|
+
title: '[1]-Status',
|
|
4395
|
+
lines: gitStatusPanelLines,
|
|
4396
|
+
borderColor: 'green'
|
|
4397
|
+
},
|
|
4176
4398
|
{
|
|
4177
4399
|
key: 'git-changes',
|
|
4178
|
-
title:
|
|
4400
|
+
title: '[2]-Files',
|
|
4179
4401
|
lines: sectionLines['git-changes'],
|
|
4180
4402
|
borderColor: 'yellow'
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
key: 'git-branches',
|
|
4406
|
+
title: '[3]-Local branches',
|
|
4407
|
+
lines: gitBranchesPanelLines,
|
|
4408
|
+
borderColor: 'magenta'
|
|
4409
|
+
},
|
|
4410
|
+
{
|
|
4411
|
+
key: 'git-commits',
|
|
4412
|
+
title: '[4]-Commits',
|
|
4413
|
+
lines: gitCommitsPanelLines,
|
|
4414
|
+
borderColor: 'cyan'
|
|
4415
|
+
},
|
|
4416
|
+
{
|
|
4417
|
+
key: 'git-stash',
|
|
4418
|
+
title: '[5]-Stash',
|
|
4419
|
+
lines: gitStashPanelLines,
|
|
4420
|
+
borderColor: 'blue'
|
|
4421
|
+
},
|
|
4422
|
+
{
|
|
4423
|
+
key: 'git-diff',
|
|
4424
|
+
title: '[0]-Unstaged changes',
|
|
4425
|
+
lines: gitInlineDiffLines,
|
|
4426
|
+
borderColor: 'yellow'
|
|
4181
4427
|
}
|
|
4182
4428
|
];
|
|
4183
4429
|
} else {
|
|
@@ -4214,7 +4460,7 @@ function createDashboardApp(deps) {
|
|
|
4214
4460
|
}
|
|
4215
4461
|
}
|
|
4216
4462
|
|
|
4217
|
-
if (!ui.showHelp && previewOpen && !overlayPreviewOpen) {
|
|
4463
|
+
if (!ui.showHelp && previewOpen && !overlayPreviewOpen && ui.view !== 'git') {
|
|
4218
4464
|
panelCandidates.push({
|
|
4219
4465
|
key: 'preview',
|
|
4220
4466
|
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
@@ -4225,7 +4471,13 @@ function createDashboardApp(deps) {
|
|
|
4225
4471
|
|
|
4226
4472
|
if (ultraCompact) {
|
|
4227
4473
|
if (previewOpen) {
|
|
4228
|
-
panelCandidates = panelCandidates.filter((panel) =>
|
|
4474
|
+
panelCandidates = panelCandidates.filter((panel) =>
|
|
4475
|
+
panel && (
|
|
4476
|
+
panel.key === focusedSection
|
|
4477
|
+
|| panel.key === 'preview'
|
|
4478
|
+
|| (ui.view === 'git' && panel.key === 'git-diff')
|
|
4479
|
+
)
|
|
4480
|
+
);
|
|
4229
4481
|
} else {
|
|
4230
4482
|
const focusedPanel = panelCandidates.find((panel) => panel?.key === focusedSection);
|
|
4231
4483
|
panelCandidates = [focusedPanel || panelCandidates[0]];
|
|
@@ -4233,7 +4485,8 @@ function createDashboardApp(deps) {
|
|
|
4233
4485
|
}
|
|
4234
4486
|
|
|
4235
4487
|
const panels = allocateSingleColumnPanels(panelCandidates, contentRowsBudget);
|
|
4236
|
-
const lazyGitHierarchyLayout =
|
|
4488
|
+
const lazyGitHierarchyLayout = ui.view === 'git'
|
|
4489
|
+
&& !ui.showHelp
|
|
4237
4490
|
&& !worktreeOverlayOpen
|
|
4238
4491
|
&& !overlayPreviewOpen
|
|
4239
4492
|
&& !ultraCompact
|
|
@@ -4254,9 +4507,10 @@ function createDashboardApp(deps) {
|
|
|
4254
4507
|
|
|
4255
4508
|
let contentNode;
|
|
4256
4509
|
if (lazyGitHierarchyLayout) {
|
|
4257
|
-
const preferredRightPanel =
|
|
4258
|
-
|
|
4259
|
-
|
|
4510
|
+
const preferredRightPanel = panelCandidates.find((panel) => panel?.key === 'git-diff')
|
|
4511
|
+
|| (previewOpen && !overlayPreviewOpen
|
|
4512
|
+
? panelCandidates.find((panel) => panel?.key === 'preview')
|
|
4513
|
+
: null);
|
|
4260
4514
|
const focusedPanel = panelCandidates.find((panel) => panel?.key === focusedSection);
|
|
4261
4515
|
const rightPanelBase = preferredRightPanel
|
|
4262
4516
|
|| focusedPanel
|
|
@@ -4307,7 +4561,9 @@ function createDashboardApp(deps) {
|
|
|
4307
4561
|
dense: densePanels,
|
|
4308
4562
|
focused: rightPanel.key === 'preview'
|
|
4309
4563
|
? paneFocus === 'preview'
|
|
4310
|
-
: (
|
|
4564
|
+
: (rightPanel.key === 'git-diff'
|
|
4565
|
+
? true
|
|
4566
|
+
: (paneFocus === 'main' && rightPanel.key === focusedSection))
|
|
4311
4567
|
})
|
|
4312
4568
|
)
|
|
4313
4569
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
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": {
|