@hongmaple0820/scale-engine 0.49.0 → 0.50.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.
Files changed (190) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/DashboardHttpConfig.d.ts +28 -0
  4. package/dist/api/DashboardHttpConfig.js +110 -0
  5. package/dist/api/DashboardHttpConfig.js.map +1 -0
  6. package/dist/api/cli.js +102 -11
  7. package/dist/api/cli.js.map +1 -1
  8. package/dist/api/http.d.ts +1 -0
  9. package/dist/api/http.js +50 -0
  10. package/dist/api/http.js.map +1 -0
  11. package/dist/artifact/types.d.ts +5 -0
  12. package/dist/artifact/types.js.map +1 -1
  13. package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
  14. package/dist/bootstrap/DependencyBootstrap.js +14 -3
  15. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  16. package/dist/cli/cortexApplyCommand.d.ts +26 -0
  17. package/dist/cli/cortexApplyCommand.js +74 -0
  18. package/dist/cli/cortexApplyCommand.js.map +1 -0
  19. package/dist/cli/cortexCandidateCommands.d.ts +42 -0
  20. package/dist/cli/cortexCandidateCommands.js +119 -0
  21. package/dist/cli/cortexCandidateCommands.js.map +1 -0
  22. package/dist/cli/cortexCommands.d.ts +15 -0
  23. package/dist/cli/cortexCommands.js +57 -15
  24. package/dist/cli/cortexCommands.js.map +1 -1
  25. package/dist/cli/engineBootstrap.d.ts +1 -1
  26. package/dist/cli/engineBootstrap.js +2 -0
  27. package/dist/cli/engineBootstrap.js.map +1 -1
  28. package/dist/cli/evalCommands.js +1 -0
  29. package/dist/cli/evalCommands.js.map +1 -1
  30. package/dist/cli/phaseCommands.d.ts +28 -0
  31. package/dist/cli/phaseCommands.js +148 -9
  32. package/dist/cli/phaseCommands.js.map +1 -1
  33. package/dist/cli/runtimeSkillCommands.js +12 -2
  34. package/dist/cli/runtimeSkillCommands.js.map +1 -1
  35. package/dist/cli/shieldCommands.d.ts +1 -0
  36. package/dist/cli/shieldCommands.js +20 -7
  37. package/dist/cli/shieldCommands.js.map +1 -1
  38. package/dist/cli/workflowEvidenceCommands.d.ts +120 -0
  39. package/dist/cli/workflowEvidenceCommands.js +228 -2
  40. package/dist/cli/workflowEvidenceCommands.js.map +1 -1
  41. package/dist/cortex/AutoFixEventObservations.d.ts +11 -0
  42. package/dist/cortex/AutoFixEventObservations.js +72 -0
  43. package/dist/cortex/AutoFixEventObservations.js.map +1 -0
  44. package/dist/cortex/GateEvidenceObservations.d.ts +22 -0
  45. package/dist/cortex/GateEvidenceObservations.js +179 -0
  46. package/dist/cortex/GateEvidenceObservations.js.map +1 -0
  47. package/dist/cortex/GovernanceMetrics.d.ts +2 -0
  48. package/dist/cortex/GovernanceMetrics.js +112 -22
  49. package/dist/cortex/GovernanceMetrics.js.map +1 -1
  50. package/dist/cortex/InstinctApplicationRecorder.d.ts +28 -0
  51. package/dist/cortex/InstinctApplicationRecorder.js +145 -0
  52. package/dist/cortex/InstinctApplicationRecorder.js.map +1 -0
  53. package/dist/cortex/InstinctCandidateAudit.d.ts +3 -0
  54. package/dist/cortex/InstinctCandidateAudit.js +39 -0
  55. package/dist/cortex/InstinctCandidateAudit.js.map +1 -0
  56. package/dist/cortex/InstinctCandidateReview.d.ts +32 -0
  57. package/dist/cortex/InstinctCandidateReview.js +125 -0
  58. package/dist/cortex/InstinctCandidateReview.js.map +1 -0
  59. package/dist/cortex/InstinctExtractor.d.ts +1 -0
  60. package/dist/cortex/InstinctExtractor.js +24 -17
  61. package/dist/cortex/InstinctExtractor.js.map +1 -1
  62. package/dist/cortex/InstinctRuntimeEvidence.d.ts +14 -0
  63. package/dist/cortex/InstinctRuntimeEvidence.js +120 -0
  64. package/dist/cortex/InstinctRuntimeEvidence.js.map +1 -0
  65. package/dist/cortex/InstinctStore.d.ts +18 -3
  66. package/dist/cortex/InstinctStore.js +30 -9
  67. package/dist/cortex/InstinctStore.js.map +1 -1
  68. package/dist/cortex/SessionInjector.d.ts +1 -0
  69. package/dist/cortex/SessionInjector.js +15 -2
  70. package/dist/cortex/SessionInjector.js.map +1 -1
  71. package/dist/dashboard/DashboardServer.d.ts +79 -0
  72. package/dist/dashboard/DashboardServer.js +330 -6
  73. package/dist/dashboard/DashboardServer.js.map +1 -1
  74. package/dist/dashboard/spa/app.js +515 -0
  75. package/dist/dashboard/spa/components/DataTable.js +53 -0
  76. package/dist/dashboard/spa/components/EventStream.js +66 -0
  77. package/dist/dashboard/spa/components/LoadingState.js +39 -0
  78. package/dist/dashboard/spa/components/MetricCard.js +30 -0
  79. package/dist/dashboard/spa/components/Panel.js +27 -0
  80. package/dist/dashboard/spa/components/StatusBadge.js +51 -0
  81. package/dist/dashboard/spa/i18n.js +767 -0
  82. package/dist/dashboard/spa/index.html +463 -0
  83. package/dist/dashboard/spa/pages/costs.js +522 -0
  84. package/dist/dashboard/spa/pages/documents.js +540 -0
  85. package/dist/dashboard/spa/pages/knowledge.js +457 -0
  86. package/dist/dashboard/spa/pages/monitoring.js +361 -0
  87. package/dist/dashboard/spa/pages/overview.js +301 -0
  88. package/dist/dashboard/spa/pages/topology-renderers.js +251 -0
  89. package/dist/dashboard/spa/pages/topology.js +370 -0
  90. package/dist/dashboard/spa/pages/workflow-renderers.js +239 -0
  91. package/dist/dashboard/spa/pages/workflow.js +217 -0
  92. package/dist/env/EnvironmentDoctor.js +12 -7
  93. package/dist/env/EnvironmentDoctor.js.map +1 -1
  94. package/dist/eval/WorkflowEval.d.ts +9 -0
  95. package/dist/eval/WorkflowEval.js +348 -2
  96. package/dist/eval/WorkflowEval.js.map +1 -1
  97. package/dist/memory/MemoryBrain.d.ts +13 -0
  98. package/dist/memory/MemoryBrain.js +47 -0
  99. package/dist/memory/MemoryBrain.js.map +1 -1
  100. package/dist/memory/MemoryFabric.d.ts +1 -0
  101. package/dist/memory/MemoryFabric.js +12 -8
  102. package/dist/memory/MemoryFabric.js.map +1 -1
  103. package/dist/memory/MemoryLearning.d.ts +1 -0
  104. package/dist/memory/MemoryLearning.js +6 -3
  105. package/dist/memory/MemoryLearning.js.map +1 -1
  106. package/dist/memory/MemoryProviders.d.ts +8 -1
  107. package/dist/memory/MemoryProviders.js +143 -29
  108. package/dist/memory/MemoryProviders.js.map +1 -1
  109. package/dist/runtime/AiOsRuntime.d.ts +14 -1
  110. package/dist/runtime/AiOsRuntime.js +59 -3
  111. package/dist/runtime/AiOsRuntime.js.map +1 -1
  112. package/dist/runtime/RuntimeDoctor.js +3 -1
  113. package/dist/runtime/RuntimeDoctor.js.map +1 -1
  114. package/dist/runtime/RuntimeEvidenceLedger.d.ts +6 -0
  115. package/dist/runtime/RuntimeEvidenceLedger.js +52 -1
  116. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -1
  117. package/dist/runtime/SessionLedger.d.ts +2 -0
  118. package/dist/runtime/SessionLedger.js +4 -0
  119. package/dist/runtime/SessionLedger.js.map +1 -1
  120. package/dist/setup/SetupVerification.js +53 -5
  121. package/dist/setup/SetupVerification.js.map +1 -1
  122. package/dist/shield/PolicyCompiler.js +73 -12
  123. package/dist/shield/PolicyCompiler.js.map +1 -1
  124. package/dist/shield/ProtectedPaths.js +4 -2
  125. package/dist/shield/ProtectedPaths.js.map +1 -1
  126. package/dist/skills/SkillCatalog.d.ts +2 -0
  127. package/dist/skills/SkillCatalog.js +8 -0
  128. package/dist/skills/SkillCatalog.js.map +1 -1
  129. package/dist/skills/SkillDoctor.d.ts +19 -2
  130. package/dist/skills/SkillDoctor.js +163 -13
  131. package/dist/skills/SkillDoctor.js.map +1 -1
  132. package/dist/tools/SafeCommandRunner.d.ts +1 -0
  133. package/dist/tools/SafeCommandRunner.js +1 -0
  134. package/dist/tools/SafeCommandRunner.js.map +1 -1
  135. package/dist/tools/ToolCapabilityRegistry.js +25 -3
  136. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  137. package/dist/tools/ToolOrchestrator.js +21 -0
  138. package/dist/tools/ToolOrchestrator.js.map +1 -1
  139. package/dist/version.d.ts +1 -1
  140. package/dist/version.js +1 -1
  141. package/dist/workflow/AgentLoopReadiness.d.ts +103 -0
  142. package/dist/workflow/AgentLoopReadiness.js +371 -0
  143. package/dist/workflow/AgentLoopReadiness.js.map +1 -0
  144. package/dist/workflow/EcosystemReadinessGate.d.ts +46 -0
  145. package/dist/workflow/EcosystemReadinessGate.js +126 -0
  146. package/dist/workflow/EcosystemReadinessGate.js.map +1 -0
  147. package/dist/workflow/EngineeringStandards.js +48 -3
  148. package/dist/workflow/EngineeringStandards.js.map +1 -1
  149. package/dist/workflow/GateCatalog.js +9 -0
  150. package/dist/workflow/GateCatalog.js.map +1 -1
  151. package/dist/workflow/GovernanceTemplatePacks.js +2 -26
  152. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  153. package/dist/workflow/GovernanceTemplates.js +8 -1
  154. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  155. package/dist/workflow/ReleaseDeploymentLedger.d.ts +63 -0
  156. package/dist/workflow/ReleaseDeploymentLedger.js +154 -0
  157. package/dist/workflow/ReleaseDeploymentLedger.js.map +1 -0
  158. package/dist/workflow/ReviewAnalyzer.js +50 -3
  159. package/dist/workflow/ReviewAnalyzer.js.map +1 -1
  160. package/dist/workflow/SessionPreamble.d.ts +7 -0
  161. package/dist/workflow/SessionPreamble.js +48 -9
  162. package/dist/workflow/SessionPreamble.js.map +1 -1
  163. package/dist/workflow/VerificationCommands.d.ts +1 -0
  164. package/dist/workflow/VerificationCommands.js.map +1 -1
  165. package/dist/workflow/VerificationProfile.d.ts +5 -0
  166. package/dist/workflow/VerificationProfile.js +26 -0
  167. package/dist/workflow/VerificationProfile.js.map +1 -1
  168. package/dist/workflow/VerificationSchema.d.ts +3 -0
  169. package/dist/workflow/VerificationSchema.js +6 -0
  170. package/dist/workflow/VerificationSchema.js.map +1 -1
  171. package/dist/workflow/WorkflowEffectiveness.d.ts +97 -0
  172. package/dist/workflow/WorkflowEffectiveness.js +302 -0
  173. package/dist/workflow/WorkflowEffectiveness.js.map +1 -0
  174. package/dist/workflow/WorkflowEffectivenessRenderer.d.ts +2 -0
  175. package/dist/workflow/WorkflowEffectivenessRenderer.js +67 -0
  176. package/dist/workflow/WorkflowEffectivenessRenderer.js.map +1 -0
  177. package/dist/workflow/WorkflowEffectivenessScoring.d.ts +6 -0
  178. package/dist/workflow/WorkflowEffectivenessScoring.js +243 -0
  179. package/dist/workflow/WorkflowEffectivenessScoring.js.map +1 -0
  180. package/dist/workflow/gates/GateSystem.d.ts +16 -0
  181. package/dist/workflow/gates/GateSystem.js +208 -41
  182. package/dist/workflow/gates/GateSystem.js.map +1 -1
  183. package/dist/workflow/gates/MetaGovernanceGates.js +269 -8
  184. package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -1
  185. package/docs/reference/cli.md +2 -1
  186. package/docs/start/agent-governance-demo.md +1 -1
  187. package/docs/workflow/README.md +1 -1
  188. package/docs/workflow/templates/github-actions-scale-preflight.yml +4 -1
  189. package/package.json +6 -3
  190. package/scripts/workflow/run-vitest.mjs +123 -0
@@ -0,0 +1,361 @@
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
+ })()
@@ -0,0 +1,301 @@
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
+ })()