specsmd 0.1.40 → 0.1.42

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.
@@ -881,6 +881,30 @@ function pushFileEntry(entries, seenPaths, candidate) {
881
881
  });
882
882
  }
883
883
 
884
+ function buildIntentScopedLabel(snapshot, intentId, filePath, fallbackName = 'file.md') {
885
+ const safeIntentId = typeof intentId === 'string' && intentId.trim() !== ''
886
+ ? intentId
887
+ : '';
888
+ const safeFallback = typeof fallbackName === 'string' && fallbackName.trim() !== ''
889
+ ? fallbackName
890
+ : 'file.md';
891
+
892
+ if (typeof filePath === 'string' && filePath.trim() !== '') {
893
+ if (safeIntentId && typeof snapshot?.rootPath === 'string' && snapshot.rootPath.trim() !== '') {
894
+ const intentPath = path.join(snapshot.rootPath, 'intents', safeIntentId);
895
+ const relativePath = path.relative(intentPath, filePath);
896
+ if (relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
897
+ return `${safeIntentId}/${relativePath.split(path.sep).join('/')}`;
898
+ }
899
+ }
900
+
901
+ const basename = path.basename(filePath);
902
+ return safeIntentId ? `${safeIntentId}/${basename}` : basename;
903
+ }
904
+
905
+ return safeIntentId ? `${safeIntentId}/${safeFallback}` : safeFallback;
906
+ }
907
+
884
908
  function collectFireRunFiles(run) {
885
909
  if (!run || typeof run.folderPath !== 'string') {
886
910
  return [];
@@ -1023,14 +1047,24 @@ function getRunFileEntries(snapshot, flow, options = {}) {
1023
1047
  const pendingItems = Array.isArray(snapshot?.pendingItems) ? snapshot.pendingItems : [];
1024
1048
  for (const pendingItem of pendingItems) {
1025
1049
  pushFileEntry(entries, seenPaths, {
1026
- label: `${pendingItem?.intentId || 'intent'}/${pendingItem?.id || 'work-item'}.md`,
1050
+ label: buildIntentScopedLabel(
1051
+ snapshot,
1052
+ pendingItem?.intentId,
1053
+ pendingItem?.filePath,
1054
+ `${pendingItem?.id || 'work-item'}.md`
1055
+ ),
1027
1056
  path: pendingItem?.filePath,
1028
1057
  scope: 'upcoming'
1029
1058
  });
1030
1059
 
1031
1060
  if (pendingItem?.intentId) {
1032
1061
  pushFileEntry(entries, seenPaths, {
1033
- label: `${pendingItem.intentId}/brief.md`,
1062
+ label: buildIntentScopedLabel(
1063
+ snapshot,
1064
+ pendingItem.intentId,
1065
+ path.join(snapshot?.rootPath || '', 'intents', pendingItem.intentId, 'brief.md'),
1066
+ 'brief.md'
1067
+ ),
1034
1068
  path: path.join(snapshot?.rootPath || '', 'intents', pendingItem.intentId, 'brief.md'),
1035
1069
  scope: 'intent'
1036
1070
  });
@@ -1049,7 +1083,12 @@ function getRunFileEntries(snapshot, flow, options = {}) {
1049
1083
  : [];
1050
1084
  for (const intent of completedIntents) {
1051
1085
  pushFileEntry(entries, seenPaths, {
1052
- label: `${intent.id}/brief.md`,
1086
+ label: buildIntentScopedLabel(
1087
+ snapshot,
1088
+ intent?.id,
1089
+ path.join(snapshot?.rootPath || '', 'intents', intent?.id || '', 'brief.md'),
1090
+ 'brief.md'
1091
+ ),
1053
1092
  path: path.join(snapshot?.rootPath || '', 'intents', intent.id, 'brief.md'),
1054
1093
  scope: 'intent'
1055
1094
  });
@@ -1098,6 +1137,58 @@ function getNoCurrentMessage(flow) {
1098
1137
  return 'No active run';
1099
1138
  }
1100
1139
 
1140
+ function buildFireCurrentRunGroups(snapshot) {
1141
+ const run = getCurrentRun(snapshot);
1142
+ if (!run) {
1143
+ return [];
1144
+ }
1145
+
1146
+ const workItems = Array.isArray(run.workItems) ? run.workItems : [];
1147
+ const completed = workItems.filter((item) => item.status === 'completed').length;
1148
+ const currentWorkItem = workItems.find((item) => item.id === run.currentItem)
1149
+ || workItems.find((item) => item.status === 'in_progress')
1150
+ || workItems[0]
1151
+ || null;
1152
+
1153
+ const currentPhase = getCurrentPhaseLabel(run, currentWorkItem);
1154
+ const phaseTrack = buildPhaseTrack(currentPhase);
1155
+ const mode = String(currentWorkItem?.mode || 'confirm').toUpperCase();
1156
+ const status = currentWorkItem?.status || 'pending';
1157
+ const statusTag = status === 'in_progress' ? 'current' : status;
1158
+
1159
+ const currentWorkItemFiles = currentWorkItem?.filePath
1160
+ ? [{
1161
+ label: `${currentWorkItem.id || 'work-item'} [${mode}] [${statusTag}] ${phaseTrack}`,
1162
+ path: currentWorkItem.filePath,
1163
+ scope: 'active'
1164
+ }]
1165
+ : [];
1166
+
1167
+ const currentRunFiles = collectFireRunFiles(run).map((fileEntry) => ({
1168
+ ...fileEntry,
1169
+ label: path.basename(fileEntry.path || fileEntry.label || ''),
1170
+ scope: 'active'
1171
+ }));
1172
+
1173
+ return [
1174
+ {
1175
+ key: `current:run:${run.id}:summary`,
1176
+ label: `${run.id} [${run.scope}] ${completed}/${workItems.length} items`,
1177
+ files: []
1178
+ },
1179
+ {
1180
+ key: `current:run:${run.id}:work-items`,
1181
+ label: 'WORK ITEMS',
1182
+ files: filterExistingFiles(currentWorkItemFiles)
1183
+ },
1184
+ {
1185
+ key: `current:run:${run.id}:run-files`,
1186
+ label: 'RUN FILES',
1187
+ files: filterExistingFiles(currentRunFiles)
1188
+ }
1189
+ ];
1190
+ }
1191
+
1101
1192
  function buildCurrentGroups(snapshot, flow) {
1102
1193
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
1103
1194
 
@@ -1130,17 +1221,7 @@ function buildCurrentGroups(snapshot, flow) {
1130
1221
  }];
1131
1222
  }
1132
1223
 
1133
- const run = getCurrentRun(snapshot);
1134
- if (!run) {
1135
- return [];
1136
- }
1137
- const workItems = Array.isArray(run.workItems) ? run.workItems : [];
1138
- const completed = workItems.filter((item) => item.status === 'completed').length;
1139
- return [{
1140
- key: `current:run:${run.id}`,
1141
- label: `${run.id} [${run.scope}] ${completed}/${workItems.length} items`,
1142
- files: filterExistingFiles(collectFireRunFiles(run).map((file) => ({ ...file, scope: 'active' })))
1143
- }];
1224
+ return buildFireCurrentRunGroups(snapshot);
1144
1225
  }
1145
1226
 
1146
1227
  function buildRunFileGroups(fileEntries) {
@@ -1315,11 +1396,16 @@ function buildOverviewIntentGroups(snapshot, flow, filter = 'next') {
1315
1396
  const workItems = Array.isArray(intent?.workItems) ? intent.workItems : [];
1316
1397
  const done = workItems.filter((item) => item.status === 'completed').length;
1317
1398
  const files = [{
1318
- label: `${intent?.id || 'intent'}/brief.md`,
1399
+ label: buildIntentScopedLabel(snapshot, intent?.id, intent?.filePath, 'brief.md'),
1319
1400
  path: intent?.filePath,
1320
1401
  scope: 'intent'
1321
1402
  }, ...workItems.map((item) => ({
1322
- label: `${intent?.id || 'intent'}/${item?.id || 'work-item'}.md`,
1403
+ label: buildIntentScopedLabel(
1404
+ snapshot,
1405
+ intent?.id,
1406
+ item?.filePath,
1407
+ `${item?.id || 'work-item'}.md`
1408
+ ),
1323
1409
  path: item?.filePath,
1324
1410
  scope: item?.status === 'completed' ? 'completed' : 'upcoming'
1325
1411
  }))];
@@ -1331,26 +1417,41 @@ function buildOverviewIntentGroups(snapshot, flow, filter = 'next') {
1331
1417
  });
1332
1418
  }
1333
1419
 
1334
- function buildStandardsGroups(snapshot, flow) {
1420
+ function buildStandardsRows(snapshot, flow) {
1335
1421
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
1336
1422
  if (effectiveFlow === 'simple') {
1337
- return [];
1423
+ return [{
1424
+ kind: 'info',
1425
+ key: 'standards:empty:simple',
1426
+ label: 'No standards for SIMPLE flow',
1427
+ selectable: false
1428
+ }];
1338
1429
  }
1339
1430
 
1340
1431
  const standards = Array.isArray(snapshot?.standards) ? snapshot.standards : [];
1341
- return standards.map((standard, index) => {
1342
- const id = standard?.type || standard?.name || String(index);
1343
- const name = `${standard?.name || standard?.type || 'standard'}.md`;
1344
- return {
1345
- key: `standards:${id}`,
1346
- label: name,
1347
- files: filterExistingFiles([{
1348
- label: name,
1349
- path: standard?.filePath,
1350
- scope: 'file'
1351
- }])
1352
- };
1353
- });
1432
+ const files = filterExistingFiles(standards.map((standard, index) => ({
1433
+ label: `${standard?.name || standard?.type || `standard-${index}`}.md`,
1434
+ path: standard?.filePath,
1435
+ scope: 'file'
1436
+ })));
1437
+
1438
+ if (files.length === 0) {
1439
+ return [{
1440
+ kind: 'info',
1441
+ key: 'standards:empty',
1442
+ label: 'No standards found',
1443
+ selectable: false
1444
+ }];
1445
+ }
1446
+
1447
+ return files.map((file, index) => ({
1448
+ kind: 'file',
1449
+ key: `standards:file:${file.path}:${index}`,
1450
+ label: file.label,
1451
+ path: file.path,
1452
+ scope: 'file',
1453
+ selectable: true
1454
+ }));
1354
1455
  }
1355
1456
 
1356
1457
  function buildProjectGroups(snapshot, flow) {
@@ -1455,14 +1556,24 @@ function buildPendingGroups(snapshot, flow) {
1455
1556
 
1456
1557
  if (item?.filePath) {
1457
1558
  files.push({
1458
- label: `${item?.intentId || 'intent'}/${item?.id || 'work-item'}.md`,
1559
+ label: buildIntentScopedLabel(
1560
+ snapshot,
1561
+ item?.intentId,
1562
+ item?.filePath,
1563
+ `${item?.id || 'work-item'}.md`
1564
+ ),
1459
1565
  path: item.filePath,
1460
1566
  scope: 'upcoming'
1461
1567
  });
1462
1568
  }
1463
1569
  if (item?.intentId) {
1464
1570
  files.push({
1465
- label: `${item.intentId}/brief.md`,
1571
+ label: buildIntentScopedLabel(
1572
+ snapshot,
1573
+ item.intentId,
1574
+ path.join(snapshot?.rootPath || '', 'intents', item.intentId, 'brief.md'),
1575
+ 'brief.md'
1576
+ ),
1466
1577
  path: path.join(snapshot?.rootPath || '', 'intents', item.intentId, 'brief.md'),
1467
1578
  scope: 'intent'
1468
1579
  });
@@ -1523,7 +1634,12 @@ function buildCompletedGroups(snapshot, flow) {
1523
1634
  key: `completed:intent:${intent?.id || index}`,
1524
1635
  label: `intent ${intent?.id || 'unknown'} [completed]`,
1525
1636
  files: filterExistingFiles([{
1526
- label: `${intent?.id || 'intent'}/brief.md`,
1637
+ label: buildIntentScopedLabel(
1638
+ snapshot,
1639
+ intent?.id,
1640
+ path.join(snapshot?.rootPath || '', 'intents', intent?.id || '', 'brief.md'),
1641
+ 'brief.md'
1642
+ ),
1527
1643
  path: path.join(snapshot?.rootPath || '', 'intents', intent?.id || '', 'brief.md'),
1528
1644
  scope: 'intent'
1529
1645
  }])
@@ -2222,11 +2338,7 @@ function createDashboardApp(deps) {
2222
2338
  )
2223
2339
  : toLoadingRows('Loading completed items...', 'completed-loading');
2224
2340
  const standardsRows = shouldHydrateSecondaryTabs
2225
- ? toExpandableRows(
2226
- buildStandardsGroups(snapshot, activeFlow),
2227
- effectiveFlow === 'simple' ? 'No standards for SIMPLE flow' : 'No standards found',
2228
- expandedGroups
2229
- )
2341
+ ? buildStandardsRows(snapshot, activeFlow)
2230
2342
  : toLoadingRows('Loading standards...', 'standards-loading');
2231
2343
  const statsRows = shouldHydrateSecondaryTabs
2232
2344
  ? toInfoRows(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
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": {