@ipation/specbridge 1.1.2 → 1.2.1
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/CHANGELOG.md +103 -0
- package/README.md +32 -1
- package/dist/cli.js +999 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +116 -1
- package/dist/index.js +288 -0
- package/dist/index.js.map +1 -1
- package/dist/public/index.html +551 -0
- package/package.json +3 -1
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>SpecBridge Compliance Dashboard</title>
|
|
7
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
8
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
9
|
+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script>
|
|
11
|
+
<style>
|
|
12
|
+
* {
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
20
|
+
-webkit-font-smoothing: antialiased;
|
|
21
|
+
-moz-osx-font-smoothing: grayscale;
|
|
22
|
+
background: #f5f7fa;
|
|
23
|
+
color: #2c3e50;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.header {
|
|
27
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
28
|
+
color: white;
|
|
29
|
+
padding: 2rem;
|
|
30
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.header h1 {
|
|
34
|
+
font-size: 2rem;
|
|
35
|
+
margin-bottom: 0.5rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.header p {
|
|
39
|
+
opacity: 0.9;
|
|
40
|
+
font-size: 1rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.container {
|
|
44
|
+
max-width: 1400px;
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
padding: 2rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.loading {
|
|
50
|
+
text-align: center;
|
|
51
|
+
padding: 4rem;
|
|
52
|
+
font-size: 1.2rem;
|
|
53
|
+
color: #666;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.error {
|
|
57
|
+
background: #fee;
|
|
58
|
+
border: 1px solid #fcc;
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
margin: 1rem 0;
|
|
62
|
+
color: #c33;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.grid {
|
|
66
|
+
display: grid;
|
|
67
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
68
|
+
gap: 1.5rem;
|
|
69
|
+
margin-bottom: 2rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.card {
|
|
73
|
+
background: white;
|
|
74
|
+
border-radius: 12px;
|
|
75
|
+
padding: 1.5rem;
|
|
76
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
77
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.card:hover {
|
|
81
|
+
transform: translateY(-2px);
|
|
82
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.card h2 {
|
|
86
|
+
font-size: 1.5rem;
|
|
87
|
+
margin-bottom: 1rem;
|
|
88
|
+
color: #2c3e50;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.metric {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
margin-bottom: 0.75rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.metric-label {
|
|
99
|
+
font-size: 0.9rem;
|
|
100
|
+
color: #666;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.metric-value {
|
|
104
|
+
font-size: 1.5rem;
|
|
105
|
+
font-weight: bold;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.metric-value.success {
|
|
109
|
+
color: #27ae60;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.metric-value.warning {
|
|
113
|
+
color: #f39c12;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.metric-value.danger {
|
|
117
|
+
color: #e74c3c;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.compliance-score {
|
|
121
|
+
text-align: center;
|
|
122
|
+
padding: 2rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.compliance-score .score {
|
|
126
|
+
font-size: 4rem;
|
|
127
|
+
font-weight: bold;
|
|
128
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
129
|
+
-webkit-background-clip: text;
|
|
130
|
+
-webkit-text-fill-color: transparent;
|
|
131
|
+
background-clip: text;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.compliance-score .label {
|
|
135
|
+
font-size: 0.9rem;
|
|
136
|
+
color: #666;
|
|
137
|
+
text-transform: uppercase;
|
|
138
|
+
letter-spacing: 1px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.trend-indicator {
|
|
142
|
+
display: inline-flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 0.25rem;
|
|
145
|
+
padding: 0.25rem 0.75rem;
|
|
146
|
+
border-radius: 12px;
|
|
147
|
+
font-size: 0.85rem;
|
|
148
|
+
font-weight: 600;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.trend-indicator.up {
|
|
152
|
+
background: #d4edda;
|
|
153
|
+
color: #155724;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.trend-indicator.down {
|
|
157
|
+
background: #f8d7da;
|
|
158
|
+
color: #721c24;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.trend-indicator.stable {
|
|
162
|
+
background: #fff3cd;
|
|
163
|
+
color: #856404;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.chart-container {
|
|
167
|
+
position: relative;
|
|
168
|
+
height: 300px;
|
|
169
|
+
margin-top: 1rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.decision-list {
|
|
173
|
+
list-style: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.decision-item {
|
|
177
|
+
padding: 0.75rem;
|
|
178
|
+
border-bottom: 1px solid #eee;
|
|
179
|
+
display: flex;
|
|
180
|
+
justify-content: space-between;
|
|
181
|
+
align-items: center;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.decision-item:last-child {
|
|
185
|
+
border-bottom: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.decision-name {
|
|
189
|
+
font-weight: 500;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.decision-compliance {
|
|
193
|
+
font-weight: bold;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.violation-badge {
|
|
197
|
+
display: inline-block;
|
|
198
|
+
padding: 0.25rem 0.5rem;
|
|
199
|
+
border-radius: 4px;
|
|
200
|
+
font-size: 0.75rem;
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
text-transform: uppercase;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.violation-badge.critical {
|
|
206
|
+
background: #fee;
|
|
207
|
+
color: #c33;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.violation-badge.high {
|
|
211
|
+
background: #fff3cd;
|
|
212
|
+
color: #856404;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.violation-badge.medium {
|
|
216
|
+
background: #d1ecf1;
|
|
217
|
+
color: #0c5460;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.violation-badge.low {
|
|
221
|
+
background: #e2e3e5;
|
|
222
|
+
color: #383d41;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.insights {
|
|
226
|
+
margin-top: 1rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.insight {
|
|
230
|
+
padding: 0.75rem;
|
|
231
|
+
border-left: 4px solid #ccc;
|
|
232
|
+
margin-bottom: 0.75rem;
|
|
233
|
+
background: #f9f9f9;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.insight.warning {
|
|
237
|
+
border-color: #f39c12;
|
|
238
|
+
background: #fff9e6;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.insight.success {
|
|
242
|
+
border-color: #27ae60;
|
|
243
|
+
background: #e8f8f0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.insight.info {
|
|
247
|
+
border-color: #3498db;
|
|
248
|
+
background: #e8f4fc;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.insight-message {
|
|
252
|
+
font-weight: 500;
|
|
253
|
+
margin-bottom: 0.25rem;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.insight-details {
|
|
257
|
+
font-size: 0.85rem;
|
|
258
|
+
color: #666;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.timestamp {
|
|
262
|
+
text-align: center;
|
|
263
|
+
color: #999;
|
|
264
|
+
font-size: 0.85rem;
|
|
265
|
+
margin-top: 2rem;
|
|
266
|
+
padding: 1rem;
|
|
267
|
+
}
|
|
268
|
+
</style>
|
|
269
|
+
</head>
|
|
270
|
+
<body>
|
|
271
|
+
<div id="root"></div>
|
|
272
|
+
|
|
273
|
+
<script type="text/babel">
|
|
274
|
+
const { useState, useEffect } = React;
|
|
275
|
+
|
|
276
|
+
function Dashboard() {
|
|
277
|
+
const [loading, setLoading] = useState(true);
|
|
278
|
+
const [error, setError] = useState(null);
|
|
279
|
+
const [report, setReport] = useState(null);
|
|
280
|
+
const [history, setHistory] = useState([]);
|
|
281
|
+
const [analytics, setAnalytics] = useState(null);
|
|
282
|
+
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
loadData();
|
|
285
|
+
}, []);
|
|
286
|
+
|
|
287
|
+
async function loadData() {
|
|
288
|
+
try {
|
|
289
|
+
setLoading(true);
|
|
290
|
+
setError(null);
|
|
291
|
+
|
|
292
|
+
// Load latest report
|
|
293
|
+
const reportRes = await fetch('/api/report/latest');
|
|
294
|
+
if (!reportRes.ok) throw new Error('Failed to load report');
|
|
295
|
+
const reportData = await reportRes.json();
|
|
296
|
+
setReport(reportData);
|
|
297
|
+
|
|
298
|
+
// Load history for chart
|
|
299
|
+
const historyRes = await fetch('/api/report/history?days=30');
|
|
300
|
+
if (historyRes.ok) {
|
|
301
|
+
const historyData = await historyRes.json();
|
|
302
|
+
setHistory(historyData);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Load analytics
|
|
306
|
+
const analyticsRes = await fetch('/api/analytics/summary?days=90');
|
|
307
|
+
if (analyticsRes.ok) {
|
|
308
|
+
const analyticsData = await analyticsRes.json();
|
|
309
|
+
setAnalytics(analyticsData);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
setLoading(false);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
setError(err.message);
|
|
315
|
+
setLoading(false);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (history.length > 0) {
|
|
321
|
+
// Small delay to ensure canvas is fully mounted
|
|
322
|
+
const timer = setTimeout(() => {
|
|
323
|
+
renderChart();
|
|
324
|
+
}, 100);
|
|
325
|
+
return () => clearTimeout(timer);
|
|
326
|
+
}
|
|
327
|
+
}, [history]);
|
|
328
|
+
|
|
329
|
+
function renderChart() {
|
|
330
|
+
const ctx = document.getElementById('complianceChart');
|
|
331
|
+
if (!ctx) {
|
|
332
|
+
console.warn('Chart canvas not found');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
// Destroy existing chart
|
|
338
|
+
const existing = Chart.getChart(ctx);
|
|
339
|
+
if (existing) {
|
|
340
|
+
existing.destroy();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Sort history by date
|
|
344
|
+
const sorted = [...history].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
345
|
+
|
|
346
|
+
// Format dates for better readability
|
|
347
|
+
const formatDate = (timestamp) => {
|
|
348
|
+
const date = new Date(timestamp);
|
|
349
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
new Chart(ctx, {
|
|
353
|
+
type: 'line',
|
|
354
|
+
data: {
|
|
355
|
+
labels: sorted.map(h => formatDate(h.timestamp)),
|
|
356
|
+
datasets: [{
|
|
357
|
+
label: 'Compliance %',
|
|
358
|
+
data: sorted.map(h => h.report.summary.compliance),
|
|
359
|
+
borderColor: '#667eea',
|
|
360
|
+
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
|
361
|
+
tension: 0.4,
|
|
362
|
+
fill: true,
|
|
363
|
+
pointRadius: 4,
|
|
364
|
+
pointHoverRadius: 6,
|
|
365
|
+
}]
|
|
366
|
+
},
|
|
367
|
+
options: {
|
|
368
|
+
responsive: true,
|
|
369
|
+
maintainAspectRatio: false,
|
|
370
|
+
plugins: {
|
|
371
|
+
legend: {
|
|
372
|
+
display: false,
|
|
373
|
+
},
|
|
374
|
+
tooltip: {
|
|
375
|
+
callbacks: {
|
|
376
|
+
label: (context) => `Compliance: ${context.parsed.y}%`
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
scales: {
|
|
381
|
+
y: {
|
|
382
|
+
beginAtZero: true,
|
|
383
|
+
max: 100,
|
|
384
|
+
ticks: {
|
|
385
|
+
callback: (value) => value + '%'
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
x: {
|
|
389
|
+
ticks: {
|
|
390
|
+
maxRotation: 45,
|
|
391
|
+
minRotation: 45
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error('Failed to render chart:', error);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (loading) {
|
|
403
|
+
return <div className="loading">Loading dashboard data...</div>;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (error) {
|
|
407
|
+
return (
|
|
408
|
+
<div className="container">
|
|
409
|
+
<div className="error">
|
|
410
|
+
<strong>Error:</strong> {error}
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!report) {
|
|
417
|
+
return (
|
|
418
|
+
<div className="container">
|
|
419
|
+
<div className="error">No report data available. Run `specbridge report` to generate a report.</div>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<>
|
|
426
|
+
<div className="header">
|
|
427
|
+
<h1>📊 SpecBridge Compliance Dashboard</h1>
|
|
428
|
+
<p>{report.project} - {new Date(report.timestamp).toLocaleString()}</p>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div className="container">
|
|
432
|
+
{/* Summary Cards */}
|
|
433
|
+
<div className="grid">
|
|
434
|
+
<div className="card">
|
|
435
|
+
<div className="compliance-score">
|
|
436
|
+
<div className="score">{report.summary.compliance}%</div>
|
|
437
|
+
<div className="label">Overall Compliance</div>
|
|
438
|
+
{analytics && (
|
|
439
|
+
<div style={{ marginTop: '1rem' }}>
|
|
440
|
+
<span className={`trend-indicator ${analytics.overallTrend}`}>
|
|
441
|
+
{analytics.overallTrend === 'up' ? '📈' : analytics.overallTrend === 'down' ? '📉' : '➡️'}
|
|
442
|
+
{' '}
|
|
443
|
+
{analytics.overallTrend.toUpperCase()}
|
|
444
|
+
</span>
|
|
445
|
+
</div>
|
|
446
|
+
)}
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<div className="card">
|
|
451
|
+
<h2>📋 Decisions</h2>
|
|
452
|
+
<div className="metric">
|
|
453
|
+
<span className="metric-label">Total Decisions</span>
|
|
454
|
+
<span className="metric-value">{report.summary.totalDecisions}</span>
|
|
455
|
+
</div>
|
|
456
|
+
<div className="metric">
|
|
457
|
+
<span className="metric-label">Active Decisions</span>
|
|
458
|
+
<span className="metric-value success">{report.summary.activeDecisions}</span>
|
|
459
|
+
</div>
|
|
460
|
+
<div className="metric">
|
|
461
|
+
<span className="metric-label">Total Constraints</span>
|
|
462
|
+
<span className="metric-value">{report.summary.totalConstraints}</span>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<div className="card">
|
|
467
|
+
<h2>⚠️ Violations</h2>
|
|
468
|
+
<div className="metric">
|
|
469
|
+
<span className="metric-label">Critical</span>
|
|
470
|
+
<span className={`metric-value ${report.summary.violations.critical > 0 ? 'danger' : 'success'}`}>
|
|
471
|
+
{report.summary.violations.critical}
|
|
472
|
+
</span>
|
|
473
|
+
</div>
|
|
474
|
+
<div className="metric">
|
|
475
|
+
<span className="metric-label">High</span>
|
|
476
|
+
<span className={`metric-value ${report.summary.violations.high > 0 ? 'warning' : 'success'}`}>
|
|
477
|
+
{report.summary.violations.high}
|
|
478
|
+
</span>
|
|
479
|
+
</div>
|
|
480
|
+
<div className="metric">
|
|
481
|
+
<span className="metric-label">Medium</span>
|
|
482
|
+
<span className="metric-value">{report.summary.violations.medium}</span>
|
|
483
|
+
</div>
|
|
484
|
+
<div className="metric">
|
|
485
|
+
<span className="metric-label">Low</span>
|
|
486
|
+
<span className="metric-value">{report.summary.violations.low}</span>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
|
|
491
|
+
{/* Compliance Trend Chart */}
|
|
492
|
+
{history.length > 1 && (
|
|
493
|
+
<div className="card">
|
|
494
|
+
<h2>📈 Compliance Trend (30 Days)</h2>
|
|
495
|
+
<div className="chart-container">
|
|
496
|
+
<canvas id="complianceChart"></canvas>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
)}
|
|
500
|
+
|
|
501
|
+
{/* Decision Breakdown */}
|
|
502
|
+
<div className="card">
|
|
503
|
+
<h2>🎯 Decision Compliance</h2>
|
|
504
|
+
<ul className="decision-list">
|
|
505
|
+
{report.byDecision.map(decision => (
|
|
506
|
+
<li key={decision.decisionId} className="decision-item">
|
|
507
|
+
<div>
|
|
508
|
+
<div className="decision-name">{decision.title}</div>
|
|
509
|
+
<div style={{ marginTop: '0.25rem' }}>
|
|
510
|
+
{decision.violations > 0 && (
|
|
511
|
+
<span className="violation-badge medium">
|
|
512
|
+
{decision.violations} violation{decision.violations !== 1 ? 's' : ''}
|
|
513
|
+
</span>
|
|
514
|
+
)}
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
<div className={`decision-compliance ${decision.compliance === 100 ? 'success' : decision.compliance < 50 ? 'danger' : 'warning'}`}>
|
|
518
|
+
{decision.compliance}%
|
|
519
|
+
</div>
|
|
520
|
+
</li>
|
|
521
|
+
))}
|
|
522
|
+
</ul>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
{/* Insights */}
|
|
526
|
+
{analytics && analytics.insights && analytics.insights.length > 0 && (
|
|
527
|
+
<div className="card">
|
|
528
|
+
<h2>💡 Insights</h2>
|
|
529
|
+
<div className="insights">
|
|
530
|
+
{analytics.insights.map((insight, i) => (
|
|
531
|
+
<div key={i} className={`insight ${insight.type}`}>
|
|
532
|
+
<div className="insight-message">{insight.message}</div>
|
|
533
|
+
{insight.details && <div className="insight-details">{insight.details}</div>}
|
|
534
|
+
</div>
|
|
535
|
+
))}
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
)}
|
|
539
|
+
|
|
540
|
+
<div className="timestamp">
|
|
541
|
+
Last updated: {new Date(report.timestamp).toLocaleString()}
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
</>
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
ReactDOM.render(<Dashboard />, document.getElementById('root'));
|
|
549
|
+
</script>
|
|
550
|
+
</body>
|
|
551
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ipation/specbridge",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Architecture Decision Runtime - Transform architectural decisions into executable, verifiable constraints",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"chalk": "^5.3.0",
|
|
78
78
|
"chokidar": "^3.6.0",
|
|
79
79
|
"commander": "^12.0.0",
|
|
80
|
+
"express": "^4.18.0",
|
|
80
81
|
"fast-glob": "^3.3.0",
|
|
81
82
|
"minimatch": "^9.0.0",
|
|
82
83
|
"ora": "^8.0.0",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"zod": "^3.25.0"
|
|
89
90
|
},
|
|
90
91
|
"devDependencies": {
|
|
92
|
+
"@types/express": "^4.17.0",
|
|
91
93
|
"@types/node": "^20.0.0",
|
|
92
94
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
93
95
|
"@typescript-eslint/parser": "^7.0.0",
|