@hongmaple0820/scale-engine 0.50.1 → 0.50.2

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.
Files changed (50) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/http.js +3 -1
  4. package/dist/api/http.js.map +1 -1
  5. package/dist/cli/cortexCommands.d.ts +16 -0
  6. package/dist/cli/cortexCommands.js +47 -4
  7. package/dist/cli/cortexCommands.js.map +1 -1
  8. package/dist/cortex/InstinctStore.d.ts +13 -1
  9. package/dist/cortex/InstinctStore.js +90 -11
  10. package/dist/cortex/InstinctStore.js.map +1 -1
  11. package/dist/cortex/SessionInjector.js +39 -2
  12. package/dist/cortex/SessionInjector.js.map +1 -1
  13. package/dist/dashboard/DashboardServer.d.ts +158 -0
  14. package/dist/dashboard/DashboardServer.js +753 -13
  15. package/dist/dashboard/DashboardServer.js.map +1 -1
  16. package/dist/dashboard/spa/assets/index-VYBCLBje.js +11 -0
  17. package/dist/dashboard/spa/assets/index-VhwY_ac1.css +1 -0
  18. package/dist/dashboard/spa/assets/naive-ui-BQy2AJkt.js +3340 -0
  19. package/dist/dashboard/spa/assets/vendor-BPU6aOYA.js +3 -0
  20. package/dist/dashboard/spa/assets/vue-CQQMb5Wi.js +17 -0
  21. package/dist/dashboard/spa/index.html +15 -462
  22. package/dist/memory/MemoryFabric.d.ts +13 -1
  23. package/dist/memory/MemoryFabric.js +60 -0
  24. package/dist/memory/MemoryFabric.js.map +1 -1
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.js +1 -1
  27. package/docs/workflow/ASSESSMENT_INDEX.md +326 -0
  28. package/docs/workflow/COMPARATIVE_ANALYSIS.md +422 -0
  29. package/docs/workflow/EXECUTIVE_SUMMARY.md +310 -0
  30. package/docs/workflow/IMPROVEMENT_CHECKLIST.md +518 -0
  31. package/docs/workflow/IMPROVEMENT_ROADMAP.md +707 -0
  32. package/docs/workflow/README.md +8 -0
  33. package/package.json +6 -2
  34. package/dist/dashboard/spa/app.js +0 -515
  35. package/dist/dashboard/spa/components/DataTable.js +0 -53
  36. package/dist/dashboard/spa/components/EventStream.js +0 -66
  37. package/dist/dashboard/spa/components/LoadingState.js +0 -39
  38. package/dist/dashboard/spa/components/MetricCard.js +0 -30
  39. package/dist/dashboard/spa/components/Panel.js +0 -27
  40. package/dist/dashboard/spa/components/StatusBadge.js +0 -51
  41. package/dist/dashboard/spa/i18n.js +0 -767
  42. package/dist/dashboard/spa/pages/costs.js +0 -522
  43. package/dist/dashboard/spa/pages/documents.js +0 -540
  44. package/dist/dashboard/spa/pages/knowledge.js +0 -457
  45. package/dist/dashboard/spa/pages/monitoring.js +0 -361
  46. package/dist/dashboard/spa/pages/overview.js +0 -301
  47. package/dist/dashboard/spa/pages/topology-renderers.js +0 -251
  48. package/dist/dashboard/spa/pages/topology.js +0 -370
  49. package/dist/dashboard/spa/pages/workflow-renderers.js +0 -239
  50. package/dist/dashboard/spa/pages/workflow.js +0 -217
@@ -1,361 +0,0 @@
1
- /**
2
- * Monitoring Page v2 — Defect trends, gate timeline, command stats, enhanced detector view
3
- */
4
- ;(() => {
5
- 'use strict'
6
-
7
- const { fetchJSON, relativeTime, formatNumber, registerChart, getTheme, runtimeLabel, t, $, $$, dom } = window.Dashboard
8
- const { chartContainer, dataTable, el, emptyState, metricCard, panel, renderText, safeClassToken } = dom
9
-
10
- async function renderMonitoring() {
11
- const app = $('#app')
12
- app.replaceChildren(
13
- el('div', { className: 'metrics-row', id: 'mon-metrics' }),
14
- el('div', { className: 'tabs', id: 'mon-tabs' }, [
15
- el('div', { className: 'tab active', text: t('monitoring.overview'), dataset: { tab: 'overview' } }),
16
- el('div', { className: 'tab', text: t('monitoring.detectors'), dataset: { tab: 'detectors' } }),
17
- el('div', { className: 'tab', text: t('monitoring.defects'), dataset: { tab: 'defects' } }),
18
- el('div', { className: 'tab', text: t('monitoring.commands'), dataset: { tab: 'commands' } }),
19
- ]),
20
- el('div', { id: 'mon-content' })
21
- )
22
-
23
- const [state, metrics] = await Promise.all([
24
- fetchJSON('/api/state'),
25
- fetchJSON('/api/metrics'),
26
- ])
27
-
28
- renderMonMetrics(state, metrics)
29
-
30
- let currentTab = 'overview'
31
- $('#mon-tabs').addEventListener('click', (e) => {
32
- const tab = e.target.dataset?.tab
33
- if (!tab) return
34
- currentTab = tab
35
- $$('#mon-tabs .tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab))
36
- renderMonTab(tab, state, metrics)
37
- })
38
-
39
- renderMonTab('overview', state, metrics)
40
- }
41
-
42
- function renderMonMetrics(state, metrics) {
43
- const container = $('#mon-metrics')
44
- const detectors = state?.detectorStats?.length ?? 0
45
- const defects = state?.autoDefectStats?.autoCreatedCount ?? 0
46
- const commands = metrics?.commandRuns?.total ?? 0
47
- const cmdPassRate = commands > 0 ? ((metrics.commandRuns.passed / commands) * 100).toFixed(0) + '%' : '-'
48
- const events = state?.recentEvents?.length ?? 0
49
-
50
- const cards = [
51
- { label: t('monitoring.activeDetectors'), value: detectors, cls: '' },
52
- { label: t('monitoring.autoDefects'), value: formatNumber(defects), cls: defects > 0 ? '' : 'accent' },
53
- { label: t('monitoring.commandRuns'), value: formatNumber(commands), cls: '' },
54
- { label: t('monitoring.commandPassRate'), value: cmdPassRate, cls: parseInt(cmdPassRate) >= 80 ? 'accent' : '' },
55
- { label: t('monitoring.recentEvents'), value: events, cls: '' },
56
- ]
57
- container.replaceChildren(...cards.map(c => metricCard(c.label, c.value, c.cls)))
58
- }
59
-
60
- function renderMonTab(tab, state, metrics) {
61
- const container = $('#mon-content')
62
- switch (tab) {
63
- case 'overview': renderOverview(container, state, metrics); break
64
- case 'detectors': renderDetectors(container, state); break
65
- case 'defects': renderDefects(container, state); break
66
- case 'commands': renderCommands(container, metrics); break
67
- }
68
- }
69
-
70
- // ── Overview ───────────────────────────────────────────────────────
71
-
72
- function renderOverview(container, state, metrics) {
73
- const eventCount = el('span', { className: 'count', text: `(${(state?.recentEvents ?? []).length})` })
74
- container.replaceChildren(
75
- el('div', { className: 'grid-2 mb-24' }, [
76
- chartContainer(t('monitoring.defectsByRootCause'), 'mon-rootcause-chart'),
77
- chartContainer(t('monitoring.defectsBySeverity'), 'mon-severity-chart'),
78
- ]),
79
- el('div', { className: 'grid-2' }, [
80
- chartContainer(t('monitoring.commandStatus'), 'mon-cmd-chart'),
81
- panel(t('monitoring.recentEvents'), 'mon-events', { titleSuffix: eventCount, bodyClassName: 'event-stream' }),
82
- ])
83
- )
84
- $('#mon-events').style.maxHeight = '300px'
85
-
86
- renderRootCauseChart(state)
87
- renderSeverityChart(state)
88
- renderCommandChart(metrics)
89
- renderEventStream(state)
90
- }
91
-
92
- function renderRootCauseChart(state) {
93
- const el = $('#mon-rootcause-chart')
94
- const data = state?.autoDefectStats?.byRootCause ?? {}
95
- const entries = Object.entries(data)
96
-
97
- if (entries.length === 0) {
98
- el.replaceChildren(emptyState(t('monitoring.noDefectData')))
99
- return
100
- }
101
-
102
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
103
- registerChart(chart)
104
-
105
- const colors = ['#ff4444', '#ffaa00', '#5588ff', '#00dc82', '#aa88ff', '#ff6688', '#44cccc']
106
- chart.setOption({
107
- tooltip: { trigger: 'axis' },
108
- grid: { left: 120, right: 20, top: 10, bottom: 30 },
109
- xAxis: { type: 'value', axisLabel: { color: '#a1a1a1' }, splitLine: { lineStyle: { color: '#2a2a2a' } } },
110
- yAxis: { type: 'category', data: entries.map(([k]) => runtimeLabel('status', k)), axisLabel: { color: '#a1a1a1', fontSize: 11 } },
111
- series: [{
112
- type: 'bar',
113
- data: entries.map(([, v], i) => ({
114
- value: v,
115
- itemStyle: { color: colors[i % colors.length], borderRadius: [0, 4, 4, 0] },
116
- })),
117
- barWidth: 20,
118
- }],
119
- })
120
- }
121
-
122
- function renderSeverityChart(state) {
123
- const el = $('#mon-severity-chart')
124
- const data = state?.autoDefectStats?.bySeverity ?? {}
125
- const entries = Object.entries(data)
126
-
127
- if (entries.length === 0) {
128
- el.replaceChildren(emptyState(t('common.noData')))
129
- return
130
- }
131
-
132
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
133
- registerChart(chart)
134
-
135
- const severityColors = { low: '#00dc82', medium: '#ffaa00', high: '#ff4444', critical: '#ff0000' }
136
- chart.setOption({
137
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
138
- series: [{
139
- type: 'pie', radius: ['40%', '70%'],
140
- label: { color: '#a1a1a1', formatter: '{b}\n{d}%' },
141
- data: entries.map(([sev, count]) => ({
142
- name: runtimeLabel('severity', sev), value: count,
143
- itemStyle: { color: severityColors[sev] || '#888' },
144
- })),
145
- }],
146
- })
147
- }
148
-
149
- function renderCommandChart(metrics) {
150
- const el = $('#mon-cmd-chart')
151
- const cmd = metrics?.commandRuns
152
- if (!cmd || cmd.total === 0) {
153
- el.replaceChildren(emptyState(t('monitoring.noCommandData')))
154
- return
155
- }
156
-
157
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
158
- registerChart(chart)
159
-
160
- chart.setOption({
161
- tooltip: { trigger: 'item' },
162
- series: [{
163
- type: 'pie', radius: ['40%', '70%'],
164
- label: { color: '#a1a1a1', formatter: '{b}\n{c}' },
165
- data: [
166
- { name: t('monitoring.passed'), value: cmd.passed, itemStyle: { color: '#00dc82' } },
167
- { name: t('monitoring.failed'), value: cmd.failed, itemStyle: { color: '#ff4444' } },
168
- ],
169
- }],
170
- })
171
- }
172
-
173
- function renderEventStream(state) {
174
- const container = $('#mon-events')
175
- const events = state?.recentEvents ?? []
176
-
177
- if (events.length === 0) {
178
- renderText(container, t('overview.noEvents'))
179
- return
180
- }
181
-
182
- container.replaceChildren(...events.slice(0, 30).map(e => {
183
- const artifactText = e.artifactId ? t('monitoring.artifactPrefix', { id: e.artifactId.slice(0, 8) }) : ''
184
- return el('div', { className: 'event-item' }, [
185
- el('span', { className: 'event-type', text: e.type }),
186
- el('span', { className: 'text-sm', text: artifactText }),
187
- el('span', { className: 'event-time', text: relativeTime(e.timestamp) }),
188
- ])
189
- }))
190
- }
191
-
192
- // ── Detectors ──────────────────────────────────────────────────────
193
-
194
- function renderDetectors(container, state) {
195
- const detectors = state?.detectorStats ?? []
196
-
197
- if (detectors.length === 0) {
198
- container.replaceChildren(emptyState(t('monitoring.noDetectorData'), '\uD83D\uDD0E'))
199
- return
200
- }
201
-
202
- const rows = detectors.map(d => {
203
- const highSev = (d.bySeverity?.high ?? 0) + (d.bySeverity?.critical ?? 0)
204
- const health = highSev > 5 ? 'critical' : highSev > 0 ? 'warning' : 'ok'
205
- const healthColor = { ok: '#00dc82', warning: '#ffaa00', critical: '#ff4444' }[health]
206
- const severityBadges = Object.entries(d.bySeverity ?? {}).map(([s, c]) =>
207
- el('span', { className: `severity-badge severity-${safeClassToken(s)}`, text: `${runtimeLabel('severity', s)}: ${c}` })
208
- )
209
- return el('tr', {}, [
210
- el('td', { text: d.name, style: { fontWeight: '500' } }),
211
- el('td', { text: d.totalTriggers }),
212
- el('td', {}, severityBadges),
213
- el('td', { className: 'text-muted text-sm', text: d.lastTrigger ? relativeTime(d.lastTrigger) : '-' }),
214
- el('td', {}, [
215
- el('span', { text: runtimeLabel('health', health), style: { color: healthColor, fontWeight: '500' } }),
216
- ]),
217
- ])
218
- })
219
-
220
- container.replaceChildren(
221
- el('div', { className: 'panel mb-24' }, [
222
- el('div', { className: 'panel-title', text: t('monitoring.detectorPerformance') }),
223
- dataTable([
224
- t('monitoring.detectors'),
225
- t('monitoring.triggerDistribution'),
226
- t('monitoring.defectsBySeverity'),
227
- t('monitoring.recentEvents'),
228
- t('monitoring.health'),
229
- ], rows),
230
- ]),
231
- chartContainer(t('monitoring.triggerDistribution'), 'mon-det-chart')
232
- )
233
-
234
- // Detector trigger chart
235
- const el = $('#mon-det-chart')
236
- if (el && detectors.length > 0) {
237
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
238
- registerChart(chart)
239
-
240
- chart.setOption({
241
- tooltip: { trigger: 'axis' },
242
- legend: { textStyle: { color: '#a1a1a1' }, bottom: 0 },
243
- grid: { left: 120, right: 20, top: 20, bottom: 40 },
244
- xAxis: { type: 'value', axisLabel: { color: '#a1a1a1' }, splitLine: { lineStyle: { color: '#2a2a2a' } } },
245
- yAxis: { type: 'category', data: detectors.map(d => d.name), axisLabel: { color: '#a1a1a1', fontSize: 11 } },
246
- series: ['low', 'medium', 'high', 'critical'].map(sev => ({
247
- name: runtimeLabel('severity', sev),
248
- type: 'bar',
249
- stack: 'total',
250
- data: detectors.map(d => d.bySeverity?.[sev] ?? 0),
251
- itemStyle: { color: { low: '#00dc82', medium: '#ffaa00', high: '#ff4444', critical: '#ff0000' }[sev] },
252
- barWidth: 18,
253
- })),
254
- })
255
- }
256
- }
257
-
258
- // ── Defects ────────────────────────────────────────────────────────
259
-
260
- function renderDefects(container, state) {
261
- const defects = state?.autoDefectStats?.recentDefects ?? []
262
- const byRootCause = state?.autoDefectStats?.byRootCause ?? {}
263
- const summaryCards = Object.entries(byRootCause).slice(0, 6).map(([cause, count]) =>
264
- metricCard(cause, count)
265
- )
266
- const defectBody = defects.length === 0
267
- ? el('div', { className: 'text-muted text-sm', text: t('monitoring.noAutoDefects') })
268
- : dataTable([
269
- t('monitoring.defects'),
270
- t('monitoring.defectsByRootCause'),
271
- t('monitoring.defectsBySeverity'),
272
- t('monitoring.detectors'),
273
- t('monitoring.recentEvents'),
274
- ], defects.map(d => el('tr', {}, [
275
- el('td', { text: d.title, style: { fontWeight: '500' } }),
276
- el('td', { className: 'text-muted', text: d.rootCause }),
277
- el('td', {}, [
278
- el('span', { className: `severity-badge severity-${safeClassToken(d.severity)}`, text: runtimeLabel('severity', d.severity) }),
279
- ]),
280
- el('td', { className: 'text-muted', text: d.detector }),
281
- el('td', { className: 'text-muted text-sm', text: relativeTime(d.createdAt) }),
282
- ])))
283
-
284
- container.replaceChildren(
285
- el('div', { className: 'grid-3 mb-24' }, summaryCards),
286
- el('div', { className: 'panel' }, [
287
- el('div', { className: 'panel-title' }, [
288
- document.createTextNode(t('monitoring.recentAutoDefects') + ' '),
289
- el('span', { className: 'count', text: `(${defects.length})` }),
290
- ]),
291
- defectBody,
292
- ])
293
- )
294
- }
295
-
296
- // ── Commands ───────────────────────────────────────────────────────
297
-
298
- function renderCommands(container, metrics) {
299
- const cmd = metrics?.commandRuns
300
- if (!cmd) {
301
- container.replaceChildren(emptyState(t('monitoring.noCommandData')))
302
- return
303
- }
304
-
305
- const efficiency = cmd.rawEstimatedTokens > 0
306
- ? ((cmd.savedEstimatedTokens / cmd.rawEstimatedTokens) * 100).toFixed(1)
307
- : '0'
308
-
309
- container.replaceChildren(
310
- el('div', { className: 'metrics-row mb-24' }, [
311
- metricCard(t('monitoring.totalRuns'), formatNumber(cmd.total)),
312
- metricCard(t('monitoring.passed'), formatNumber(cmd.passed), 'accent'),
313
- metricCard(t('monitoring.failed'), formatNumber(cmd.failed)),
314
- metricCard(t('monitoring.tokenSavings'), `${efficiency}%`, 'accent'),
315
- ]),
316
- el('div', { className: 'grid-2' }, [
317
- chartContainer(t('monitoring.tokenBreakdown'), 'mon-token-breakdown'),
318
- chartContainer(t('monitoring.passFailRatio'), 'mon-passfail'),
319
- ])
320
- )
321
-
322
- // Token breakdown
323
- const tokenEl = $('#mon-token-breakdown')
324
- if (tokenEl) {
325
- const chart = echarts.init(tokenEl, getTheme() === 'dark' ? 'dark' : null)
326
- registerChart(chart)
327
- chart.setOption({
328
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
329
- series: [{
330
- type: 'pie', radius: ['40%', '70%'],
331
- label: { color: '#a1a1a1', formatter: '{b}\n{d}%' },
332
- data: [
333
- { name: t('monitoring.compressed'), value: cmd.compressedEstimatedTokens, itemStyle: { color: '#5588ff' } },
334
- { name: t('monitoring.saved'), value: cmd.savedEstimatedTokens, itemStyle: { color: '#00dc82' } },
335
- ],
336
- }],
337
- })
338
- }
339
-
340
- // Pass/Fail
341
- const pfEl = $('#mon-passfail')
342
- if (pfEl) {
343
- const chart = echarts.init(pfEl, getTheme() === 'dark' ? 'dark' : null)
344
- registerChart(chart)
345
- chart.setOption({
346
- tooltip: { trigger: 'item' },
347
- series: [{
348
- type: 'pie', radius: ['40%', '70%'],
349
- label: { color: '#a1a1a1', formatter: '{b}\n{c}' },
350
- data: [
351
- { name: t('monitoring.passed'), value: cmd.passed, itemStyle: { color: '#00dc82' } },
352
- { name: t('monitoring.failed'), value: cmd.failed, itemStyle: { color: '#ff4444' } },
353
- ],
354
- }],
355
- })
356
- }
357
- }
358
-
359
- window.DashboardPages = window.DashboardPages || {}
360
- window.DashboardPages.monitoring = renderMonitoring
361
- })()
@@ -1,301 +0,0 @@
1
- /**
2
- * Overview Page — Dashboard home with key metrics, charts, and event stream
3
- */
4
- ;(() => {
5
- 'use strict'
6
-
7
- const { fetchJSON, formatNumber, formatTime, relativeTime, registerChart, getTheme, runtimeLabel, t, $, dom } = window.Dashboard
8
- const { autoRefreshControl, chartContainer, dataNote, dataTable, el, emptyState, metricCard, panel, renderText, safeClassToken } = dom
9
-
10
- async function renderOverview() {
11
- const app = $('#app')
12
- const eventCount = el('span', { className: 'count', id: 'ov-event-count' })
13
- const projectCount = el('span', { className: 'count', id: 'ov-project-count' })
14
- const refresh = el('button', { className: 'topo-btn', text: t('overview.refreshSnapshot'), title: t('common.manualRefreshHint') })
15
- async function loadSnapshot() {
16
- if (!$('#ov-metrics')) return
17
- const [state, metrics, projects] = await Promise.all([
18
- fetchJSON('/api/state'),
19
- fetchJSON('/api/metrics'),
20
- fetchJSON('/api/projects/summary'),
21
- ])
22
- if (!$('#ov-metrics')) return
23
- renderMetricCards(state, metrics)
24
- renderOverviewDataNote()
25
- renderProjectSummary(projects)
26
- renderArtifactChart(state)
27
- renderGateChart(metrics)
28
- renderEventStream(state)
29
- renderPending(state)
30
- }
31
- refresh.addEventListener('click', loadSnapshot)
32
- app.replaceChildren(
33
- el('div', { className: 'page-toolbar' }, [
34
- refresh,
35
- autoRefreshControl(loadSnapshot),
36
- el('span', { className: 'section-copy', text: t('overview.dataHint') }),
37
- ]),
38
- el('div', { id: 'ov-data-note' }),
39
- el('div', { className: 'metrics-row', id: 'ov-metrics' }),
40
- panel(t('overview.projects'), 'ov-projects', { titleSuffix: projectCount }),
41
- el('div', { className: 'grid-2 mb-24' }, [
42
- chartContainer(t('overview.artifactDistribution'), 'ov-artifact-chart'),
43
- chartContainer(t('overview.gateStatus'), 'ov-gate-chart'),
44
- ]),
45
- el('div', { className: 'grid-2' }, [
46
- panel(t('overview.recentEvents'), 'ov-events', { titleSuffix: eventCount }),
47
- panel(t('overview.pendingActions'), 'ov-pending'),
48
- ])
49
- )
50
- $('#ov-events').className = 'event-stream'
51
-
52
- await loadSnapshot()
53
- }
54
-
55
- function renderOverviewDataNote() {
56
- const node = $('#ov-data-note')
57
- if (!node) return
58
- node.replaceChildren(dataNote([
59
- { strong: true, text: t('common.mixedRefresh') },
60
- `${t('common.lastLoaded')}: ${formatTime(Date.now())}`,
61
- t('common.manualRefreshHint'),
62
- t('common.liveStreamHint'),
63
- ]))
64
- }
65
-
66
- function renderMetricCards(state, metrics) {
67
- const container = $('#ov-metrics')
68
- if (!container) return
69
- if (!state) {
70
- renderText(container, t('common.noData'), 'text-muted')
71
- return
72
- }
73
-
74
- const artifactCount = countArtifacts(state.artifacts)
75
- const defectCount = state.autoDefectStats?.totalDefects ?? 0
76
- const taskCount = metrics?.taskMetrics?.recentTasks ?? 0
77
- const firstPass = metrics?.taskMetrics?.recentFirstPassRate ?? 0
78
- const eventCount = state.recentEvents?.length ?? 0
79
- const savedTokens = metrics?.commandRuns?.savedEstimatedTokens ?? 0
80
-
81
- const cards = [
82
- { label: t('overview.totalArtifacts'), value: formatNumber(artifactCount), cls: 'accent' },
83
- { label: t('overview.pendingReviews'), value: formatNumber(taskCount), cls: '' },
84
- { label: t('overview.activeGates'), value: (firstPass * 100).toFixed(0) + '%', cls: firstPass >= 0.8 ? 'accent' : '' },
85
- { label: t('overview.defects'), value: formatNumber(defectCount), cls: defectCount > 0 ? '' : 'accent' },
86
- { label: t('costs.tokensSaved'), value: formatNumber(savedTokens), cls: savedTokens > 0 ? 'accent' : '' },
87
- ]
88
-
89
- container.replaceChildren(...cards.map(c => metricCard(c.label, c.value, c.cls)))
90
- }
91
-
92
- function renderProjectSummary(report) {
93
- const container = $('#ov-projects')
94
- const countEl = $('#ov-project-count')
95
- const panelNode = container?.closest('.panel')
96
- const projects = report?.projects ?? []
97
- if (!container || !panelNode) return
98
- if (projects.length <= 1) {
99
- panelNode.style.display = 'none'
100
- return
101
- }
102
- panelNode.style.display = ''
103
- countEl.textContent = `(${projects.length})`
104
- const rows = projects.map(item => {
105
- const projectLink = item.project.url
106
- ? el('a', { text: item.project.name, attrs: { href: item.project.url, title: item.project.projectDir } })
107
- : el('span', { text: item.project.name, title: item.project.projectDir })
108
- const nameCell = [projectLink]
109
- if (item.project.current) nameCell.push(el('span', { className: 'count', text: ` ${t('overview.currentProject')}` }))
110
- return el('tr', {}, [
111
- el('td', {}, nameCell),
112
- el('td', {}, [
113
- el('span', {
114
- className: `badge ${healthClass(item.health)}`,
115
- text: runtimeLabel('health', item.health),
116
- }),
117
- ]),
118
- el('td', { text: formatNumber(item.documents?.total ?? 0) }),
119
- el('td', { text: `${formatNumber(item.knowledge?.active ?? 0)} / ${formatNumber(item.knowledge?.total ?? 0)}` }),
120
- el('td', { text: `${formatPercent(item.metrics?.commandPassRate)} (${t('common.failedCount', { count: formatNumber(item.metrics?.failedCommandRuns ?? 0) })})` }),
121
- el('td', { text: formatNumber(item.metrics?.gateFailures ?? 0) }),
122
- ])
123
- })
124
- container.replaceChildren(dataTable([
125
- t('overview.colProject'),
126
- t('overview.colHealth'),
127
- t('overview.colDocuments'),
128
- t('overview.colMemory'),
129
- t('overview.colCommands'),
130
- t('overview.colGateFailures'),
131
- ], rows))
132
- }
133
-
134
- function healthClass(health) {
135
- if (health === 'ready') return 'badge-success'
136
- if (health === 'missing') return 'badge-danger'
137
- return 'badge-warning'
138
- }
139
-
140
- function formatPercent(value) {
141
- if (typeof value !== 'number' || Number.isNaN(value)) return '-'
142
- return `${Math.round(value * 100)}%`
143
- }
144
-
145
- function countArtifacts(roots) {
146
- let count = 0
147
- const walk = (nodes) => {
148
- for (const n of nodes ?? []) { count++; walk(n.children) }
149
- }
150
- walk(roots)
151
- return count
152
- }
153
-
154
- function renderArtifactChart(state) {
155
- const el = $('#ov-artifact-chart')
156
- if (!el) return
157
-
158
- // Count by status
159
- const statusCounts = {}
160
- const walk = (nodes) => {
161
- for (const n of nodes ?? []) {
162
- statusCounts[n.status] = (statusCounts[n.status] ?? 0) + 1
163
- walk(n.children)
164
- }
165
- }
166
- walk(state?.artifacts ?? [])
167
-
168
- const entries = Object.entries(statusCounts)
169
- if (entries.length === 0) {
170
- el.replaceChildren(emptyState(t('overview.noArtifacts')))
171
- return
172
- }
173
-
174
- echarts.getInstanceByDom(el)?.dispose()
175
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
176
- registerChart(chart)
177
-
178
- const colors = {
179
- DRAFT: '#666', REVIEWING: '#ffaa00', FROZEN: '#5588ff',
180
- COMPLETED: '#00dc82', BLOCKED: '#ff4444', IN_PROGRESS: '#ffaa00',
181
- DONE: '#00dc82', PROPOSED: '#5588ff', APPROVED: '#00dc82', REJECTED: '#ff4444',
182
- }
183
-
184
- chart.setOption({
185
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
186
- series: [{
187
- type: 'pie', radius: ['40%', '70%'], center: ['50%', '50%'],
188
- itemStyle: { borderRadius: 6, borderColor: 'transparent', borderWidth: 2 },
189
- label: { show: true, color: getTheme() === 'dark' ? '#a1a1a1' : '#555' },
190
- data: entries.map(([status, count]) => ({
191
- value: count, name: status,
192
- itemStyle: { color: colors[status] || '#888' },
193
- })),
194
- }],
195
- })
196
-
197
- window.addEventListener('themechange', () => {
198
- chart.dispose()
199
- renderArtifactChart(state)
200
- })
201
- }
202
-
203
- function renderGateChart(metrics) {
204
- const el = $('#ov-gate-chart')
205
- if (!el) return
206
-
207
- const gateFailures = metrics?.gateFailures
208
- if (!gateFailures || gateFailures.total === 0) {
209
- el.replaceChildren(emptyState(t('overview.noGateData')))
210
- return
211
- }
212
-
213
- const gates = Object.entries(gateFailures.byGate).sort((a, b) => b[1] - a[1]).slice(0, 10)
214
- const passed = gateFailures.total - gateFailures.failed
215
-
216
- echarts.getInstanceByDom(el)?.dispose()
217
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
218
- registerChart(chart)
219
- chart.setOption({
220
- tooltip: { trigger: 'axis' },
221
- grid: { left: 60, right: 20, top: 20, bottom: 30 },
222
- xAxis: { type: 'category', data: [t('monitoring.passed'), t('monitoring.failed')], axisLabel: { color: '#a1a1a1' } },
223
- yAxis: { type: 'value', axisLabel: { color: '#a1a1a1' }, splitLine: { lineStyle: { color: '#2a2a2a' } } },
224
- series: [{
225
- type: 'bar', barWidth: 40,
226
- data: [
227
- { value: passed, itemStyle: { color: '#00dc82', borderRadius: [4, 4, 0, 0] } },
228
- { value: gateFailures.failed, itemStyle: { color: '#ff4444', borderRadius: [4, 4, 0, 0] } },
229
- ],
230
- }],
231
- })
232
- }
233
-
234
- function renderEventStream(state) {
235
- const container = $('#ov-events')
236
- const countEl = $('#ov-event-count')
237
- if (!container || !countEl) return
238
- const events = state?.recentEvents ?? []
239
-
240
- if (events.length === 0) {
241
- countEl.textContent = ''
242
- renderText(container, t('overview.noEvents'), 'text-muted text-sm')
243
- return
244
- }
245
-
246
- countEl.textContent = `(${events.length})`
247
- container.replaceChildren(...events.slice(0, 20).map(e => {
248
- const artifactText = e.artifactId ? t('monitoring.artifactPrefix', { id: e.artifactId.slice(0, 8) }) : ''
249
- return el('div', { className: 'event-item' }, [
250
- el('span', { className: 'event-type', text: e.type }),
251
- el('span', { className: 'text-sm', text: artifactText }),
252
- el('span', { className: 'event-time', text: relativeTime(e.timestamp) }),
253
- ])
254
- }))
255
- }
256
-
257
- function renderPending(state) {
258
- const container = $('#ov-pending')
259
- if (!container) return
260
- const artifacts = state?.artifacts ?? []
261
-
262
- // Find artifacts that need attention (REVIEWING, PROPOSED, BLOCKED)
263
- const pending = []
264
- const walk = (nodes) => {
265
- for (const n of nodes ?? []) {
266
- if (['REVIEWING', 'PROPOSED', 'BLOCKED', 'IN_PROGRESS'].includes(n.status)) {
267
- pending.push(n)
268
- }
269
- walk(n.children)
270
- }
271
- }
272
- walk(artifacts)
273
-
274
- if (pending.length === 0) {
275
- container.replaceChildren(emptyState(t('overview.noActions'), '\u2713'))
276
- return
277
- }
278
-
279
- const rows = pending.slice(0, 10).map(a => {
280
- const status = String(a.status ?? '')
281
- return el('tr', {}, [
282
- el('td', { text: a.title }),
283
- el('td', { className: 'text-muted', text: a.type }),
284
- el('td', {}, [
285
- el('span', { className: `badge-status badge-${safeClassToken(status)}`, text: runtimeLabel('status', status) }),
286
- ]),
287
- el('td', { className: 'text-muted text-sm', text: relativeTime(a.createdAt) }),
288
- ])
289
- })
290
- container.replaceChildren(dataTable([
291
- t('overview.colArtifact'),
292
- t('workflow.colType'),
293
- t('workflow.colStatus'),
294
- t('overview.colTime'),
295
- ], rows))
296
- }
297
-
298
- // Export
299
- window.DashboardPages = window.DashboardPages || {}
300
- window.DashboardPages.overview = renderOverview
301
- })()