specsmd 0.1.70 → 0.1.72
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/aidlc/parser.js +22 -2
- package/lib/dashboard/fire/model.js +15 -1
- package/lib/dashboard/fire/parser.js +50 -8
- package/lib/dashboard/web/extension-adapter.js +19 -11
- package/lib/dashboard/web/public/app.js +111 -0
- package/lib/dashboard/web/public/styles.css +74 -0
- package/lib/dashboard/web/public/webview-bundle.js +148 -0
- package/lib/dashboard/web/server.js +22 -10
- package/package.json +1 -1
|
@@ -114,6 +114,25 @@ function normalizeTimestamp(value) {
|
|
|
114
114
|
return parsed.toISOString();
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function compareByCreatedAtThenId(a, b) {
|
|
118
|
+
const aTime = a?.createdAt ? Date.parse(a.createdAt) : NaN;
|
|
119
|
+
const bTime = b?.createdAt ? Date.parse(b.createdAt) : NaN;
|
|
120
|
+
const aHasTime = !Number.isNaN(aTime);
|
|
121
|
+
const bHasTime = !Number.isNaN(bTime);
|
|
122
|
+
|
|
123
|
+
if (aHasTime && bHasTime && aTime !== bTime) {
|
|
124
|
+
return aTime - bTime;
|
|
125
|
+
}
|
|
126
|
+
if (aHasTime && !bHasTime) {
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
if (!aHasTime && bHasTime) {
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return String(a?.id || '').localeCompare(String(b?.id || ''));
|
|
134
|
+
}
|
|
135
|
+
|
|
117
136
|
function parseIntentFolderName(folderName) {
|
|
118
137
|
const match = String(folderName).match(/^(\d{3})-(.+)$/);
|
|
119
138
|
if (!match) {
|
|
@@ -272,7 +291,8 @@ function parseIntent(intentPath, warnings) {
|
|
|
272
291
|
completedStories: storyStats.completed,
|
|
273
292
|
inProgressStories: storyStats.inProgress,
|
|
274
293
|
pendingStories: storyStats.pending,
|
|
275
|
-
blockedStories: storyStats.blocked
|
|
294
|
+
blockedStories: storyStats.blocked,
|
|
295
|
+
createdAt: normalizeTimestamp(requirementsFrontmatter.created)
|
|
276
296
|
};
|
|
277
297
|
}
|
|
278
298
|
|
|
@@ -486,7 +506,7 @@ function parseAidlcDashboard(workspacePath) {
|
|
|
486
506
|
const intents = intentFolders
|
|
487
507
|
.map((intentFolder) => parseIntent(path.join(intentsPath, intentFolder), warnings))
|
|
488
508
|
.filter(Boolean)
|
|
489
|
-
.sort(
|
|
509
|
+
.sort(compareByCreatedAtThenId);
|
|
490
510
|
|
|
491
511
|
if (intentFolders.length === 0) {
|
|
492
512
|
warnings.push('No intents found under memory-bank/intents.');
|
|
@@ -350,7 +350,8 @@ function buildPendingItems(intents) {
|
|
|
350
350
|
mode: item.mode,
|
|
351
351
|
complexity: item.complexity,
|
|
352
352
|
dependencies: item.dependencies || [],
|
|
353
|
-
filePath: item.filePath
|
|
353
|
+
filePath: item.filePath,
|
|
354
|
+
createdAt: item.createdAt
|
|
354
355
|
});
|
|
355
356
|
}
|
|
356
357
|
}
|
|
@@ -360,6 +361,19 @@ function buildPendingItems(intents) {
|
|
|
360
361
|
if (depDiff !== 0) {
|
|
361
362
|
return depDiff;
|
|
362
363
|
}
|
|
364
|
+
const aTime = a.createdAt ? Date.parse(a.createdAt) : NaN;
|
|
365
|
+
const bTime = b.createdAt ? Date.parse(b.createdAt) : NaN;
|
|
366
|
+
const aHasTime = !Number.isNaN(aTime);
|
|
367
|
+
const bHasTime = !Number.isNaN(bTime);
|
|
368
|
+
if (aHasTime && bHasTime && aTime !== bTime) {
|
|
369
|
+
return aTime - bTime;
|
|
370
|
+
}
|
|
371
|
+
if (aHasTime && !bHasTime) {
|
|
372
|
+
return -1;
|
|
373
|
+
}
|
|
374
|
+
if (!aHasTime && bHasTime) {
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
363
377
|
return a.id.localeCompare(b.id);
|
|
364
378
|
});
|
|
365
379
|
|
|
@@ -12,7 +12,8 @@ const {
|
|
|
12
12
|
calculateStats,
|
|
13
13
|
parseDependencies,
|
|
14
14
|
buildPendingItems,
|
|
15
|
-
normalizeRunWorkItem
|
|
15
|
+
normalizeRunWorkItem,
|
|
16
|
+
normalizeTimestamp
|
|
16
17
|
} = require('./model');
|
|
17
18
|
|
|
18
19
|
const STANDARD_TYPES = [
|
|
@@ -80,6 +81,46 @@ function getFirstStringValue(record, keys) {
|
|
|
80
81
|
return undefined;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
function compareByCreatedAtThenId(a, b) {
|
|
85
|
+
const aTime = a?.createdAt ? Date.parse(a.createdAt) : NaN;
|
|
86
|
+
const bTime = b?.createdAt ? Date.parse(b.createdAt) : NaN;
|
|
87
|
+
const aHasTime = !Number.isNaN(aTime);
|
|
88
|
+
const bHasTime = !Number.isNaN(bTime);
|
|
89
|
+
|
|
90
|
+
if (aHasTime && bHasTime && aTime !== bTime) {
|
|
91
|
+
return aTime - bTime;
|
|
92
|
+
}
|
|
93
|
+
if (aHasTime && !bHasTime) {
|
|
94
|
+
return -1;
|
|
95
|
+
}
|
|
96
|
+
if (!aHasTime && bHasTime) {
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return String(a?.id || '').localeCompare(String(b?.id || ''));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function compareRunDatesDesc(a, b) {
|
|
104
|
+
const aDate = a?.completedAt || a?.startedAt;
|
|
105
|
+
const bDate = b?.completedAt || b?.startedAt;
|
|
106
|
+
const aTime = aDate ? Date.parse(aDate) : NaN;
|
|
107
|
+
const bTime = bDate ? Date.parse(bDate) : NaN;
|
|
108
|
+
const aHasTime = !Number.isNaN(aTime);
|
|
109
|
+
const bHasTime = !Number.isNaN(bTime);
|
|
110
|
+
|
|
111
|
+
if (aHasTime && bHasTime && aTime !== bTime) {
|
|
112
|
+
return bTime - aTime;
|
|
113
|
+
}
|
|
114
|
+
if (aHasTime && !bHasTime) {
|
|
115
|
+
return -1;
|
|
116
|
+
}
|
|
117
|
+
if (!aHasTime && bHasTime) {
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return String(b?.id || '').localeCompare(String(a?.id || ''));
|
|
122
|
+
}
|
|
123
|
+
|
|
83
124
|
function parseRunLog(runLogPath) {
|
|
84
125
|
const content = readFileSafe(runLogPath);
|
|
85
126
|
if (!content) {
|
|
@@ -210,8 +251,8 @@ function scanWorkItems(intentPath, intentId, stateWorkItems, warnings) {
|
|
|
210
251
|
filePath,
|
|
211
252
|
description: typeof frontmatter.description === 'string' ? frontmatter.description : undefined,
|
|
212
253
|
dependencies,
|
|
213
|
-
createdAt:
|
|
214
|
-
completedAt:
|
|
254
|
+
createdAt: normalizeTimestamp(frontmatter.created),
|
|
255
|
+
completedAt: normalizeTimestamp(frontmatter.completed_at)
|
|
215
256
|
};
|
|
216
257
|
});
|
|
217
258
|
}
|
|
@@ -252,8 +293,8 @@ function scanIntents(rootPath, normalizedState, warnings) {
|
|
|
252
293
|
filePath: briefPath,
|
|
253
294
|
description: typeof frontmatter.description === 'string' ? frontmatter.description : undefined,
|
|
254
295
|
workItems,
|
|
255
|
-
createdAt:
|
|
256
|
-
completedAt:
|
|
296
|
+
createdAt: normalizeTimestamp(frontmatter.created),
|
|
297
|
+
completedAt: normalizeTimestamp(frontmatter.completed_at)
|
|
257
298
|
};
|
|
258
299
|
});
|
|
259
300
|
}
|
|
@@ -325,7 +366,8 @@ function buildActiveRuns(runs, normalizedState) {
|
|
|
325
366
|
|
|
326
367
|
return (normalizedState.runs?.active || [])
|
|
327
368
|
.map((active) => byId.get(active.id) || null)
|
|
328
|
-
.filter(Boolean)
|
|
369
|
+
.filter(Boolean)
|
|
370
|
+
.sort(compareRunDatesDesc);
|
|
329
371
|
}
|
|
330
372
|
|
|
331
373
|
function buildCompletedRuns(runs) {
|
|
@@ -430,8 +472,8 @@ function parseFireDashboard(workspacePath) {
|
|
|
430
472
|
|
|
431
473
|
const warnings = [];
|
|
432
474
|
const normalizedState = normalizeState(rawState);
|
|
433
|
-
const intents = scanIntents(rootPath, normalizedState, warnings);
|
|
434
|
-
const runs = scanRuns(rootPath, normalizedState);
|
|
475
|
+
const intents = scanIntents(rootPath, normalizedState, warnings).sort(compareByCreatedAtThenId);
|
|
476
|
+
const runs = scanRuns(rootPath, normalizedState).sort(compareRunDatesDesc);
|
|
435
477
|
const activeRuns = buildActiveRuns(runs, normalizedState);
|
|
436
478
|
const completedRuns = buildCompletedRuns(runs);
|
|
437
479
|
const standards = scanStandards(rootPath);
|
|
@@ -248,7 +248,8 @@ function buildSpecsData(snapshot) {
|
|
|
248
248
|
path: intent.path,
|
|
249
249
|
storiesComplete: units.reduce((sum, unit) => sum + unit.storiesComplete, 0),
|
|
250
250
|
storiesTotal: units.reduce((sum, unit) => sum + unit.storiesTotal, 0),
|
|
251
|
-
units
|
|
251
|
+
units,
|
|
252
|
+
createdAt: intent.createdAt
|
|
252
253
|
};
|
|
253
254
|
});
|
|
254
255
|
|
|
@@ -400,7 +401,8 @@ function buildFireViewData(snapshot) {
|
|
|
400
401
|
mode: normalizeFireMode(item.mode),
|
|
401
402
|
complexity: normalizeFireComplexity(item.complexity),
|
|
402
403
|
filePath: item.filePath,
|
|
403
|
-
dependencies: item.dependencies || []
|
|
404
|
+
dependencies: item.dependencies || [],
|
|
405
|
+
createdAt: item.createdAt
|
|
404
406
|
}));
|
|
405
407
|
|
|
406
408
|
const completedRuns = (snapshot.completedRuns || []).map((run) => ({
|
|
@@ -418,13 +420,15 @@ function buildFireViewData(snapshot) {
|
|
|
418
420
|
status: normalizeFireStatus(intent.status),
|
|
419
421
|
filePath: intent.filePath,
|
|
420
422
|
description: intent.description,
|
|
423
|
+
createdAt: intent.createdAt,
|
|
421
424
|
workItems: (intent.workItems || []).map((item) => ({
|
|
422
425
|
id: item.id,
|
|
423
426
|
title: item.title || item.id,
|
|
424
427
|
status: normalizeFireStatus(item.status),
|
|
425
428
|
mode: normalizeFireMode(item.mode),
|
|
426
429
|
complexity: normalizeFireComplexity(item.complexity),
|
|
427
|
-
filePath: item.filePath
|
|
430
|
+
filePath: item.filePath,
|
|
431
|
+
createdAt: item.createdAt
|
|
428
432
|
}))
|
|
429
433
|
}));
|
|
430
434
|
|
|
@@ -706,12 +710,16 @@ function createSetDataMessage(data) {
|
|
|
706
710
|
};
|
|
707
711
|
}
|
|
708
712
|
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
713
|
+
const availableFlows = (data.availableFlows && data.availableFlows.length > 0
|
|
714
|
+
? data.availableFlows
|
|
715
|
+
: [data.flow])
|
|
716
|
+
.filter(Boolean)
|
|
717
|
+
.map((flow) => ({
|
|
718
|
+
id: flow,
|
|
719
|
+
displayName: flowDisplayName(flow),
|
|
720
|
+
icon: flowIcon(flow),
|
|
721
|
+
rootFolder: flowRootFolder(flow)
|
|
722
|
+
}));
|
|
715
723
|
|
|
716
724
|
if (data.flow === 'fire') {
|
|
717
725
|
return {
|
|
@@ -733,7 +741,7 @@ function createSetDataMessage(data) {
|
|
|
733
741
|
specsHtml: '',
|
|
734
742
|
overviewHtml: '',
|
|
735
743
|
fireData: buildFireViewData(data.snapshot),
|
|
736
|
-
availableFlows
|
|
744
|
+
availableFlows,
|
|
737
745
|
activeFlowId: data.flow
|
|
738
746
|
};
|
|
739
747
|
}
|
|
@@ -757,7 +765,7 @@ function createSetDataMessage(data) {
|
|
|
757
765
|
},
|
|
758
766
|
specsHtml: getSpecsViewHtml(webviewData),
|
|
759
767
|
overviewHtml: getOverviewViewHtml(webviewData),
|
|
760
|
-
availableFlows
|
|
768
|
+
availableFlows,
|
|
761
769
|
activeFlowId: data.flow
|
|
762
770
|
};
|
|
763
771
|
}
|
|
@@ -28,6 +28,103 @@
|
|
|
28
28
|
document.documentElement.style.colorScheme = theme;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function copyText(text) {
|
|
32
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
33
|
+
return navigator.clipboard.writeText(text);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var textarea = document.createElement('textarea');
|
|
37
|
+
textarea.value = text;
|
|
38
|
+
textarea.setAttribute('readonly', 'readonly');
|
|
39
|
+
textarea.style.position = 'fixed';
|
|
40
|
+
textarea.style.opacity = '0';
|
|
41
|
+
document.body.appendChild(textarea);
|
|
42
|
+
textarea.select();
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
document.execCommand('copy');
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return Promise.reject(error);
|
|
49
|
+
} finally {
|
|
50
|
+
textarea.remove();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function closeCommandDialog() {
|
|
55
|
+
var existing = document.querySelector('.specsmd-command-dialog');
|
|
56
|
+
if (existing) {
|
|
57
|
+
existing.remove();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function showCommandDialog(command) {
|
|
62
|
+
closeCommandDialog();
|
|
63
|
+
|
|
64
|
+
var overlay = document.createElement('div');
|
|
65
|
+
overlay.className = 'specsmd-command-dialog';
|
|
66
|
+
overlay.setAttribute('role', 'dialog');
|
|
67
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
68
|
+
overlay.setAttribute('aria-label', 'Start FIRE run command');
|
|
69
|
+
|
|
70
|
+
var panel = document.createElement('div');
|
|
71
|
+
panel.className = 'specsmd-command-dialog-panel';
|
|
72
|
+
|
|
73
|
+
var title = document.createElement('div');
|
|
74
|
+
title.className = 'specsmd-command-dialog-title';
|
|
75
|
+
title.textContent = 'Start FIRE run';
|
|
76
|
+
|
|
77
|
+
var description = document.createElement('div');
|
|
78
|
+
description.className = 'specsmd-command-dialog-description';
|
|
79
|
+
description.textContent = 'Run this command from your project folder.';
|
|
80
|
+
|
|
81
|
+
var commandBox = document.createElement('textarea');
|
|
82
|
+
commandBox.className = 'specsmd-command-dialog-command';
|
|
83
|
+
commandBox.value = command;
|
|
84
|
+
commandBox.readOnly = true;
|
|
85
|
+
commandBox.rows = 2;
|
|
86
|
+
|
|
87
|
+
var actions = document.createElement('div');
|
|
88
|
+
actions.className = 'specsmd-command-dialog-actions';
|
|
89
|
+
|
|
90
|
+
var copyButton = document.createElement('button');
|
|
91
|
+
copyButton.type = 'button';
|
|
92
|
+
copyButton.className = 'specsmd-command-dialog-copy';
|
|
93
|
+
copyButton.textContent = 'Copy Command';
|
|
94
|
+
|
|
95
|
+
var closeButton = document.createElement('button');
|
|
96
|
+
closeButton.type = 'button';
|
|
97
|
+
closeButton.className = 'specsmd-command-dialog-close';
|
|
98
|
+
closeButton.textContent = 'Close';
|
|
99
|
+
|
|
100
|
+
copyButton.addEventListener('click', function () {
|
|
101
|
+
copyText(command).then(function () {
|
|
102
|
+
copyButton.textContent = 'Copied';
|
|
103
|
+
}).catch(function () {
|
|
104
|
+
commandBox.focus();
|
|
105
|
+
commandBox.select();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
closeButton.addEventListener('click', closeCommandDialog);
|
|
110
|
+
overlay.addEventListener('click', function (event) {
|
|
111
|
+
if (event.target === overlay) {
|
|
112
|
+
closeCommandDialog();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
actions.appendChild(copyButton);
|
|
117
|
+
actions.appendChild(closeButton);
|
|
118
|
+
panel.appendChild(title);
|
|
119
|
+
panel.appendChild(description);
|
|
120
|
+
panel.appendChild(commandBox);
|
|
121
|
+
panel.appendChild(actions);
|
|
122
|
+
overlay.appendChild(panel);
|
|
123
|
+
document.body.appendChild(overlay);
|
|
124
|
+
commandBox.focus();
|
|
125
|
+
commandBox.select();
|
|
126
|
+
}
|
|
127
|
+
|
|
31
128
|
document.documentElement.dataset.host = 'dashboard-web';
|
|
32
129
|
applyTheme(readTheme());
|
|
33
130
|
|
|
@@ -44,4 +141,18 @@
|
|
|
44
141
|
document.documentElement.dataset.loaded = 'true';
|
|
45
142
|
}
|
|
46
143
|
});
|
|
144
|
+
|
|
145
|
+
window.addEventListener('specsmd-dashboard-command', function (event) {
|
|
146
|
+
if (!event.detail || !event.detail.command) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
showCommandDialog(String(event.detail.command));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
window.addEventListener('keydown', function (event) {
|
|
154
|
+
if (event.key === 'Escape') {
|
|
155
|
+
closeCommandDialog();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
47
158
|
}());
|
|
@@ -64,3 +64,77 @@ specsmd-app {
|
|
|
64
64
|
width: 100vw;
|
|
65
65
|
height: 100vh;
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
.specsmd-command-dialog {
|
|
69
|
+
position: fixed;
|
|
70
|
+
inset: 0;
|
|
71
|
+
z-index: 1000;
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
padding: 24px;
|
|
76
|
+
background: rgba(0, 0, 0, 0.45);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.specsmd-command-dialog-panel {
|
|
80
|
+
width: min(520px, 100%);
|
|
81
|
+
padding: 16px;
|
|
82
|
+
border: 1px solid var(--vscode-sideBarSectionHeader-border);
|
|
83
|
+
border-radius: 8px;
|
|
84
|
+
background: var(--vscode-sideBar-background);
|
|
85
|
+
color: var(--vscode-foreground);
|
|
86
|
+
box-shadow: 0 18px 56px rgba(0, 0, 0, 0.28);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.specsmd-command-dialog-title {
|
|
90
|
+
font-size: 15px;
|
|
91
|
+
font-weight: 700;
|
|
92
|
+
margin-bottom: 4px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.specsmd-command-dialog-description {
|
|
96
|
+
margin-bottom: 12px;
|
|
97
|
+
color: var(--vscode-descriptionForeground);
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.specsmd-command-dialog-command {
|
|
102
|
+
box-sizing: border-box;
|
|
103
|
+
width: 100%;
|
|
104
|
+
min-height: 58px;
|
|
105
|
+
resize: vertical;
|
|
106
|
+
padding: 10px;
|
|
107
|
+
border: 1px solid var(--vscode-input-border);
|
|
108
|
+
border-radius: 6px;
|
|
109
|
+
background: var(--vscode-input-background);
|
|
110
|
+
color: var(--vscode-foreground);
|
|
111
|
+
font: 12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.specsmd-command-dialog-actions {
|
|
115
|
+
display: flex;
|
|
116
|
+
justify-content: flex-end;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
margin-top: 12px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.specsmd-command-dialog-actions button {
|
|
122
|
+
min-width: 88px;
|
|
123
|
+
padding: 7px 10px;
|
|
124
|
+
border: 1px solid var(--vscode-input-border);
|
|
125
|
+
border-radius: 6px;
|
|
126
|
+
color: var(--vscode-foreground);
|
|
127
|
+
background: transparent;
|
|
128
|
+
font: inherit;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.specsmd-command-dialog-actions button:hover {
|
|
133
|
+
background: var(--vscode-list-hoverBackground);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.specsmd-command-dialog-copy {
|
|
137
|
+
border-color: var(--vscode-button-background) !important;
|
|
138
|
+
background: var(--vscode-button-background) !important;
|
|
139
|
+
color: var(--vscode-button-foreground) !important;
|
|
140
|
+
}
|
|
@@ -6167,6 +6167,31 @@
|
|
|
6167
6167
|
|
|
6168
6168
|
<!-- Resources Footer -->
|
|
6169
6169
|
<div class="resources-footer">
|
|
6170
|
+
<div class="fabriqa-card">
|
|
6171
|
+
<div class="fabriqa-brand">
|
|
6172
|
+
<div class="fabriqa-mark">FA</div>
|
|
6173
|
+
<div>
|
|
6174
|
+
<div class="fabriqa-title">specs.md by Fabriqa.AI</div>
|
|
6175
|
+
<div class="fabriqa-subtitle">Spec-native agentic development environment</div>
|
|
6176
|
+
</div>
|
|
6177
|
+
</div>
|
|
6178
|
+
<div class="fabriqa-copy">
|
|
6179
|
+
Use Fabriqa.AI with your existing AI subscription to design, run, and reuse agentic workflows around your specs. It is free to try.
|
|
6180
|
+
</div>
|
|
6181
|
+
<div class="fabriqa-actions">
|
|
6182
|
+
<div class="fabriqa-link" @click=${() => this._openExternal("https://fabriqa.ai")}>Explore Fabriqa.AI</div>
|
|
6183
|
+
<div class="fabriqa-link secondary" @click=${() => this._openExternal("https://specs.md")}>Open specs.md</div>
|
|
6184
|
+
</div>
|
|
6185
|
+
</div>
|
|
6186
|
+
<div class="dashboard-tip">
|
|
6187
|
+
<div class="dashboard-tip-title">Did you know?</div>
|
|
6188
|
+
<div class="dashboard-tip-copy">
|
|
6189
|
+
You can use the specs.md dashboard outside VS Code and VS Code variants. Run <code>npx specsmd@latest dashboard</code> from your project folder.
|
|
6190
|
+
</div>
|
|
6191
|
+
<div class="fabriqa-actions">
|
|
6192
|
+
<div class="fabriqa-link secondary" @click=${() => this._openExternal("https://specs.md/getting-started/cli-dashboard")}>Dashboard docs</div>
|
|
6193
|
+
</div>
|
|
6194
|
+
</div>
|
|
6170
6195
|
<div class="resources-title">Links</div>
|
|
6171
6196
|
<div class="resources-links">
|
|
6172
6197
|
<div class="resource-link" @click=${() => this._openExternal("https://specs.md")} title="Website">
|
|
@@ -6497,6 +6522,104 @@
|
|
|
6497
6522
|
.feedback-link:hover {
|
|
6498
6523
|
opacity: 0.8;
|
|
6499
6524
|
}
|
|
6525
|
+
|
|
6526
|
+
.fabriqa-card {
|
|
6527
|
+
padding: 12px;
|
|
6528
|
+
margin-bottom: 12px;
|
|
6529
|
+
border: 1px solid rgba(249, 115, 22, 0.35);
|
|
6530
|
+
border-radius: 6px;
|
|
6531
|
+
background: linear-gradient(135deg, rgba(249, 115, 22, 0.12), rgba(34, 197, 94, 0.08));
|
|
6532
|
+
}
|
|
6533
|
+
|
|
6534
|
+
.fabriqa-brand {
|
|
6535
|
+
display: flex;
|
|
6536
|
+
align-items: center;
|
|
6537
|
+
gap: 10px;
|
|
6538
|
+
margin-bottom: 8px;
|
|
6539
|
+
}
|
|
6540
|
+
|
|
6541
|
+
.fabriqa-mark {
|
|
6542
|
+
display: flex;
|
|
6543
|
+
align-items: center;
|
|
6544
|
+
justify-content: center;
|
|
6545
|
+
flex: 0 0 30px;
|
|
6546
|
+
height: 30px;
|
|
6547
|
+
border-radius: 6px;
|
|
6548
|
+
background: var(--status-active);
|
|
6549
|
+
color: #ffffff;
|
|
6550
|
+
font-size: 11px;
|
|
6551
|
+
font-weight: 700;
|
|
6552
|
+
}
|
|
6553
|
+
|
|
6554
|
+
.fabriqa-title {
|
|
6555
|
+
font-size: 13px;
|
|
6556
|
+
font-weight: 700;
|
|
6557
|
+
color: var(--foreground);
|
|
6558
|
+
}
|
|
6559
|
+
|
|
6560
|
+
.fabriqa-subtitle,
|
|
6561
|
+
.fabriqa-copy,
|
|
6562
|
+
.dashboard-tip-copy {
|
|
6563
|
+
font-size: 11px;
|
|
6564
|
+
line-height: 1.45;
|
|
6565
|
+
color: var(--description-foreground);
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
.fabriqa-copy {
|
|
6569
|
+
margin-bottom: 10px;
|
|
6570
|
+
}
|
|
6571
|
+
|
|
6572
|
+
.fabriqa-actions {
|
|
6573
|
+
display: flex;
|
|
6574
|
+
flex-wrap: wrap;
|
|
6575
|
+
gap: 8px;
|
|
6576
|
+
}
|
|
6577
|
+
|
|
6578
|
+
.fabriqa-link {
|
|
6579
|
+
display: inline-flex;
|
|
6580
|
+
align-items: center;
|
|
6581
|
+
justify-content: center;
|
|
6582
|
+
padding: 6px 9px;
|
|
6583
|
+
border-radius: 5px;
|
|
6584
|
+
background: var(--status-active);
|
|
6585
|
+
color: #ffffff;
|
|
6586
|
+
font-size: 11px;
|
|
6587
|
+
font-weight: 600;
|
|
6588
|
+
cursor: pointer;
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6591
|
+
.fabriqa-link.secondary {
|
|
6592
|
+
border: 1px solid var(--border-color);
|
|
6593
|
+
background: var(--editor-background);
|
|
6594
|
+
color: var(--foreground);
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6597
|
+
.fabriqa-link:hover {
|
|
6598
|
+
opacity: 0.86;
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
.dashboard-tip {
|
|
6602
|
+
padding: 10px;
|
|
6603
|
+
margin-bottom: 12px;
|
|
6604
|
+
border: 1px solid var(--border-color);
|
|
6605
|
+
border-radius: 6px;
|
|
6606
|
+
background: var(--editor-background);
|
|
6607
|
+
}
|
|
6608
|
+
|
|
6609
|
+
.dashboard-tip-title {
|
|
6610
|
+
margin-bottom: 4px;
|
|
6611
|
+
color: var(--foreground);
|
|
6612
|
+
font-size: 11px;
|
|
6613
|
+
font-weight: 700;
|
|
6614
|
+
}
|
|
6615
|
+
|
|
6616
|
+
.dashboard-tip code {
|
|
6617
|
+
padding: 1px 4px;
|
|
6618
|
+
border-radius: 4px;
|
|
6619
|
+
background: var(--background);
|
|
6620
|
+
color: var(--foreground);
|
|
6621
|
+
font-family: var(--font-family);
|
|
6622
|
+
}
|
|
6500
6623
|
`
|
|
6501
6624
|
];
|
|
6502
6625
|
__decorateClass([
|
|
@@ -6644,6 +6767,15 @@
|
|
|
6644
6767
|
connectEvents();
|
|
6645
6768
|
return {
|
|
6646
6769
|
postMessage(message) {
|
|
6770
|
+
if (isStandaloneStartRunMessage(message)) {
|
|
6771
|
+
window.dispatchEvent(new CustomEvent("specsmd-dashboard-command", {
|
|
6772
|
+
detail: {
|
|
6773
|
+
command: buildFireStartRunCommand(message.workItemIds),
|
|
6774
|
+
workItemIds: message.workItemIds
|
|
6775
|
+
}
|
|
6776
|
+
}));
|
|
6777
|
+
return;
|
|
6778
|
+
}
|
|
6647
6779
|
fetch("/api/message", {
|
|
6648
6780
|
method: "POST",
|
|
6649
6781
|
headers: { "content-type": "application/json" },
|
|
@@ -6665,6 +6797,13 @@
|
|
|
6665
6797
|
}
|
|
6666
6798
|
};
|
|
6667
6799
|
}
|
|
6800
|
+
function isStandaloneStartRunMessage(message) {
|
|
6801
|
+
return typeof message === "object" && message !== null && message.type === "startRun" && Array.isArray(message.workItemIds);
|
|
6802
|
+
}
|
|
6803
|
+
function buildFireStartRunCommand(workItemIds) {
|
|
6804
|
+
const ids = workItemIds.map((id) => String(id).trim()).filter(Boolean);
|
|
6805
|
+
return ["/specsmd-fire-builder", ...ids].join(" ");
|
|
6806
|
+
}
|
|
6668
6807
|
var vscode = typeof acquireVsCodeApi === "function" ? acquireVsCodeApi() : createStandaloneApi();
|
|
6669
6808
|
|
|
6670
6809
|
// src/webview/components/app.ts
|
|
@@ -7105,6 +7244,15 @@
|
|
|
7105
7244
|
}
|
|
7106
7245
|
}
|
|
7107
7246
|
_handleFireFilterChange(e7) {
|
|
7247
|
+
if (this._fireData) {
|
|
7248
|
+
this._fireData = {
|
|
7249
|
+
...this._fireData,
|
|
7250
|
+
intentsData: {
|
|
7251
|
+
...this._fireData.intentsData,
|
|
7252
|
+
filter: e7.detail.filter
|
|
7253
|
+
}
|
|
7254
|
+
};
|
|
7255
|
+
}
|
|
7108
7256
|
vscode.postMessage({ type: "fireIntentsFilter", filter: e7.detail.filter });
|
|
7109
7257
|
}
|
|
7110
7258
|
_handleFireToggleExpand(e7) {
|
|
@@ -5,7 +5,7 @@ const { URL } = require('url');
|
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
const { createWatchRuntime } = require('../runtime/watch-runtime');
|
|
8
|
-
const { detectFlow } = require('../flow-detect');
|
|
8
|
+
const { detectAvailableFlows, detectFlow } = require('../flow-detect');
|
|
9
9
|
const { loadWebDashboardData } = require('./snapshot');
|
|
10
10
|
|
|
11
11
|
const PUBLIC_DIR = path.join(__dirname, 'public');
|
|
@@ -179,9 +179,13 @@ async function startDashboardWeb(options = {}) {
|
|
|
179
179
|
const clients = new Set();
|
|
180
180
|
let watcher = null;
|
|
181
181
|
let lastData = null;
|
|
182
|
+
let activeFlow = options.flow || null;
|
|
182
183
|
|
|
183
184
|
async function loadAndBroadcast() {
|
|
184
|
-
lastData = await loadWebDashboardData({ workspacePath, flow:
|
|
185
|
+
lastData = await loadWebDashboardData({ workspacePath, flow: activeFlow });
|
|
186
|
+
if (lastData.flow) {
|
|
187
|
+
activeFlow = lastData.flow;
|
|
188
|
+
}
|
|
185
189
|
const message = lastData.webviewMessage || lastData;
|
|
186
190
|
const payload = `event: message\ndata: ${JSON.stringify(message)}\n\n`;
|
|
187
191
|
for (const client of clients) {
|
|
@@ -218,6 +222,19 @@ async function startDashboardWeb(options = {}) {
|
|
|
218
222
|
sendJson(res, 200, { ok: true, data });
|
|
219
223
|
return;
|
|
220
224
|
}
|
|
225
|
+
if (message.type === 'switchFlow') {
|
|
226
|
+
const availableFlows = detectAvailableFlows(workspacePath);
|
|
227
|
+
const requestedFlow = typeof message.flowId === 'string' ? message.flowId : null;
|
|
228
|
+
if (requestedFlow && availableFlows.includes(requestedFlow)) {
|
|
229
|
+
activeFlow = requestedFlow;
|
|
230
|
+
} else if (availableFlows.length > 0) {
|
|
231
|
+
const currentIndex = availableFlows.indexOf(activeFlow);
|
|
232
|
+
activeFlow = availableFlows[(currentIndex + 1) % availableFlows.length];
|
|
233
|
+
}
|
|
234
|
+
const data = await loadAndBroadcast();
|
|
235
|
+
sendJson(res, 200, { ok: true, data });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
221
238
|
if (message.type === 'openExternal') {
|
|
222
239
|
const opened = openExternal(message.url);
|
|
223
240
|
sendJson(res, opened ? 200 : 400, opened
|
|
@@ -308,14 +325,9 @@ async function startDashboardWeb(options = {}) {
|
|
|
308
325
|
|
|
309
326
|
const initialData = await loadAndBroadcast();
|
|
310
327
|
if (options.watch !== false && initialData.flow) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
} catch {
|
|
315
|
-
detection = null;
|
|
316
|
-
}
|
|
317
|
-
const flow = detection?.flow || initialData.flow;
|
|
318
|
-
const roots = buildWatchRoots(workspacePath, flow).filter((root) => fs.existsSync(root));
|
|
328
|
+
const roots = detectAvailableFlows(workspacePath)
|
|
329
|
+
.flatMap((flow) => buildWatchRoots(workspacePath, flow))
|
|
330
|
+
.filter((root) => fs.existsSync(root));
|
|
319
331
|
if (roots.length > 0) {
|
|
320
332
|
watcher = createWatchRuntime({
|
|
321
333
|
rootPaths: roots,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
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": {
|