specsmd 0.1.36 → 0.1.37
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 +112 -6
- 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) {
|
|
@@ -1132,6 +1132,73 @@ function buildRunFileGroups(fileEntries) {
|
|
|
1132
1132
|
return groups;
|
|
1133
1133
|
}
|
|
1134
1134
|
|
|
1135
|
+
function getFileEntityLabel(fileEntry, fallbackIndex = 0) {
|
|
1136
|
+
const rawLabel = typeof fileEntry?.label === 'string' ? fileEntry.label : '';
|
|
1137
|
+
if (rawLabel.includes('/')) {
|
|
1138
|
+
return rawLabel.split('/')[0] || `item-${fallbackIndex + 1}`;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const filePath = typeof fileEntry?.path === 'string' ? fileEntry.path : '';
|
|
1142
|
+
if (filePath !== '') {
|
|
1143
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
1144
|
+
if (parentDir && parentDir !== '.' && parentDir !== path.sep) {
|
|
1145
|
+
return parentDir;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const baseName = path.basename(filePath);
|
|
1149
|
+
if (baseName) {
|
|
1150
|
+
return baseName;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return `item-${fallbackIndex + 1}`;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function buildRunFileEntityGroups(snapshot, flow) {
|
|
1158
|
+
const order = ['active', 'upcoming', 'completed', 'intent', 'other'];
|
|
1159
|
+
const rankByScope = new Map(order.map((scope, index) => [scope, index]));
|
|
1160
|
+
const entries = filterExistingFiles(getRunFileEntries(snapshot, flow));
|
|
1161
|
+
const groupsByEntity = new Map();
|
|
1162
|
+
|
|
1163
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
1164
|
+
const fileEntry = entries[index];
|
|
1165
|
+
const entity = getFileEntityLabel(fileEntry, index);
|
|
1166
|
+
const scope = order.includes(fileEntry?.scope) ? fileEntry.scope : 'other';
|
|
1167
|
+
const scopeRank = rankByScope.get(scope) ?? rankByScope.get('other');
|
|
1168
|
+
|
|
1169
|
+
if (!groupsByEntity.has(entity)) {
|
|
1170
|
+
groupsByEntity.set(entity, {
|
|
1171
|
+
entity,
|
|
1172
|
+
files: [],
|
|
1173
|
+
scope,
|
|
1174
|
+
scopeRank
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const group = groupsByEntity.get(entity);
|
|
1179
|
+
group.files.push(fileEntry);
|
|
1180
|
+
|
|
1181
|
+
if (scopeRank < group.scopeRank) {
|
|
1182
|
+
group.scopeRank = scopeRank;
|
|
1183
|
+
group.scope = scope;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return Array.from(groupsByEntity.values())
|
|
1188
|
+
.sort((a, b) => {
|
|
1189
|
+
if (a.scopeRank !== b.scopeRank) {
|
|
1190
|
+
return a.scopeRank - b.scopeRank;
|
|
1191
|
+
}
|
|
1192
|
+
return String(a.entity).localeCompare(String(b.entity));
|
|
1193
|
+
})
|
|
1194
|
+
.map((group) => ({
|
|
1195
|
+
key: `run-files:entity:${group.entity}`,
|
|
1196
|
+
label: `${group.entity} [${formatScope(group.scope)}] (${group.files.length})`,
|
|
1197
|
+
files: filterExistingFiles(group.files)
|
|
1198
|
+
}))
|
|
1199
|
+
.filter((group) => group.files.length > 0);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1135
1202
|
function normalizeInfoLine(line) {
|
|
1136
1203
|
const normalized = normalizePanelLine(line);
|
|
1137
1204
|
return {
|
|
@@ -1611,11 +1678,14 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
1611
1678
|
|
|
1612
1679
|
if (view === 'runs' || view === 'intents' || view === 'completed' || view === 'health') {
|
|
1613
1680
|
if (previewOpen) {
|
|
1614
|
-
parts.push('tab pane', '↑/↓ nav/scroll', 'v close');
|
|
1681
|
+
parts.push('tab pane', '↑/↓ nav/scroll', 'v/space close');
|
|
1615
1682
|
} else {
|
|
1616
|
-
parts.push('↑/↓ navigate', 'enter expand', 'v preview');
|
|
1683
|
+
parts.push('↑/↓ navigate', 'enter expand', 'v/space preview');
|
|
1617
1684
|
}
|
|
1618
1685
|
}
|
|
1686
|
+
if (view === 'runs') {
|
|
1687
|
+
parts.push('a current', 'f files');
|
|
1688
|
+
}
|
|
1619
1689
|
parts.push(`tab1 ${activeLabel}`);
|
|
1620
1690
|
|
|
1621
1691
|
if (availableFlowCount > 1) {
|
|
@@ -1651,9 +1721,10 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
1651
1721
|
{ text: '', color: undefined, bold: false },
|
|
1652
1722
|
{ text: 'Tab 1 Active', color: 'yellow', bold: true },
|
|
1653
1723
|
`a focus active ${itemLabel}`,
|
|
1724
|
+
`f focus ${itemLabel} files`,
|
|
1654
1725
|
'up/down or j/k move selection',
|
|
1655
1726
|
'enter expand/collapse selected folder row',
|
|
1656
|
-
'v preview selected file',
|
|
1727
|
+
'v or space preview selected file',
|
|
1657
1728
|
'v twice quickly opens fullscreen preview overlay',
|
|
1658
1729
|
'tab switch focus between main and preview panes',
|
|
1659
1730
|
'o open selected file in system default app'
|
|
@@ -1982,6 +2053,7 @@ function createDashboardApp(deps) {
|
|
|
1982
2053
|
});
|
|
1983
2054
|
const [selectionBySection, setSelectionBySection] = useState({
|
|
1984
2055
|
'current-run': 0,
|
|
2056
|
+
'run-files': 0,
|
|
1985
2057
|
'intent-status': 0,
|
|
1986
2058
|
'completed-runs': 0,
|
|
1987
2059
|
standards: 0,
|
|
@@ -2043,6 +2115,18 @@ function createDashboardApp(deps) {
|
|
|
2043
2115
|
currentExpandedGroups
|
|
2044
2116
|
);
|
|
2045
2117
|
const shouldHydrateSecondaryTabs = deferredTabsReady || ui.view !== 'runs';
|
|
2118
|
+
const runFileGroups = buildRunFileEntityGroups(snapshot, activeFlow);
|
|
2119
|
+
const runFileExpandedGroups = { ...expandedGroups };
|
|
2120
|
+
for (const group of runFileGroups) {
|
|
2121
|
+
if (runFileExpandedGroups[group.key] == null) {
|
|
2122
|
+
runFileExpandedGroups[group.key] = true;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
const runFileRows = toExpandableRows(
|
|
2126
|
+
runFileGroups,
|
|
2127
|
+
getNoFileMessage(effectiveFlow),
|
|
2128
|
+
runFileExpandedGroups
|
|
2129
|
+
);
|
|
2046
2130
|
const intentRows = shouldHydrateSecondaryTabs
|
|
2047
2131
|
? [
|
|
2048
2132
|
{
|
|
@@ -2098,6 +2182,7 @@ function createDashboardApp(deps) {
|
|
|
2098
2182
|
|
|
2099
2183
|
const rowsBySection = {
|
|
2100
2184
|
'current-run': currentRunRows,
|
|
2185
|
+
'run-files': runFileRows,
|
|
2101
2186
|
'intent-status': intentRows,
|
|
2102
2187
|
'completed-runs': completedRows,
|
|
2103
2188
|
standards: standardsRows,
|
|
@@ -2231,6 +2316,7 @@ function createDashboardApp(deps) {
|
|
|
2231
2316
|
});
|
|
2232
2317
|
setSelectionBySection({
|
|
2233
2318
|
'current-run': 0,
|
|
2319
|
+
'run-files': 0,
|
|
2234
2320
|
'intent-status': 0,
|
|
2235
2321
|
'completed-runs': 0,
|
|
2236
2322
|
standards: 0,
|
|
@@ -2268,6 +2354,7 @@ function createDashboardApp(deps) {
|
|
|
2268
2354
|
});
|
|
2269
2355
|
setSelectionBySection({
|
|
2270
2356
|
'current-run': 0,
|
|
2357
|
+
'run-files': 0,
|
|
2271
2358
|
'intent-status': 0,
|
|
2272
2359
|
'completed-runs': 0,
|
|
2273
2360
|
standards: 0,
|
|
@@ -2340,6 +2427,11 @@ function createDashboardApp(deps) {
|
|
|
2340
2427
|
setPaneFocus('main');
|
|
2341
2428
|
return;
|
|
2342
2429
|
}
|
|
2430
|
+
if (input === 'f') {
|
|
2431
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'run-files' }));
|
|
2432
|
+
setPaneFocus('main');
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2343
2435
|
} else if (ui.view === 'intents') {
|
|
2344
2436
|
if (input === 'i') {
|
|
2345
2437
|
setSectionFocus((previous) => ({ ...previous, intents: 'intent-status' }));
|
|
@@ -2426,7 +2518,7 @@ function createDashboardApp(deps) {
|
|
|
2426
2518
|
return;
|
|
2427
2519
|
}
|
|
2428
2520
|
|
|
2429
|
-
if (input === 'v') {
|
|
2521
|
+
if (input === 'v' || input === ' ' || key.space) {
|
|
2430
2522
|
const target = selectedFocusedFile || previewTarget;
|
|
2431
2523
|
if (!target) {
|
|
2432
2524
|
setStatusLine('Select a file row first.');
|
|
@@ -2624,13 +2716,21 @@ function createDashboardApp(deps) {
|
|
|
2624
2716
|
const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
2625
2717
|
|
|
2626
2718
|
const fullWidth = Math.max(40, cols - 1);
|
|
2719
|
+
const showFlowBar = availableFlowIds.length > 1;
|
|
2627
2720
|
const showFooterHelpLine = rows >= 10;
|
|
2628
2721
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
2629
2722
|
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp;
|
|
2630
2723
|
const showErrorInline = Boolean(error) && !showErrorPanel;
|
|
2724
|
+
const showStatusLine = statusLine !== '';
|
|
2631
2725
|
const densePanels = rows <= 28 || cols <= 120;
|
|
2632
2726
|
|
|
2633
|
-
const reservedRows =
|
|
2727
|
+
const reservedRows =
|
|
2728
|
+
2 +
|
|
2729
|
+
(showFlowBar ? 1 : 0) +
|
|
2730
|
+
(showFooterHelpLine ? 1 : 0) +
|
|
2731
|
+
(showGlobalErrorPanel ? 5 : 0) +
|
|
2732
|
+
(showErrorInline ? 1 : 0) +
|
|
2733
|
+
(showStatusLine ? 1 : 0);
|
|
2634
2734
|
const contentRowsBudget = Math.max(4, rows - reservedRows);
|
|
2635
2735
|
const ultraCompact = rows <= 14;
|
|
2636
2736
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
@@ -2752,6 +2852,12 @@ function createDashboardApp(deps) {
|
|
|
2752
2852
|
title: panelTitles.current,
|
|
2753
2853
|
lines: sectionLines['current-run'],
|
|
2754
2854
|
borderColor: 'green'
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
key: 'run-files',
|
|
2858
|
+
title: panelTitles.files,
|
|
2859
|
+
lines: sectionLines['run-files'],
|
|
2860
|
+
borderColor: 'yellow'
|
|
2755
2861
|
}
|
|
2756
2862
|
];
|
|
2757
2863
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
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": {
|