specsmd 0.1.42 → 0.1.44

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.
@@ -905,6 +905,46 @@ function buildIntentScopedLabel(snapshot, intentId, filePath, fallbackName = 'fi
905
905
  return safeIntentId ? `${safeIntentId}/${safeFallback}` : safeFallback;
906
906
  }
907
907
 
908
+ function findIntentIdForWorkItem(snapshot, workItemId) {
909
+ if (typeof workItemId !== 'string' || workItemId.trim() === '') {
910
+ return '';
911
+ }
912
+
913
+ const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
914
+ for (const intent of intents) {
915
+ const items = Array.isArray(intent?.workItems) ? intent.workItems : [];
916
+ if (items.some((item) => item?.id === workItemId)) {
917
+ return intent?.id || '';
918
+ }
919
+ }
920
+
921
+ return '';
922
+ }
923
+
924
+ function resolveFireWorkItemPath(snapshot, intentId, workItemId, explicitPath) {
925
+ if (typeof explicitPath === 'string' && explicitPath.trim() !== '') {
926
+ return explicitPath;
927
+ }
928
+
929
+ if (typeof snapshot?.rootPath !== 'string' || snapshot.rootPath.trim() === '') {
930
+ return null;
931
+ }
932
+
933
+ if (typeof workItemId !== 'string' || workItemId.trim() === '') {
934
+ return null;
935
+ }
936
+
937
+ const safeIntentId = typeof intentId === 'string' && intentId.trim() !== ''
938
+ ? intentId
939
+ : findIntentIdForWorkItem(snapshot, workItemId);
940
+
941
+ if (!safeIntentId) {
942
+ return null;
943
+ }
944
+
945
+ return path.join(snapshot.rootPath, 'intents', safeIntentId, 'work-items', `${workItemId}.md`);
946
+ }
947
+
908
948
  function collectFireRunFiles(run) {
909
949
  if (!run || typeof run.folderPath !== 'string') {
910
950
  return [];
@@ -1156,13 +1196,32 @@ function buildFireCurrentRunGroups(snapshot) {
1156
1196
  const status = currentWorkItem?.status || 'pending';
1157
1197
  const statusTag = status === 'in_progress' ? 'current' : status;
1158
1198
 
1159
- const currentWorkItemFiles = currentWorkItem?.filePath
1160
- ? [{
1161
- label: `${currentWorkItem.id || 'work-item'} [${mode}] [${statusTag}] ${phaseTrack}`,
1162
- path: currentWorkItem.filePath,
1163
- scope: 'active'
1164
- }]
1165
- : [];
1199
+ const runIntentId = typeof run?.intent === 'string' ? run.intent : '';
1200
+ const currentWorkItemFiles = workItems.map((item, index) => {
1201
+ const itemId = typeof item?.id === 'string' && item.id !== '' ? item.id : `work-item-${index + 1}`;
1202
+ const intentId = typeof item?.intent === 'string' && item.intent !== ''
1203
+ ? item.intent
1204
+ : (runIntentId || findIntentIdForWorkItem(snapshot, itemId));
1205
+ const filePath = resolveFireWorkItemPath(snapshot, intentId, itemId, item?.filePath);
1206
+ if (!filePath) {
1207
+ return null;
1208
+ }
1209
+
1210
+ const itemMode = String(item?.mode || 'confirm').toUpperCase();
1211
+ const itemStatus = item?.status || 'pending';
1212
+ const isCurrent = Boolean(currentWorkItem?.id) && itemId === currentWorkItem.id;
1213
+ const itemScope = isCurrent
1214
+ ? 'active'
1215
+ : (itemStatus === 'completed' ? 'completed' : 'upcoming');
1216
+ const itemStatusTag = isCurrent ? 'current' : itemStatus;
1217
+ const labelPath = buildIntentScopedLabel(snapshot, intentId, filePath, `${itemId}.md`);
1218
+
1219
+ return {
1220
+ label: `${labelPath} [${itemMode}] [${itemStatusTag}]`,
1221
+ path: filePath,
1222
+ scope: itemScope
1223
+ };
1224
+ }).filter(Boolean);
1166
1225
 
1167
1226
  const currentRunFiles = collectFireRunFiles(run).map((fileEntry) => ({
1168
1227
  ...fileEntry,
@@ -1178,7 +1237,7 @@ function buildFireCurrentRunGroups(snapshot) {
1178
1237
  },
1179
1238
  {
1180
1239
  key: `current:run:${run.id}:work-items`,
1181
- label: 'WORK ITEMS',
1240
+ label: `WORK ITEMS (${currentWorkItemFiles.length})`,
1182
1241
  files: filterExistingFiles(currentWorkItemFiles)
1183
1242
  },
1184
1243
  {
@@ -2159,28 +2218,58 @@ function createDashboardApp(deps) {
2159
2218
  const primaryLabel = effectiveFlow === 'aidlc' ? 'BOLTS' : (effectiveFlow === 'simple' ? 'SPECS' : 'RUNS');
2160
2219
  const completedLabel = effectiveFlow === 'aidlc' ? 'COMPLETED BOLTS' : (effectiveFlow === 'simple' ? 'COMPLETED SPECS' : 'COMPLETED RUNS');
2161
2220
  const tabs = [
2162
- { id: 'runs', label: ` 1 ${icons.runs} ${primaryLabel} ` },
2163
- { id: 'intents', label: ` 2 ${icons.overview} INTENTS ` },
2164
- { id: 'completed', label: ` 3 ${icons.runs} ${completedLabel} ` },
2165
- { id: 'health', label: ` 4 ${icons.health} STANDARDS/HEALTH ` }
2221
+ { id: 'runs', label: `1 ${icons.runs} ${primaryLabel}` },
2222
+ { id: 'intents', label: `2 ${icons.overview} INTENTS` },
2223
+ { id: 'completed', label: `3 ${icons.runs} ${completedLabel}` },
2224
+ { id: 'health', label: `4 ${icons.health} STANDARDS/HEALTH` }
2166
2225
  ];
2226
+ const maxWidth = Math.max(8, Math.floor(width));
2227
+ const segments = [];
2228
+ let consumed = 0;
2229
+
2230
+ for (const tab of tabs) {
2231
+ const isActive = tab.id === view;
2232
+ const segmentText = isActive ? `[${tab.label}]` : tab.label;
2233
+ const separator = segments.length > 0 ? ' ' : '';
2234
+ const segmentWidth = stringWidth(separator) + stringWidth(segmentText);
2235
+ if (consumed + segmentWidth > maxWidth) {
2236
+ break;
2237
+ }
2238
+
2239
+ if (separator !== '') {
2240
+ segments.push({
2241
+ key: `${tab.id}:sep`,
2242
+ text: separator,
2243
+ active: false
2244
+ });
2245
+ }
2246
+ segments.push({
2247
+ key: tab.id,
2248
+ text: segmentText,
2249
+ active: isActive
2250
+ });
2251
+ consumed += segmentWidth;
2252
+ }
2253
+
2254
+ if (segments.length === 0) {
2255
+ const fallback = tabs.find((tab) => tab.id === view) || tabs[0];
2256
+ const fallbackText = truncate(`[${fallback.label}]`, maxWidth);
2257
+ return React.createElement(Text, { color: 'white', bold: true }, fallbackText);
2258
+ }
2167
2259
 
2168
2260
  return React.createElement(
2169
2261
  Box,
2170
- { width, flexWrap: 'nowrap' },
2171
- ...tabs.map((tab) => {
2172
- const isActive = tab.id === view;
2173
- return React.createElement(
2174
- Text,
2175
- {
2176
- key: tab.id,
2177
- bold: isActive,
2178
- color: isActive ? 'white' : 'gray',
2179
- backgroundColor: isActive ? 'blue' : undefined
2180
- },
2181
- tab.label
2182
- );
2183
- })
2262
+ { width: maxWidth, flexWrap: 'nowrap' },
2263
+ ...segments.map((segment) => React.createElement(
2264
+ Text,
2265
+ {
2266
+ key: segment.key,
2267
+ bold: segment.active,
2268
+ color: segment.active ? 'white' : 'gray',
2269
+ backgroundColor: segment.active ? 'blue' : undefined
2270
+ },
2271
+ segment.text
2272
+ ))
2184
2273
  );
2185
2274
  }
2186
2275
 
@@ -2189,23 +2278,52 @@ function createDashboardApp(deps) {
2189
2278
  if (!Array.isArray(flowIds) || flowIds.length <= 1) {
2190
2279
  return null;
2191
2280
  }
2281
+ const maxWidth = Math.max(8, Math.floor(width));
2282
+ const segments = [];
2283
+ let consumed = 0;
2284
+
2285
+ for (const flowId of flowIds) {
2286
+ const isActive = flowId === activeFlow;
2287
+ const segmentText = isActive ? `[${flowId.toUpperCase()}]` : flowId.toUpperCase();
2288
+ const separator = segments.length > 0 ? ' ' : '';
2289
+ const segmentWidth = stringWidth(separator) + stringWidth(segmentText);
2290
+ if (consumed + segmentWidth > maxWidth) {
2291
+ break;
2292
+ }
2293
+
2294
+ if (separator !== '') {
2295
+ segments.push({
2296
+ key: `${flowId}:sep`,
2297
+ text: separator,
2298
+ active: false
2299
+ });
2300
+ }
2301
+ segments.push({
2302
+ key: flowId,
2303
+ text: segmentText,
2304
+ active: isActive
2305
+ });
2306
+ consumed += segmentWidth;
2307
+ }
2308
+
2309
+ if (segments.length === 0) {
2310
+ const fallback = (activeFlow || flowIds[0] || 'flow').toUpperCase();
2311
+ return React.createElement(Text, { color: 'black', backgroundColor: 'green', bold: true }, truncate(`[${fallback}]`, maxWidth));
2312
+ }
2192
2313
 
2193
2314
  return React.createElement(
2194
2315
  Box,
2195
- { width, flexWrap: 'nowrap' },
2196
- ...flowIds.map((flowId) => {
2197
- const isActive = flowId === activeFlow;
2198
- return React.createElement(
2199
- Text,
2200
- {
2201
- key: flowId,
2202
- bold: isActive,
2203
- color: isActive ? 'black' : 'gray',
2204
- backgroundColor: isActive ? 'green' : undefined
2205
- },
2206
- ` ${flowId.toUpperCase()} `
2207
- );
2208
- })
2316
+ { width: maxWidth, flexWrap: 'nowrap' },
2317
+ ...segments.map((segment) => React.createElement(
2318
+ Text,
2319
+ {
2320
+ key: segment.key,
2321
+ bold: segment.active,
2322
+ color: segment.active ? 'black' : 'gray',
2323
+ backgroundColor: segment.active ? 'green' : undefined
2324
+ },
2325
+ segment.text
2326
+ ))
2209
2327
  );
2210
2328
  }
2211
2329
 
@@ -2774,11 +2892,6 @@ function createDashboardApp(deps) {
2774
2892
  }, [activeFlow, rowLengthSignature, snapshot?.generatedAt]);
2775
2893
 
2776
2894
  useEffect(() => {
2777
- if (ui.view !== 'runs') {
2778
- setDeferredTabsReady(true);
2779
- return undefined;
2780
- }
2781
-
2782
2895
  setDeferredTabsReady(false);
2783
2896
  const timer = setTimeout(() => {
2784
2897
  setDeferredTabsReady(true);
@@ -2786,7 +2899,7 @@ function createDashboardApp(deps) {
2786
2899
  return () => {
2787
2900
  clearTimeout(timer);
2788
2901
  };
2789
- }, [activeFlow, snapshot?.generatedAt, ui.view]);
2902
+ }, [activeFlow]);
2790
2903
 
2791
2904
  useEffect(() => {
2792
2905
  setPaneFocus('main');
@@ -2834,6 +2947,12 @@ function createDashboardApp(deps) {
2834
2947
  columns: stdout.columns || process.stdout.columns || 120,
2835
2948
  rows: stdout.rows || process.stdout.rows || 40
2836
2949
  });
2950
+
2951
+ // Resize in some terminals can leave stale frame rows behind.
2952
+ // Force clear so next render paints from a clean origin.
2953
+ if (typeof stdout.write === 'function' && stdout.isTTY !== false) {
2954
+ stdout.write('\u001B[2J\u001B[3J\u001B[H');
2955
+ }
2837
2956
  };
2838
2957
 
2839
2958
  updateSize();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
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": {