specsmd 0.1.33 → 0.1.35

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.
@@ -689,15 +689,70 @@ function buildOverviewProjectLines(snapshot, width, flow) {
689
689
  return buildFireOverviewProjectLines(snapshot, width);
690
690
  }
691
691
 
692
- function buildOverviewIntentLines(snapshot, width, flow) {
692
+ function listOverviewIntentEntries(snapshot, flow) {
693
693
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
694
694
  if (effectiveFlow === 'aidlc') {
695
- return buildAidlcOverviewIntentLines(snapshot, width);
695
+ const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
696
+ return intents.map((intent) => ({
697
+ id: intent?.id || 'unknown',
698
+ status: intent?.status || 'pending',
699
+ line: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${intent?.completedStories || 0}/${intent?.storyCount || 0} stories, ${intent?.completedUnits || 0}/${intent?.unitCount || 0} units)`
700
+ }));
696
701
  }
697
702
  if (effectiveFlow === 'simple') {
698
- return buildSimpleOverviewIntentLines(snapshot, width);
703
+ const specs = Array.isArray(snapshot?.specs) ? snapshot.specs : [];
704
+ return specs.map((spec) => ({
705
+ id: spec?.name || 'unknown',
706
+ status: spec?.state || 'pending',
707
+ line: `${spec?.name || 'unknown'}: ${spec?.state || 'pending'} (${spec?.tasksCompleted || 0}/${spec?.tasksTotal || 0} tasks)`
708
+ }));
699
709
  }
700
- return buildFireOverviewIntentLines(snapshot, width);
710
+ const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
711
+ return intents.map((intent) => {
712
+ const workItems = Array.isArray(intent?.workItems) ? intent.workItems : [];
713
+ const done = workItems.filter((item) => item.status === 'completed').length;
714
+ return {
715
+ id: intent?.id || 'unknown',
716
+ status: intent?.status || 'pending',
717
+ line: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${done}/${workItems.length} work items)`
718
+ };
719
+ });
720
+ }
721
+
722
+ function buildOverviewIntentLines(snapshot, width, flow, filter = 'next') {
723
+ const entries = listOverviewIntentEntries(snapshot, flow);
724
+ const normalizedFilter = filter === 'completed' ? 'completed' : 'next';
725
+ const isNextFilter = normalizedFilter === 'next';
726
+ const nextLabel = isNextFilter ? '[NEXT]' : ' next ';
727
+ const completedLabel = !isNextFilter ? '[COMPLETED]' : ' completed ';
728
+
729
+ const filtered = entries.filter((entry) => {
730
+ if (normalizedFilter === 'completed') {
731
+ return entry.status === 'completed';
732
+ }
733
+ return entry.status !== 'completed';
734
+ });
735
+
736
+ const lines = [{
737
+ text: truncate(`filter ${nextLabel} | ${completedLabel} (←/→ or n/x)`, width),
738
+ color: 'cyan',
739
+ bold: true
740
+ }];
741
+
742
+ if (filtered.length === 0) {
743
+ lines.push({
744
+ text: truncate(
745
+ normalizedFilter === 'completed' ? 'No completed intents yet' : 'No upcoming intents',
746
+ width
747
+ ),
748
+ color: 'gray',
749
+ bold: false
750
+ });
751
+ return lines;
752
+ }
753
+
754
+ lines.push(...filtered.map((entry) => truncate(entry.line, width)));
755
+ return lines;
701
756
  }
702
757
 
703
758
  function buildOverviewStandardsLines(snapshot, width, flow) {
@@ -739,12 +794,12 @@ function getPanelTitles(flow, snapshot) {
739
794
 
740
795
  function getSectionOrderForView(view) {
741
796
  if (view === 'overview') {
742
- return ['project', 'intent-status', 'standards'];
797
+ return ['intent-status', 'completed-runs', 'standards', 'project'];
743
798
  }
744
799
  if (view === 'health') {
745
800
  return ['stats', 'warnings', 'error-details'];
746
801
  }
747
- return ['current-run', 'run-files', 'pending', 'completed'];
802
+ return ['current-run', 'run-files', 'pending'];
748
803
  }
749
804
 
750
805
  function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
@@ -1342,6 +1397,81 @@ function openFileWithDefaultApp(filePath) {
1342
1397
  };
1343
1398
  }
1344
1399
 
1400
+ function buildQuickHelpText(view, options = {}) {
1401
+ const {
1402
+ previewOpen = false,
1403
+ availableFlowCount = 1
1404
+ } = options;
1405
+
1406
+ const parts = ['1/2/3 views', 'g/G sections'];
1407
+
1408
+ if (view === 'runs') {
1409
+ if (previewOpen) {
1410
+ parts.push('tab pane', '↑/↓ nav/scroll', 'v close');
1411
+ } else {
1412
+ parts.push('↑/↓ navigate', 'enter expand', 'v preview');
1413
+ }
1414
+ }
1415
+
1416
+ if (availableFlowCount > 1) {
1417
+ parts.push('[/] flow');
1418
+ }
1419
+
1420
+ parts.push('r refresh', '? shortcuts', 'q quit');
1421
+ return parts.join(' | ');
1422
+ }
1423
+
1424
+ function buildHelpOverlayLines(options = {}) {
1425
+ const {
1426
+ view = 'runs',
1427
+ previewOpen = false,
1428
+ paneFocus = 'main',
1429
+ availableFlowCount = 1,
1430
+ showErrorSection = false
1431
+ } = options;
1432
+
1433
+ const lines = [
1434
+ { text: 'Global', color: 'cyan', bold: true },
1435
+ 'q or Ctrl+C quit',
1436
+ 'r refresh snapshot',
1437
+ '1 runs | 2 overview | 3 health',
1438
+ 'g next section | G previous section',
1439
+ 'h/? toggle this shortcuts overlay',
1440
+ 'esc close overlays (help/preview/fullscreen)',
1441
+ { text: '', color: undefined, bold: false },
1442
+ { text: 'Runs View', color: 'yellow', bold: true },
1443
+ 'a current | f files | p pending',
1444
+ 'up/down or j/k move selection',
1445
+ 'enter expand/collapse pending/completed groups',
1446
+ 'v preview selected file',
1447
+ 'v twice quickly opens fullscreen preview overlay',
1448
+ 'tab switch focus between main and preview panes',
1449
+ 'o open selected file in system default app'
1450
+ ];
1451
+
1452
+ if (previewOpen) {
1453
+ lines.push(`preview is open (focus: ${paneFocus})`);
1454
+ }
1455
+
1456
+ if (availableFlowCount > 1) {
1457
+ lines.push('[/] (and m) switch flow');
1458
+ }
1459
+
1460
+ lines.push(
1461
+ { text: '', color: undefined, bold: false },
1462
+ { text: 'Overview View', color: 'green', bold: true },
1463
+ 'i intents | c completed | s standards | p project',
1464
+ 'when Intents is focused: n next | x completed | left/right toggle filter',
1465
+ { text: '', color: undefined, bold: false },
1466
+ { text: 'Health View', color: 'magenta', bold: true },
1467
+ `t stats | w warnings${showErrorSection ? ' | e errors' : ''}`,
1468
+ { text: '', color: undefined, bold: false },
1469
+ { text: `Current view: ${String(view || 'runs').toUpperCase()}`, color: 'gray', bold: false }
1470
+ );
1471
+
1472
+ return lines;
1473
+ }
1474
+
1345
1475
  function colorizeMarkdownLine(line, inCodeBlock) {
1346
1476
  const text = String(line ?? '');
1347
1477
 
@@ -1638,6 +1768,7 @@ function createDashboardApp(deps) {
1638
1768
  });
1639
1769
  const [expandedGroups, setExpandedGroups] = useState({});
1640
1770
  const [previewTarget, setPreviewTarget] = useState(null);
1771
+ const [overviewIntentFilter, setOverviewIntentFilter] = useState('next');
1641
1772
  const [previewOpen, setPreviewOpen] = useState(false);
1642
1773
  const [paneFocus, setPaneFocus] = useState('main');
1643
1774
  const [overlayPreviewOpen, setOverlayPreviewOpen] = useState(false);
@@ -1767,6 +1898,15 @@ function createDashboardApp(deps) {
1767
1898
  return;
1768
1899
  }
1769
1900
 
1901
+ if (key.escape && ui.showHelp) {
1902
+ setUi((previous) => ({ ...previous, showHelp: false }));
1903
+ return;
1904
+ }
1905
+
1906
+ if (ui.showHelp) {
1907
+ return;
1908
+ }
1909
+
1770
1910
  if (input === '1') {
1771
1911
  setUi((previous) => ({ ...previous, view: 'runs' }));
1772
1912
  setPaneFocus('main');
@@ -1807,6 +1947,7 @@ function createDashboardApp(deps) {
1807
1947
  overview: 'project',
1808
1948
  health: 'stats'
1809
1949
  });
1950
+ setOverviewIntentFilter('next');
1810
1951
  setExpandedGroups({});
1811
1952
  setPreviewTarget(null);
1812
1953
  setPreviewOpen(false);
@@ -1838,6 +1979,7 @@ function createDashboardApp(deps) {
1838
1979
  overview: 'project',
1839
1980
  health: 'stats'
1840
1981
  });
1982
+ setOverviewIntentFilter('next');
1841
1983
  setExpandedGroups({});
1842
1984
  setPreviewTarget(null);
1843
1985
  setPreviewOpen(false);
@@ -1857,7 +1999,22 @@ function createDashboardApp(deps) {
1857
1999
  return;
1858
2000
  }
1859
2001
 
1860
- if (key.rightArrow || input === 'g') {
2002
+ if (ui.view === 'overview' && activeSection === 'intent-status') {
2003
+ if (input === 'n') {
2004
+ setOverviewIntentFilter('next');
2005
+ return;
2006
+ }
2007
+ if (input === 'x') {
2008
+ setOverviewIntentFilter('completed');
2009
+ return;
2010
+ }
2011
+ if (key.rightArrow || key.leftArrow) {
2012
+ setOverviewIntentFilter((previous) => (previous === 'completed' ? 'next' : 'completed'));
2013
+ return;
2014
+ }
2015
+ }
2016
+
2017
+ if (input === 'g' || key.rightArrow) {
1861
2018
  setSectionFocus((previous) => ({
1862
2019
  ...previous,
1863
2020
  [ui.view]: cycleSection(ui.view, activeSection, 1, availableSections)
@@ -1866,7 +2023,7 @@ function createDashboardApp(deps) {
1866
2023
  return;
1867
2024
  }
1868
2025
 
1869
- if (key.leftArrow || input === 'G') {
2026
+ if (input === 'G' || key.leftArrow) {
1870
2027
  setSectionFocus((previous) => ({
1871
2028
  ...previous,
1872
2029
  [ui.view]: cycleSection(ui.view, activeSection, -1, availableSections)
@@ -1891,11 +2048,6 @@ function createDashboardApp(deps) {
1891
2048
  setPaneFocus('main');
1892
2049
  return;
1893
2050
  }
1894
- if (input === 'c') {
1895
- setSectionFocus((previous) => ({ ...previous, runs: 'completed' }));
1896
- setPaneFocus('main');
1897
- return;
1898
- }
1899
2051
  } else if (ui.view === 'overview') {
1900
2052
  if (input === 'p') {
1901
2053
  setSectionFocus((previous) => ({ ...previous, overview: 'project' }));
@@ -1909,6 +2061,10 @@ function createDashboardApp(deps) {
1909
2061
  setSectionFocus((previous) => ({ ...previous, overview: 'standards' }));
1910
2062
  return;
1911
2063
  }
2064
+ if (input === 'c') {
2065
+ setSectionFocus((previous) => ({ ...previous, overview: 'completed-runs' }));
2066
+ return;
2067
+ }
1912
2068
  } else if (ui.view === 'health') {
1913
2069
  if (input === 't') {
1914
2070
  setSectionFocus((previous) => ({ ...previous, health: 'stats' }));
@@ -2168,16 +2324,16 @@ function createDashboardApp(deps) {
2168
2324
  const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
2169
2325
 
2170
2326
  const fullWidth = Math.max(40, cols - 1);
2171
- const showHelpLine = ui.showHelp && rows >= 14;
2327
+ const showFooterHelpLine = rows >= 10;
2172
2328
  const showErrorPanel = Boolean(error) && rows >= 18;
2173
2329
  const showErrorInline = Boolean(error) && !showErrorPanel;
2174
2330
  const densePanels = rows <= 28 || cols <= 120;
2175
2331
 
2176
- const reservedRows = 2 + (showHelpLine ? 1 : 0) + (showErrorPanel ? 5 : 0) + (showErrorInline ? 1 : 0);
2332
+ const reservedRows = 2 + (showFooterHelpLine ? 1 : 0) + (showErrorPanel ? 5 : 0) + (showErrorInline ? 1 : 0);
2177
2333
  const contentRowsBudget = Math.max(4, rows - reservedRows);
2178
2334
  const ultraCompact = rows <= 14;
2179
2335
  const panelTitles = getPanelTitles(activeFlow, snapshot);
2180
- const splitPreviewLayout = ui.view === 'runs' && previewOpen && !overlayPreviewOpen && cols >= 110 && rows >= 16;
2336
+ const splitPreviewLayout = ui.view === 'runs' && previewOpen && !overlayPreviewOpen && !ui.showHelp && cols >= 110 && rows >= 16;
2181
2337
  const mainPaneWidth = splitPreviewLayout
2182
2338
  ? Math.max(34, Math.floor((fullWidth - 1) * 0.52))
2183
2339
  : fullWidth;
@@ -2201,13 +2357,6 @@ function createDashboardApp(deps) {
2201
2357
  mainCompactWidth,
2202
2358
  ui.view === 'runs' && focusedSection === 'pending' && paneFocus === 'main'
2203
2359
  );
2204
- const completedLines = buildInteractiveRowsLines(
2205
- completedRows,
2206
- selectionBySection.completed || 0,
2207
- icons,
2208
- mainCompactWidth,
2209
- ui.view === 'runs' && focusedSection === 'completed' && paneFocus === 'main'
2210
- );
2211
2360
  const effectivePreviewTarget = previewTarget || selectedFocusedFile;
2212
2361
  const previewLines = previewOpen
2213
2362
  ? buildPreviewLines(effectivePreviewTarget, previewCompactWidth, previewScroll, {
@@ -2215,8 +2364,30 @@ function createDashboardApp(deps) {
2215
2364
  })
2216
2365
  : [];
2217
2366
 
2367
+ const shortcutsOverlayLines = buildHelpOverlayLines({
2368
+ view: ui.view,
2369
+ previewOpen,
2370
+ paneFocus,
2371
+ availableFlowCount: availableFlowIds.length,
2372
+ showErrorSection: showErrorPanel
2373
+ });
2374
+ const quickHelpText = buildQuickHelpText(ui.view, {
2375
+ previewOpen,
2376
+ paneFocus,
2377
+ availableFlowCount: availableFlowIds.length
2378
+ });
2379
+
2218
2380
  let panelCandidates;
2219
- if (ui.view === 'runs' && previewOpen && overlayPreviewOpen) {
2381
+ if (ui.showHelp) {
2382
+ panelCandidates = [
2383
+ {
2384
+ key: 'shortcuts-overlay',
2385
+ title: 'Keyboard Shortcuts',
2386
+ lines: shortcutsOverlayLines,
2387
+ borderColor: 'cyan'
2388
+ }
2389
+ ];
2390
+ } else if (ui.view === 'runs' && previewOpen && overlayPreviewOpen) {
2220
2391
  panelCandidates = [
2221
2392
  {
2222
2393
  key: 'preview-overlay',
@@ -2227,23 +2398,29 @@ function createDashboardApp(deps) {
2227
2398
  ];
2228
2399
  } else if (ui.view === 'overview') {
2229
2400
  panelCandidates = [
2230
- {
2231
- key: 'project',
2232
- title: 'Project + Workspace',
2233
- lines: buildOverviewProjectLines(snapshot, mainCompactWidth, activeFlow),
2234
- borderColor: 'green'
2235
- },
2236
2401
  {
2237
2402
  key: 'intent-status',
2238
- title: 'Intent Status',
2239
- lines: buildOverviewIntentLines(snapshot, mainCompactWidth, activeFlow),
2403
+ title: 'Intents',
2404
+ lines: buildOverviewIntentLines(snapshot, mainCompactWidth, activeFlow, overviewIntentFilter),
2240
2405
  borderColor: 'yellow'
2241
2406
  },
2407
+ {
2408
+ key: 'completed-runs',
2409
+ title: panelTitles.completed,
2410
+ lines: buildCompletedLines(snapshot, mainCompactWidth, activeFlow),
2411
+ borderColor: 'blue'
2412
+ },
2242
2413
  {
2243
2414
  key: 'standards',
2244
2415
  title: 'Standards',
2245
2416
  lines: buildOverviewStandardsLines(snapshot, mainCompactWidth, activeFlow),
2246
2417
  borderColor: 'blue'
2418
+ },
2419
+ {
2420
+ key: 'project',
2421
+ title: 'Project + Workspace',
2422
+ lines: buildOverviewProjectLines(snapshot, mainCompactWidth, activeFlow),
2423
+ borderColor: 'green'
2247
2424
  }
2248
2425
  ];
2249
2426
  } else if (ui.view === 'health') {
@@ -2298,12 +2475,6 @@ function createDashboardApp(deps) {
2298
2475
  title: panelTitles.pending,
2299
2476
  lines: pendingLines,
2300
2477
  borderColor: 'yellow'
2301
- },
2302
- {
2303
- key: 'completed',
2304
- title: panelTitles.completed,
2305
- lines: completedLines,
2306
- borderColor: 'blue'
2307
2478
  }
2308
2479
  ];
2309
2480
  }
@@ -2317,16 +2488,6 @@ function createDashboardApp(deps) {
2317
2488
  }
2318
2489
 
2319
2490
  const panels = allocateSingleColumnPanels(panelCandidates, contentRowsBudget);
2320
- const flowSwitchHint = availableFlowIds.length > 1 ? ' | [ or ] switch flow' : '';
2321
- const sectionHint = ui.view === 'runs'
2322
- ? ' | a active | f files | p pending | c done'
2323
- : (ui.view === 'overview' ? ' | p project | i intents | s standards' : ' | t stats | w warnings | e errors');
2324
- const previewHint = ui.view === 'runs'
2325
- ? (previewOpen
2326
- ? ` | tab ${paneFocus === 'preview' ? 'main' : 'preview'} | ↑/↓ ${paneFocus === 'preview' ? 'scroll' : 'navigate'} | v close | vv fullscreen`
2327
- : ' | ↑/↓ navigate | enter expand | v preview | vv fullscreen | o open')
2328
- : '';
2329
- const helpText = `q quit | r refresh | h/? help | 1 runs | 2 overview | 3 health | g/G section${sectionHint}${previewHint}${flowSwitchHint}`;
2330
2491
 
2331
2492
  const renderPanel = (panel, index, width, isFocused) => React.createElement(SectionPanel, {
2332
2493
  key: panel.key,
@@ -2390,9 +2551,11 @@ function createDashboardApp(deps) {
2390
2551
  panel,
2391
2552
  index,
2392
2553
  fullWidth,
2393
- (panel.key === 'preview' || panel.key === 'preview-overlay')
2554
+ ui.showHelp
2555
+ ? true
2556
+ : ((panel.key === 'preview' || panel.key === 'preview-overlay')
2394
2557
  ? paneFocus === 'preview'
2395
- : (paneFocus === 'main' && panel.key === focusedSection)
2558
+ : (paneFocus === 'main' && panel.key === focusedSection))
2396
2559
  ));
2397
2560
  }
2398
2561
 
@@ -2421,8 +2584,8 @@ function createDashboardApp(deps) {
2421
2584
  statusLine !== ''
2422
2585
  ? React.createElement(Text, { color: 'yellow' }, truncate(statusLine, fullWidth))
2423
2586
  : null,
2424
- showHelpLine
2425
- ? React.createElement(Text, { color: 'gray' }, truncate(helpText, fullWidth))
2587
+ showFooterHelpLine
2588
+ ? React.createElement(Text, { color: 'gray' }, truncate(quickHelpText, fullWidth))
2426
2589
  : null
2427
2590
  );
2428
2591
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
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": {