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.
- package/lib/dashboard/tui/app.js +151 -39
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
?
|
|
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.
|
|
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": {
|