specsmd 0.1.36 → 0.1.38
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 +135 -9
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -802,7 +802,7 @@ function getSectionOrderForView(view) {
|
|
|
802
802
|
if (view === 'health') {
|
|
803
803
|
return ['standards', 'stats', 'warnings', 'error-details'];
|
|
804
804
|
}
|
|
805
|
-
return ['current-run'];
|
|
805
|
+
return ['current-run', 'run-files'];
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
|
|
@@ -906,7 +906,8 @@ function collectSimpleSpecFiles(spec) {
|
|
|
906
906
|
}));
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
-
function getRunFileEntries(snapshot, flow) {
|
|
909
|
+
function getRunFileEntries(snapshot, flow, options = {}) {
|
|
910
|
+
const includeBacklog = options.includeBacklog !== false;
|
|
910
911
|
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
911
912
|
const entries = [];
|
|
912
913
|
const seenPaths = new Set();
|
|
@@ -917,6 +918,10 @@ function getRunFileEntries(snapshot, flow) {
|
|
|
917
918
|
pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
|
|
918
919
|
}
|
|
919
920
|
|
|
921
|
+
if (!includeBacklog) {
|
|
922
|
+
return entries;
|
|
923
|
+
}
|
|
924
|
+
|
|
920
925
|
const pendingBolts = Array.isArray(snapshot?.pendingBolts) ? snapshot.pendingBolts : [];
|
|
921
926
|
for (const pendingBolt of pendingBolts) {
|
|
922
927
|
for (const file of collectAidlcBoltFiles(pendingBolt)) {
|
|
@@ -963,6 +968,10 @@ function getRunFileEntries(snapshot, flow) {
|
|
|
963
968
|
pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
|
|
964
969
|
}
|
|
965
970
|
|
|
971
|
+
if (!includeBacklog) {
|
|
972
|
+
return entries;
|
|
973
|
+
}
|
|
974
|
+
|
|
966
975
|
const pendingSpecs = Array.isArray(snapshot?.pendingSpecs) ? snapshot.pendingSpecs : [];
|
|
967
976
|
for (const pendingSpec of pendingSpecs) {
|
|
968
977
|
for (const file of collectSimpleSpecFiles(pendingSpec)) {
|
|
@@ -985,6 +994,10 @@ function getRunFileEntries(snapshot, flow) {
|
|
|
985
994
|
pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
|
|
986
995
|
}
|
|
987
996
|
|
|
997
|
+
if (!includeBacklog) {
|
|
998
|
+
return entries;
|
|
999
|
+
}
|
|
1000
|
+
|
|
988
1001
|
const pendingItems = Array.isArray(snapshot?.pendingItems) ? snapshot.pendingItems : [];
|
|
989
1002
|
for (const pendingItem of pendingItems) {
|
|
990
1003
|
pushFileEntry(entries, seenPaths, {
|
|
@@ -1132,6 +1145,73 @@ function buildRunFileGroups(fileEntries) {
|
|
|
1132
1145
|
return groups;
|
|
1133
1146
|
}
|
|
1134
1147
|
|
|
1148
|
+
function getFileEntityLabel(fileEntry, fallbackIndex = 0) {
|
|
1149
|
+
const rawLabel = typeof fileEntry?.label === 'string' ? fileEntry.label : '';
|
|
1150
|
+
if (rawLabel.includes('/')) {
|
|
1151
|
+
return rawLabel.split('/')[0] || `item-${fallbackIndex + 1}`;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const filePath = typeof fileEntry?.path === 'string' ? fileEntry.path : '';
|
|
1155
|
+
if (filePath !== '') {
|
|
1156
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
1157
|
+
if (parentDir && parentDir !== '.' && parentDir !== path.sep) {
|
|
1158
|
+
return parentDir;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const baseName = path.basename(filePath);
|
|
1162
|
+
if (baseName) {
|
|
1163
|
+
return baseName;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
return `item-${fallbackIndex + 1}`;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function buildRunFileEntityGroups(snapshot, flow, options = {}) {
|
|
1171
|
+
const order = ['active', 'upcoming', 'completed', 'intent', 'other'];
|
|
1172
|
+
const rankByScope = new Map(order.map((scope, index) => [scope, index]));
|
|
1173
|
+
const entries = filterExistingFiles(getRunFileEntries(snapshot, flow, options));
|
|
1174
|
+
const groupsByEntity = new Map();
|
|
1175
|
+
|
|
1176
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
1177
|
+
const fileEntry = entries[index];
|
|
1178
|
+
const entity = getFileEntityLabel(fileEntry, index);
|
|
1179
|
+
const scope = order.includes(fileEntry?.scope) ? fileEntry.scope : 'other';
|
|
1180
|
+
const scopeRank = rankByScope.get(scope) ?? rankByScope.get('other');
|
|
1181
|
+
|
|
1182
|
+
if (!groupsByEntity.has(entity)) {
|
|
1183
|
+
groupsByEntity.set(entity, {
|
|
1184
|
+
entity,
|
|
1185
|
+
files: [],
|
|
1186
|
+
scope,
|
|
1187
|
+
scopeRank
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const group = groupsByEntity.get(entity);
|
|
1192
|
+
group.files.push(fileEntry);
|
|
1193
|
+
|
|
1194
|
+
if (scopeRank < group.scopeRank) {
|
|
1195
|
+
group.scopeRank = scopeRank;
|
|
1196
|
+
group.scope = scope;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return Array.from(groupsByEntity.values())
|
|
1201
|
+
.sort((a, b) => {
|
|
1202
|
+
if (a.scopeRank !== b.scopeRank) {
|
|
1203
|
+
return a.scopeRank - b.scopeRank;
|
|
1204
|
+
}
|
|
1205
|
+
return String(a.entity).localeCompare(String(b.entity));
|
|
1206
|
+
})
|
|
1207
|
+
.map((group) => ({
|
|
1208
|
+
key: `run-files:entity:${group.entity}`,
|
|
1209
|
+
label: `${group.entity} [${formatScope(group.scope)}] (${group.files.length})`,
|
|
1210
|
+
files: filterExistingFiles(group.files)
|
|
1211
|
+
}))
|
|
1212
|
+
.filter((group) => group.files.length > 0);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1135
1215
|
function normalizeInfoLine(line) {
|
|
1136
1216
|
const normalized = normalizePanelLine(line);
|
|
1137
1217
|
return {
|
|
@@ -1611,11 +1691,14 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
1611
1691
|
|
|
1612
1692
|
if (view === 'runs' || view === 'intents' || view === 'completed' || view === 'health') {
|
|
1613
1693
|
if (previewOpen) {
|
|
1614
|
-
parts.push('tab pane', '↑/↓ nav/scroll', 'v close');
|
|
1694
|
+
parts.push('tab pane', '↑/↓ nav/scroll', 'v/space close');
|
|
1615
1695
|
} else {
|
|
1616
|
-
parts.push('↑/↓ navigate', 'enter expand', 'v preview');
|
|
1696
|
+
parts.push('↑/↓ navigate', 'enter expand', 'v/space preview');
|
|
1617
1697
|
}
|
|
1618
1698
|
}
|
|
1699
|
+
if (view === 'runs') {
|
|
1700
|
+
parts.push('a current', 'f files');
|
|
1701
|
+
}
|
|
1619
1702
|
parts.push(`tab1 ${activeLabel}`);
|
|
1620
1703
|
|
|
1621
1704
|
if (availableFlowCount > 1) {
|
|
@@ -1651,9 +1734,10 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
1651
1734
|
{ text: '', color: undefined, bold: false },
|
|
1652
1735
|
{ text: 'Tab 1 Active', color: 'yellow', bold: true },
|
|
1653
1736
|
`a focus active ${itemLabel}`,
|
|
1737
|
+
`f focus ${itemLabel} files`,
|
|
1654
1738
|
'up/down or j/k move selection',
|
|
1655
1739
|
'enter expand/collapse selected folder row',
|
|
1656
|
-
'v preview selected file',
|
|
1740
|
+
'v or space preview selected file',
|
|
1657
1741
|
'v twice quickly opens fullscreen preview overlay',
|
|
1658
1742
|
'tab switch focus between main and preview panes',
|
|
1659
1743
|
'o open selected file in system default app'
|
|
@@ -1982,6 +2066,7 @@ function createDashboardApp(deps) {
|
|
|
1982
2066
|
});
|
|
1983
2067
|
const [selectionBySection, setSelectionBySection] = useState({
|
|
1984
2068
|
'current-run': 0,
|
|
2069
|
+
'run-files': 0,
|
|
1985
2070
|
'intent-status': 0,
|
|
1986
2071
|
'completed-runs': 0,
|
|
1987
2072
|
standards: 0,
|
|
@@ -2043,6 +2128,20 @@ function createDashboardApp(deps) {
|
|
|
2043
2128
|
currentExpandedGroups
|
|
2044
2129
|
);
|
|
2045
2130
|
const shouldHydrateSecondaryTabs = deferredTabsReady || ui.view !== 'runs';
|
|
2131
|
+
const runFileGroups = buildRunFileEntityGroups(snapshot, activeFlow, {
|
|
2132
|
+
includeBacklog: shouldHydrateSecondaryTabs
|
|
2133
|
+
});
|
|
2134
|
+
const runFileExpandedGroups = { ...expandedGroups };
|
|
2135
|
+
for (const group of runFileGroups) {
|
|
2136
|
+
if (runFileExpandedGroups[group.key] == null) {
|
|
2137
|
+
runFileExpandedGroups[group.key] = true;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
const runFileRows = toExpandableRows(
|
|
2141
|
+
runFileGroups,
|
|
2142
|
+
getNoFileMessage(effectiveFlow),
|
|
2143
|
+
runFileExpandedGroups
|
|
2144
|
+
);
|
|
2046
2145
|
const intentRows = shouldHydrateSecondaryTabs
|
|
2047
2146
|
? [
|
|
2048
2147
|
{
|
|
@@ -2098,6 +2197,7 @@ function createDashboardApp(deps) {
|
|
|
2098
2197
|
|
|
2099
2198
|
const rowsBySection = {
|
|
2100
2199
|
'current-run': currentRunRows,
|
|
2200
|
+
'run-files': runFileRows,
|
|
2101
2201
|
'intent-status': intentRows,
|
|
2102
2202
|
'completed-runs': completedRows,
|
|
2103
2203
|
standards: standardsRows,
|
|
@@ -2231,6 +2331,7 @@ function createDashboardApp(deps) {
|
|
|
2231
2331
|
});
|
|
2232
2332
|
setSelectionBySection({
|
|
2233
2333
|
'current-run': 0,
|
|
2334
|
+
'run-files': 0,
|
|
2234
2335
|
'intent-status': 0,
|
|
2235
2336
|
'completed-runs': 0,
|
|
2236
2337
|
standards: 0,
|
|
@@ -2268,6 +2369,7 @@ function createDashboardApp(deps) {
|
|
|
2268
2369
|
});
|
|
2269
2370
|
setSelectionBySection({
|
|
2270
2371
|
'current-run': 0,
|
|
2372
|
+
'run-files': 0,
|
|
2271
2373
|
'intent-status': 0,
|
|
2272
2374
|
'completed-runs': 0,
|
|
2273
2375
|
standards: 0,
|
|
@@ -2340,6 +2442,11 @@ function createDashboardApp(deps) {
|
|
|
2340
2442
|
setPaneFocus('main');
|
|
2341
2443
|
return;
|
|
2342
2444
|
}
|
|
2445
|
+
if (input === 'f') {
|
|
2446
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'run-files' }));
|
|
2447
|
+
setPaneFocus('main');
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2343
2450
|
} else if (ui.view === 'intents') {
|
|
2344
2451
|
if (input === 'i') {
|
|
2345
2452
|
setSectionFocus((previous) => ({ ...previous, intents: 'intent-status' }));
|
|
@@ -2426,7 +2533,7 @@ function createDashboardApp(deps) {
|
|
|
2426
2533
|
return;
|
|
2427
2534
|
}
|
|
2428
2535
|
|
|
2429
|
-
if (input === 'v') {
|
|
2536
|
+
if (input === 'v' || input === ' ' || key.space) {
|
|
2430
2537
|
const target = selectedFocusedFile || previewTarget;
|
|
2431
2538
|
if (!target) {
|
|
2432
2539
|
setStatusLine('Select a file row first.');
|
|
@@ -2500,14 +2607,19 @@ function createDashboardApp(deps) {
|
|
|
2500
2607
|
}, [activeFlow, rowLengthSignature, snapshot?.generatedAt]);
|
|
2501
2608
|
|
|
2502
2609
|
useEffect(() => {
|
|
2610
|
+
if (ui.view !== 'runs') {
|
|
2611
|
+
setDeferredTabsReady(true);
|
|
2612
|
+
return undefined;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2503
2615
|
setDeferredTabsReady(false);
|
|
2504
2616
|
const timer = setTimeout(() => {
|
|
2505
2617
|
setDeferredTabsReady(true);
|
|
2506
|
-
},
|
|
2618
|
+
}, 250);
|
|
2507
2619
|
return () => {
|
|
2508
2620
|
clearTimeout(timer);
|
|
2509
2621
|
};
|
|
2510
|
-
}, [activeFlow, snapshot?.generatedAt]);
|
|
2622
|
+
}, [activeFlow, snapshot?.generatedAt, ui.view]);
|
|
2511
2623
|
|
|
2512
2624
|
useEffect(() => {
|
|
2513
2625
|
setPaneFocus('main');
|
|
@@ -2624,13 +2736,21 @@ function createDashboardApp(deps) {
|
|
|
2624
2736
|
const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
2625
2737
|
|
|
2626
2738
|
const fullWidth = Math.max(40, cols - 1);
|
|
2739
|
+
const showFlowBar = availableFlowIds.length > 1;
|
|
2627
2740
|
const showFooterHelpLine = rows >= 10;
|
|
2628
2741
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
2629
2742
|
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp;
|
|
2630
2743
|
const showErrorInline = Boolean(error) && !showErrorPanel;
|
|
2744
|
+
const showStatusLine = statusLine !== '';
|
|
2631
2745
|
const densePanels = rows <= 28 || cols <= 120;
|
|
2632
2746
|
|
|
2633
|
-
const reservedRows =
|
|
2747
|
+
const reservedRows =
|
|
2748
|
+
2 +
|
|
2749
|
+
(showFlowBar ? 1 : 0) +
|
|
2750
|
+
(showFooterHelpLine ? 1 : 0) +
|
|
2751
|
+
(showGlobalErrorPanel ? 5 : 0) +
|
|
2752
|
+
(showErrorInline ? 1 : 0) +
|
|
2753
|
+
(showStatusLine ? 1 : 0);
|
|
2634
2754
|
const contentRowsBudget = Math.max(4, rows - reservedRows);
|
|
2635
2755
|
const ultraCompact = rows <= 14;
|
|
2636
2756
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
@@ -2752,6 +2872,12 @@ function createDashboardApp(deps) {
|
|
|
2752
2872
|
title: panelTitles.current,
|
|
2753
2873
|
lines: sectionLines['current-run'],
|
|
2754
2874
|
borderColor: 'green'
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
key: 'run-files',
|
|
2878
|
+
title: panelTitles.files,
|
|
2879
|
+
lines: sectionLines['run-files'],
|
|
2880
|
+
borderColor: 'yellow'
|
|
2755
2881
|
}
|
|
2756
2882
|
];
|
|
2757
2883
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
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": {
|