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.
- package/lib/dashboard/tui/app.js +165 -46
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -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
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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:
|
|
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: `
|
|
2163
|
-
{ id: 'intents', label: `
|
|
2164
|
-
{ id: 'completed', label: `
|
|
2165
|
-
{ id: 'health', label: `
|
|
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
|
-
...
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
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
|
-
...
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
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
|
|
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.
|
|
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": {
|