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.
@@ -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 = 2 + (showFooterHelpLine ? 1 : 0) + (showGlobalErrorPanel ? 5 : 0) + (showErrorInline ? 1 : 0);
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.36",
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": {