specsmd 0.1.41 → 0.1.43

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 [];
@@ -1137,6 +1177,77 @@ function getNoCurrentMessage(flow) {
1137
1177
  return 'No active run';
1138
1178
  }
1139
1179
 
1180
+ function buildFireCurrentRunGroups(snapshot) {
1181
+ const run = getCurrentRun(snapshot);
1182
+ if (!run) {
1183
+ return [];
1184
+ }
1185
+
1186
+ const workItems = Array.isArray(run.workItems) ? run.workItems : [];
1187
+ const completed = workItems.filter((item) => item.status === 'completed').length;
1188
+ const currentWorkItem = workItems.find((item) => item.id === run.currentItem)
1189
+ || workItems.find((item) => item.status === 'in_progress')
1190
+ || workItems[0]
1191
+ || null;
1192
+
1193
+ const currentPhase = getCurrentPhaseLabel(run, currentWorkItem);
1194
+ const phaseTrack = buildPhaseTrack(currentPhase);
1195
+ const mode = String(currentWorkItem?.mode || 'confirm').toUpperCase();
1196
+ const status = currentWorkItem?.status || 'pending';
1197
+ const statusTag = status === 'in_progress' ? 'current' : status;
1198
+
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);
1225
+
1226
+ const currentRunFiles = collectFireRunFiles(run).map((fileEntry) => ({
1227
+ ...fileEntry,
1228
+ label: path.basename(fileEntry.path || fileEntry.label || ''),
1229
+ scope: 'active'
1230
+ }));
1231
+
1232
+ return [
1233
+ {
1234
+ key: `current:run:${run.id}:summary`,
1235
+ label: `${run.id} [${run.scope}] ${completed}/${workItems.length} items`,
1236
+ files: []
1237
+ },
1238
+ {
1239
+ key: `current:run:${run.id}:work-items`,
1240
+ label: `WORK ITEMS (${currentWorkItemFiles.length})`,
1241
+ files: filterExistingFiles(currentWorkItemFiles)
1242
+ },
1243
+ {
1244
+ key: `current:run:${run.id}:run-files`,
1245
+ label: 'RUN FILES',
1246
+ files: filterExistingFiles(currentRunFiles)
1247
+ }
1248
+ ];
1249
+ }
1250
+
1140
1251
  function buildCurrentGroups(snapshot, flow) {
1141
1252
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
1142
1253
 
@@ -1169,17 +1280,7 @@ function buildCurrentGroups(snapshot, flow) {
1169
1280
  }];
1170
1281
  }
1171
1282
 
1172
- const run = getCurrentRun(snapshot);
1173
- if (!run) {
1174
- return [];
1175
- }
1176
- const workItems = Array.isArray(run.workItems) ? run.workItems : [];
1177
- const completed = workItems.filter((item) => item.status === 'completed').length;
1178
- return [{
1179
- key: `current:run:${run.id}`,
1180
- label: `${run.id} [${run.scope}] ${completed}/${workItems.length} items`,
1181
- files: filterExistingFiles(collectFireRunFiles(run).map((file) => ({ ...file, scope: 'active' })))
1182
- }];
1283
+ return buildFireCurrentRunGroups(snapshot);
1183
1284
  }
1184
1285
 
1185
1286
  function buildRunFileGroups(fileEntries) {
@@ -1375,26 +1476,41 @@ function buildOverviewIntentGroups(snapshot, flow, filter = 'next') {
1375
1476
  });
1376
1477
  }
1377
1478
 
1378
- function buildStandardsGroups(snapshot, flow) {
1479
+ function buildStandardsRows(snapshot, flow) {
1379
1480
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
1380
1481
  if (effectiveFlow === 'simple') {
1381
- return [];
1482
+ return [{
1483
+ kind: 'info',
1484
+ key: 'standards:empty:simple',
1485
+ label: 'No standards for SIMPLE flow',
1486
+ selectable: false
1487
+ }];
1382
1488
  }
1383
1489
 
1384
1490
  const standards = Array.isArray(snapshot?.standards) ? snapshot.standards : [];
1385
- return standards.map((standard, index) => {
1386
- const id = standard?.type || standard?.name || String(index);
1387
- const name = `${standard?.name || standard?.type || 'standard'}.md`;
1388
- return {
1389
- key: `standards:${id}`,
1390
- label: name,
1391
- files: filterExistingFiles([{
1392
- label: name,
1393
- path: standard?.filePath,
1394
- scope: 'file'
1395
- }])
1396
- };
1397
- });
1491
+ const files = filterExistingFiles(standards.map((standard, index) => ({
1492
+ label: `${standard?.name || standard?.type || `standard-${index}`}.md`,
1493
+ path: standard?.filePath,
1494
+ scope: 'file'
1495
+ })));
1496
+
1497
+ if (files.length === 0) {
1498
+ return [{
1499
+ kind: 'info',
1500
+ key: 'standards:empty',
1501
+ label: 'No standards found',
1502
+ selectable: false
1503
+ }];
1504
+ }
1505
+
1506
+ return files.map((file, index) => ({
1507
+ kind: 'file',
1508
+ key: `standards:file:${file.path}:${index}`,
1509
+ label: file.label,
1510
+ path: file.path,
1511
+ scope: 'file',
1512
+ selectable: true
1513
+ }));
1398
1514
  }
1399
1515
 
1400
1516
  function buildProjectGroups(snapshot, flow) {
@@ -2281,11 +2397,7 @@ function createDashboardApp(deps) {
2281
2397
  )
2282
2398
  : toLoadingRows('Loading completed items...', 'completed-loading');
2283
2399
  const standardsRows = shouldHydrateSecondaryTabs
2284
- ? toExpandableRows(
2285
- buildStandardsGroups(snapshot, activeFlow),
2286
- effectiveFlow === 'simple' ? 'No standards for SIMPLE flow' : 'No standards found',
2287
- expandedGroups
2288
- )
2400
+ ? buildStandardsRows(snapshot, activeFlow)
2289
2401
  : toLoadingRows('Loading standards...', 'standards-loading');
2290
2402
  const statsRows = shouldHydrateSecondaryTabs
2291
2403
  ? toInfoRows(
@@ -2721,11 +2833,6 @@ function createDashboardApp(deps) {
2721
2833
  }, [activeFlow, rowLengthSignature, snapshot?.generatedAt]);
2722
2834
 
2723
2835
  useEffect(() => {
2724
- if (ui.view !== 'runs') {
2725
- setDeferredTabsReady(true);
2726
- return undefined;
2727
- }
2728
-
2729
2836
  setDeferredTabsReady(false);
2730
2837
  const timer = setTimeout(() => {
2731
2838
  setDeferredTabsReady(true);
@@ -2733,7 +2840,7 @@ function createDashboardApp(deps) {
2733
2840
  return () => {
2734
2841
  clearTimeout(timer);
2735
2842
  };
2736
- }, [activeFlow, snapshot?.generatedAt, ui.view]);
2843
+ }, [activeFlow]);
2737
2844
 
2738
2845
  useEffect(() => {
2739
2846
  setPaneFocus('main');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
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": {