akm-cli 0.9.0-beta.2 → 0.9.0-beta.26
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 +614 -0
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +5 -1
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/templates/html/default.html +78 -0
- package/dist/assets/templates/html/health.html +730 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/shared.js +21 -5
- package/dist/cli.js +47 -5
- package/dist/commands/agent/contribute-cli.js +16 -3
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph.js +75 -71
- package/dist/commands/health/checks.js +48 -0
- package/dist/commands/health/html-report.js +790 -0
- package/dist/commands/health.js +478 -15
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate.js +634 -111
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +145 -69
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +115 -1
- package/dist/commands/improve/extract-prompt.js +33 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +280 -35
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +139 -6
- package/dist/commands/improve/improve-profiles.js +12 -0
- package/dist/commands/improve/improve.js +1851 -515
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/proactive-maintenance.js +87 -0
- package/dist/commands/improve/procedural.js +409 -0
- package/dist/commands/improve/recombine.js +488 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +51 -1
- package/dist/commands/improve/related-sessions.js +120 -0
- package/dist/commands/improve/salience.js +386 -0
- package/dist/commands/improve/triage.js +95 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +34 -1
- package/dist/commands/lint/index.js +30 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +5 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain.js +73 -6
- package/dist/commands/proposal/proposal-cli.js +22 -10
- package/dist/commands/proposal/proposal.js +17 -1
- package/dist/commands/proposal/validators/proposals.js +369 -329
- package/dist/commands/read/curate.js +294 -79
- package/dist/commands/read/search-cli.js +7 -0
- package/dist/commands/read/search.js +1 -0
- package/dist/commands/remember.js +6 -2
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/commands/sources/stash-cli.js +10 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/config/config-schema.js +241 -0
- package/dist/core/config/config.js +2 -2
- package/dist/core/logs-db.js +305 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/state-db.js +706 -42
- package/dist/indexer/db/db.js +347 -38
- package/dist/indexer/db/graph-db.js +81 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +114 -111
- package/dist/indexer/passes/memory-inference.js +71 -25
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +15 -4
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/integrations/harnesses/claude/session-log.js +27 -5
- package/dist/integrations/harnesses/opencode/session-log.js +9 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +38 -4
- package/dist/llm/embedder.js +27 -3
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/graph-extract.js +2 -1
- package/dist/llm/memory-infer.js +4 -8
- package/dist/llm/metadata-enhance.js +9 -1
- package/dist/llm/usage-persist.js +77 -0
- package/dist/llm/usage-telemetry.js +103 -0
- package/dist/output/context.js +3 -2
- package/dist/output/html-render.js +73 -0
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +17 -1
- package/dist/output/text/helpers.js +78 -1
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +1194 -607
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +455 -270
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/runner.js +99 -16
- package/dist/workflows/db.js +5 -2
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +1 -0
- package/package.json +7 -5
|
@@ -0,0 +1,730 @@
|
|
|
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>%%REPORT_TITLE%%</title>
|
|
7
|
+
<!-- %%ECHARTS_TAG%% : either an inlined <script>…echarts…</script> (self-contained
|
|
8
|
+
/ deterministic / offline) or a CDN <script src> when the renderer is run
|
|
9
|
+
with --echarts cdn. -->
|
|
10
|
+
%%ECHARTS_TAG%%
|
|
11
|
+
<style>
|
|
12
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
13
|
+
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #0d1117;
|
|
16
|
+
--surface: #161b22;
|
|
17
|
+
--surface2:#21262d;
|
|
18
|
+
--border: #30363d;
|
|
19
|
+
--text: #e6edf3;
|
|
20
|
+
--muted: #9ea8b5;
|
|
21
|
+
--accent: #58a6ff;
|
|
22
|
+
--green: #3fb950;
|
|
23
|
+
--yellow: #d29922;
|
|
24
|
+
--red: #f85149;
|
|
25
|
+
--purple: #bc8cff;
|
|
26
|
+
--neutral: #8b949e;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', sans-serif;
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--text);
|
|
33
|
+
font-size: 14px;
|
|
34
|
+
line-height: 1.6;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ── Header ─────────────────────────────────────────────────────────── */
|
|
38
|
+
header {
|
|
39
|
+
background: var(--surface);
|
|
40
|
+
border-bottom: 1px solid var(--border);
|
|
41
|
+
padding: 16px 20px;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: 12px;
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
}
|
|
47
|
+
header .logo { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; flex-shrink: 0; display: flex; flex-direction: column; line-height: 1.1; }
|
|
48
|
+
header .logo-version { font-size: 10px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
|
|
49
|
+
header .meta { color: var(--muted); font-size: 13px; min-width: 0; }
|
|
50
|
+
header .meta .title { font-weight: 600; color: var(--text); }
|
|
51
|
+
header .badges { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
52
|
+
@media (max-width: 600px) {
|
|
53
|
+
header { padding: 12px 16px; gap: 8px; }
|
|
54
|
+
header .badges { margin-left: 0; width: 100%; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ── Badges ─────────────────────────────────────────────────────────── */
|
|
58
|
+
.badge-pill {
|
|
59
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
60
|
+
padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;
|
|
61
|
+
}
|
|
62
|
+
.badge-warn { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid rgba(210,153,34,0.3); }
|
|
63
|
+
.badge-pass { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid rgba(63,185,80,0.3); }
|
|
64
|
+
.badge-fail { background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid rgba(248,81,73,0.3); }
|
|
65
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
66
|
+
.dot-warn { background: var(--yellow); }
|
|
67
|
+
.dot-pass { background: var(--green); }
|
|
68
|
+
.dot-fail { background: var(--red); }
|
|
69
|
+
|
|
70
|
+
/* ── Layout ─────────────────────────────────────────────────────────── */
|
|
71
|
+
main { padding: 20px 32px; max-width: 1400px; margin: 0 auto; }
|
|
72
|
+
@media (max-width: 768px) { main { padding: 16px; } }
|
|
73
|
+
|
|
74
|
+
.section { margin-bottom: 32px; }
|
|
75
|
+
h2 { font-size: 16px; font-weight: 600; margin-bottom: 16px; }
|
|
76
|
+
h3 { font-size: 13px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
|
|
77
|
+
|
|
78
|
+
abbr[title] { text-decoration: underline dotted; cursor: help; text-underline-offset: 2px; }
|
|
79
|
+
|
|
80
|
+
/* ── Executive summary (Discord card) ───────────────────────────────── */
|
|
81
|
+
.exec {
|
|
82
|
+
background: linear-gradient(180deg, var(--surface) 0%, #12161c 100%);
|
|
83
|
+
border: 1px solid var(--border); border-left: 4px solid var(--accent);
|
|
84
|
+
border-radius: 10px; padding: 18px 22px; margin-bottom: 28px;
|
|
85
|
+
}
|
|
86
|
+
.exec h2 { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
|
|
87
|
+
.exec .verdict {
|
|
88
|
+
font-size: 14px; color: var(--text); margin-bottom: 16px;
|
|
89
|
+
padding: 10px 14px; background: var(--surface2); border-radius: 8px;
|
|
90
|
+
border-left: 3px solid var(--accent);
|
|
91
|
+
}
|
|
92
|
+
.exec .verdict b { font-weight: 700; }
|
|
93
|
+
.exec .verdict.pass { border-left-color: var(--green); }
|
|
94
|
+
.exec .verdict.warn { border-left-color: var(--yellow); }
|
|
95
|
+
.exec .verdict.fail { border-left-color: var(--red); }
|
|
96
|
+
.exec .freshness { font-size: 12px; color: var(--muted); margin-bottom: 14px; }
|
|
97
|
+
.exec .freshness.stale { color: var(--yellow); font-weight: 600; }
|
|
98
|
+
.exec .exec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
|
|
99
|
+
@media (max-width: 900px) { .exec .exec-grid { grid-template-columns: 1fr; gap: 16px; } }
|
|
100
|
+
.exec . blk-title, .exec h4 {
|
|
101
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px;
|
|
102
|
+
color: var(--muted); margin-bottom: 8px; font-weight: 600;
|
|
103
|
+
}
|
|
104
|
+
.exec ul { list-style: none; }
|
|
105
|
+
.exec li { display: flex; justify-content: space-between; gap: 12px; padding: 3px 0; font-size: 13px; border-bottom: 1px dashed rgba(48,54,61,0.6); }
|
|
106
|
+
.exec li:last-child { border-bottom: none; }
|
|
107
|
+
.exec li .k { color: var(--muted); }
|
|
108
|
+
.exec li .v { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
|
|
109
|
+
.trend-pill {
|
|
110
|
+
display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px;
|
|
111
|
+
border-radius: 12px; font-size: 11px; font-weight: 600;
|
|
112
|
+
}
|
|
113
|
+
.trend-pill.up { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
114
|
+
.trend-pill.down { background: rgba(248,81,73,0.15); color: var(--red); }
|
|
115
|
+
.trend-pill.flat { background: rgba(139,148,158,0.12);color: var(--muted); }
|
|
116
|
+
.exec .overall {
|
|
117
|
+
margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
|
|
118
|
+
font-size: 13px; color: var(--muted);
|
|
119
|
+
}
|
|
120
|
+
.exec .overall b { color: var(--text); }
|
|
121
|
+
|
|
122
|
+
/* ── KPI cards ──────────────────────────────────────────────────────── */
|
|
123
|
+
.kpi-grid {
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
126
|
+
gap: 10px; margin-bottom: 28px;
|
|
127
|
+
}
|
|
128
|
+
.filter-bar {
|
|
129
|
+
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
130
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
131
|
+
border-radius: 8px; padding: 12px 14px; margin-bottom: 20px;
|
|
132
|
+
}
|
|
133
|
+
.filter-bar label { color: var(--muted); font-size: 12px; font-weight: 600; }
|
|
134
|
+
.filter-bar select {
|
|
135
|
+
background: var(--surface2); color: var(--text); border: 1px solid var(--border);
|
|
136
|
+
border-radius: 6px; padding: 6px 10px; font-size: 12px;
|
|
137
|
+
}
|
|
138
|
+
.filter-note { color: var(--muted); font-size: 12px; margin-left: auto; }
|
|
139
|
+
@media (max-width: 600px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 20px; } }
|
|
140
|
+
.kpi-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; }
|
|
141
|
+
.kpi-card .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
|
|
142
|
+
.kpi-card .value { font-size: 26px; font-weight: 700; line-height: 1; color: var(--text); }
|
|
143
|
+
.kpi-card .sub { font-size: 11px; color: var(--muted); margin-top: 4px; }
|
|
144
|
+
@media (max-width: 600px) { .kpi-card .value { font-size: 22px; } }
|
|
145
|
+
.kpi-card.green .value { color: var(--green); }
|
|
146
|
+
.kpi-card.yellow .value { color: var(--yellow); }
|
|
147
|
+
.kpi-card.red .value { color: var(--red); }
|
|
148
|
+
.kpi-card.blue .value { color: var(--accent); }
|
|
149
|
+
.kpi-card.purple .value { color: var(--purple); }
|
|
150
|
+
.kpi-card.neutral .value { color: var(--text); }
|
|
151
|
+
.kpi-card.green { border-left: 3px solid var(--green); }
|
|
152
|
+
.kpi-card.yellow { border-left: 3px solid var(--yellow); }
|
|
153
|
+
.kpi-card.red { border-left: 3px solid var(--red); }
|
|
154
|
+
|
|
155
|
+
/* ── Chart panels ───────────────────────────────────────────────────── */
|
|
156
|
+
.charts-grid {
|
|
157
|
+
display: grid;
|
|
158
|
+
grid-template-columns: repeat(2, 1fr);
|
|
159
|
+
gap: 14px; margin-bottom: 28px;
|
|
160
|
+
}
|
|
161
|
+
@media (max-width: 768px) {
|
|
162
|
+
.charts-grid { grid-template-columns: 1fr; gap: 10px; }
|
|
163
|
+
.chart-panel.full-width { grid-column: auto; }
|
|
164
|
+
}
|
|
165
|
+
.chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; position: relative; }
|
|
166
|
+
.chart-panel.full-width { grid-column: 1 / -1; }
|
|
167
|
+
.chart-panel .ec { width: 100%; height: 240px; }
|
|
168
|
+
.chart-panel.full-width .ec { height: 280px; }
|
|
169
|
+
.chart-panel .ec.tall { height: 300px; }
|
|
170
|
+
.chart-panel.full-width .ec.tall { height: 320px; }
|
|
171
|
+
@media (max-width: 600px) { .chart-panel .ec { height: 200px; } }
|
|
172
|
+
.empty-overlay {
|
|
173
|
+
position: absolute; inset: 44px 16px 16px 16px;
|
|
174
|
+
display: none; align-items: center; justify-content: center;
|
|
175
|
+
color: var(--muted); font-size: 13px; text-align: center;
|
|
176
|
+
pointer-events: none;
|
|
177
|
+
}
|
|
178
|
+
.empty-overlay.show { display: flex; }
|
|
179
|
+
|
|
180
|
+
/* ── Tables ─────────────────────────────────────────────────────────── */
|
|
181
|
+
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
182
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 480px; }
|
|
183
|
+
thead th {
|
|
184
|
+
text-align: left; padding: 8px 12px;
|
|
185
|
+
font-size: 11px; font-weight: 600; color: var(--muted);
|
|
186
|
+
text-transform: uppercase; letter-spacing: 0.4px;
|
|
187
|
+
border-bottom: 1px solid var(--border); white-space: nowrap;
|
|
188
|
+
}
|
|
189
|
+
tbody tr { border-bottom: 1px solid var(--border); }
|
|
190
|
+
tbody tr:hover { background: var(--surface2); }
|
|
191
|
+
tbody td { padding: 9px 12px; color: var(--text); }
|
|
192
|
+
@media (max-width: 600px) {
|
|
193
|
+
table { font-size: 12px; }
|
|
194
|
+
thead th, tbody td { padding: 7px 10px; }
|
|
195
|
+
}
|
|
196
|
+
@media (hover: none) { tbody tr:active { background: var(--surface2); } }
|
|
197
|
+
|
|
198
|
+
/* ── Tags ───────────────────────────────────────────────────────────── */
|
|
199
|
+
.tag { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
|
200
|
+
.tag-extract { background: rgba(88,166,255,0.15); color: var(--accent); }
|
|
201
|
+
.tag-consolidate { background: rgba(188,140,255,0.15); color: var(--purple); }
|
|
202
|
+
.prio { display: inline-flex; align-items: center; padding: 1px 7px; border-radius: 4px; font-size: 11px; font-weight: 700; margin-right: 6px; }
|
|
203
|
+
.prio.p1 { background: rgba(248,81,73,0.18); color: var(--red); }
|
|
204
|
+
.prio.p2 { background: rgba(210,153,34,0.18); color: var(--yellow); }
|
|
205
|
+
.prio.p3 { background: rgba(139,148,158,0.15);color: var(--muted); }
|
|
206
|
+
|
|
207
|
+
/* ── Advisories / Action items ──────────────────────────────────────── */
|
|
208
|
+
.advisory-list { display: flex; flex-direction: column; gap: 10px; }
|
|
209
|
+
.advisory {
|
|
210
|
+
display: flex; gap: 12px;
|
|
211
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
212
|
+
border-radius: 8px; padding: 14px 16px;
|
|
213
|
+
}
|
|
214
|
+
.advisory.warn { border-left: 3px solid var(--yellow); }
|
|
215
|
+
.advisory.fail { border-left: 3px solid var(--red); }
|
|
216
|
+
.advisory-icon { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
|
|
217
|
+
.advisory-body .title { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
|
|
218
|
+
.advisory-body .desc { font-size: 12px; color: var(--muted); word-break: break-word; }
|
|
219
|
+
.advisory-body .remedy { font-size: 12px; margin-top: 6px; }
|
|
220
|
+
|
|
221
|
+
/* ── Trend indicators ───────────────────────────────────────────────── */
|
|
222
|
+
.trend { font-size: 12px; }
|
|
223
|
+
.trend-up { color: var(--green); }
|
|
224
|
+
.trend-down { color: var(--red); }
|
|
225
|
+
.trend-flat { color: var(--muted); }
|
|
226
|
+
|
|
227
|
+
/* ── Two-col ─────────────────────────────────────────────────────────── */
|
|
228
|
+
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
229
|
+
@media (max-width: 900px) { .two-col { grid-template-columns: 1fr; } }
|
|
230
|
+
|
|
231
|
+
/* ── Summary table wrapper ──────────────────────────────────────────── */
|
|
232
|
+
.summary-table { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
233
|
+
|
|
234
|
+
/* ── Code / command block ───────────────────────────────────────────── */
|
|
235
|
+
code {
|
|
236
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
237
|
+
font-size: 11px; background: var(--surface2);
|
|
238
|
+
padding: 2px 6px; border-radius: 4px; color: var(--accent); word-break: break-all;
|
|
239
|
+
}
|
|
240
|
+
/* ── Footer ─────────────────────────────────────────────────────────── */
|
|
241
|
+
footer {
|
|
242
|
+
border-top: 1px solid var(--border); padding: 14px 32px;
|
|
243
|
+
color: var(--muted); font-size: 12px;
|
|
244
|
+
display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px;
|
|
245
|
+
}
|
|
246
|
+
@media (max-width: 600px) { footer { padding: 12px 16px; flex-direction: column; } }
|
|
247
|
+
</style>
|
|
248
|
+
</head>
|
|
249
|
+
<body>
|
|
250
|
+
|
|
251
|
+
<header>
|
|
252
|
+
<div class="logo">AKM<span class="logo-version">v%%AKM_VERSION%%</span></div>
|
|
253
|
+
<div class="meta">
|
|
254
|
+
<div class="title">Improve Health Report</div>
|
|
255
|
+
<div>Window: %%WINDOW%% · %%SINCE_HUMAN%% · %%RUN_COUNT%% runs</div>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="badges">
|
|
258
|
+
%%STATUS_BADGE_HTML%%
|
|
259
|
+
</div>
|
|
260
|
+
</header>
|
|
261
|
+
|
|
262
|
+
<main>
|
|
263
|
+
|
|
264
|
+
<!-- Executive Summary (HTML rendering of the Discord/improve-health text report) -->
|
|
265
|
+
<section class="exec">
|
|
266
|
+
%%EXEC_SUMMARY_HTML%%
|
|
267
|
+
</section>
|
|
268
|
+
|
|
269
|
+
<!-- Action Items (merged + de-duplicated advisories and what-to-watch) -->
|
|
270
|
+
<div class="section">
|
|
271
|
+
<h2>Action Items</h2>
|
|
272
|
+
<div class="advisory-list">
|
|
273
|
+
%%ACTION_ITEMS_HTML%%
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<!-- KPI Row -->
|
|
278
|
+
<div class="kpi-grid">
|
|
279
|
+
%%KPI_CARDS_HTML%%
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<!-- Summary -->
|
|
283
|
+
<div class="section">
|
|
284
|
+
<h2>%%WINDOW%% Aggregate Summary</h2>
|
|
285
|
+
<div class="summary-table">
|
|
286
|
+
<div class="table-wrap">
|
|
287
|
+
<table>
|
|
288
|
+
<thead><tr><th>Metric</th><th>Value</th><th>Trend</th></tr></thead>
|
|
289
|
+
<tbody>
|
|
290
|
+
%%SUMMARY_ROWS_HTML%%
|
|
291
|
+
</tbody>
|
|
292
|
+
</table>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div class="filter-bar">
|
|
298
|
+
<label for="rangeSelect">Slice</label>
|
|
299
|
+
<select id="rangeSelect">
|
|
300
|
+
%%SLICE_OPTIONS_HTML%%
|
|
301
|
+
</select>
|
|
302
|
+
|
|
303
|
+
<label for="taskFilter">Task</label>
|
|
304
|
+
<select id="taskFilter"></select>
|
|
305
|
+
|
|
306
|
+
<label for="statusFilter">Status</label>
|
|
307
|
+
<select id="statusFilter">
|
|
308
|
+
<option value="all" selected>all</option>
|
|
309
|
+
<option value="ok">ok</option>
|
|
310
|
+
<option value="failed">failed</option>
|
|
311
|
+
</select>
|
|
312
|
+
|
|
313
|
+
<div class="filter-note">Filters affect charts and the Recent Runs table.</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<!-- Charts -->
|
|
317
|
+
<div class="section">
|
|
318
|
+
<h2>Run Trend Lines (%%WINDOW%%)</h2>
|
|
319
|
+
<div class="charts-grid">
|
|
320
|
+
|
|
321
|
+
<div class="chart-panel full-width">
|
|
322
|
+
<h3>Wall Time — successful vs failed runs (min)</h3>
|
|
323
|
+
<div id="chartWallTime" class="ec tall" role="img" aria-label="Line chart of wall time per run in minutes with a rolling average overlay"></div>
|
|
324
|
+
<div class="empty-overlay" id="emptyWallTime">No runs in the selected slice.</div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<div class="chart-panel full-width">
|
|
328
|
+
<h3>Per-Phase Wall Time Decomposition (stacked, min)</h3>
|
|
329
|
+
<div id="chartPhases" class="ec tall" role="img" aria-label="Stacked area chart decomposing wall time per run into consolidation, memory inference, graph extraction, and unattributed phases"></div>
|
|
330
|
+
<div class="empty-overlay" id="emptyPhases">No runs in the selected slice.</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<div class="chart-panel full-width">
|
|
334
|
+
<h3>LLM Tokens per Stage (stacked: prompt / completion / reasoning)</h3>
|
|
335
|
+
<div id="chartLlmStages" class="ec" role="img" aria-label="Horizontal stacked bar chart of LLM token usage per pipeline stage, broken into prompt, completion, and reasoning tokens"></div>
|
|
336
|
+
<div class="empty-overlay" id="emptyLlmStages">No per-stage LLM usage recorded in this window.</div>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div class="chart-panel">
|
|
340
|
+
<h3>Stash Growth (derived vs eligible)</h3>
|
|
341
|
+
<div id="chartStash" class="ec tall" role="img" aria-label="Line chart of stash derived versus eligible asset counts per run"></div>
|
|
342
|
+
<div class="empty-overlay" id="emptyStash">No runs in the selected slice.</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div class="chart-panel">
|
|
346
|
+
<h3>Consolidation Output (promoted line · merged / deleted bars)</h3>
|
|
347
|
+
<div id="chartConsOutput" class="ec tall" role="img" aria-label="Consolidation output per run: promoted as a line on the right axis, merged and deleted as bars on the left axis"></div>
|
|
348
|
+
<div class="empty-overlay" id="emptyConsOutput">No runs in the selected slice.</div>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<div class="chart-panel">
|
|
352
|
+
<h3>Success / Failures per Run</h3>
|
|
353
|
+
<div id="chartSuccess" class="ec" role="img" aria-label="Line chart of wall time for successful runs with failed runs marked as red triangles"></div>
|
|
354
|
+
<div class="empty-overlay" id="emptySuccess">No runs in the selected slice.</div>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<div class="chart-panel">
|
|
358
|
+
<h3>Lint Flagged vs Fixed</h3>
|
|
359
|
+
<div id="chartLint" class="ec" role="img" aria-label="Line chart of lint issues flagged versus fixed per run"></div>
|
|
360
|
+
<div class="empty-overlay" id="emptyLint">No runs in the selected slice.</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div class="chart-panel full-width">
|
|
364
|
+
<h3>Distill Skip-Reason Breakdown (stacked)</h3>
|
|
365
|
+
<div id="chartDistill" class="ec tall" role="img" aria-label="Stacked bar chart of distill skip reasons per run"></div>
|
|
366
|
+
<div class="empty-overlay" id="emptyDistill">No distill skips recorded in this window.</div>
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<!-- Proposals -->
|
|
373
|
+
<div class="section">
|
|
374
|
+
<h2>Pending Proposals (%%PROPOSAL_COUNT%%)</h2>
|
|
375
|
+
<div class="summary-table">
|
|
376
|
+
<div class="table-wrap">
|
|
377
|
+
<table>
|
|
378
|
+
<thead><tr><th>#</th><th>Ref</th><th>Source</th><th>Created</th></tr></thead>
|
|
379
|
+
<tbody>
|
|
380
|
+
%%PROPOSAL_ROWS_HTML%%
|
|
381
|
+
</tbody>
|
|
382
|
+
</table>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<!-- Recent runs -->
|
|
388
|
+
<div class="section">
|
|
389
|
+
<h2>Recent Runs</h2>
|
|
390
|
+
<div class="summary-table">
|
|
391
|
+
<div class="table-wrap">
|
|
392
|
+
<table>
|
|
393
|
+
<thead>
|
|
394
|
+
<tr><th>Started</th><th>Task</th><th>Wall</th><th>Promoted</th><th>Merged</th><th>Contradicted</th><th>MI Written</th><th>Entities</th><th>Lint Fixed</th><th>Status</th></tr>
|
|
395
|
+
</thead>
|
|
396
|
+
<tbody id="lastRunsTable"></tbody>
|
|
397
|
+
</table>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
</main>
|
|
403
|
+
|
|
404
|
+
<footer>
|
|
405
|
+
<span>AKM v%%AKM_VERSION%% · Window: %%WINDOW%% · %%REPORT_TITLE%%</span>
|
|
406
|
+
<span>Generated <time data-iso="%%GENERATED_AT%%">%%GENERATED_AT%%</time></span>
|
|
407
|
+
</footer>
|
|
408
|
+
|
|
409
|
+
<script>
|
|
410
|
+
// ── Injected run data ─────────────────────────────────────────────────────────
|
|
411
|
+
%%RUNS_JS_CONST%%
|
|
412
|
+
const DISTILL_REASONS = %%DISTILL_REASONS_JSON%%;
|
|
413
|
+
const LLM_BY_STAGE = %%LLM_BY_STAGE_JSON%%;
|
|
414
|
+
|
|
415
|
+
// ── Theme constants ───────────────────────────────────────────────────────────
|
|
416
|
+
const C = {
|
|
417
|
+
bg: '#161b22', surface2: '#21262d', border: '#30363d',
|
|
418
|
+
text: '#e6edf3', muted: '#9ea8b5',
|
|
419
|
+
accent: '#58a6ff', green: '#3fb950', yellow: '#d29922',
|
|
420
|
+
red: '#f85149', purple: '#bc8cff',
|
|
421
|
+
};
|
|
422
|
+
const SERIES_PALETTE = [C.accent, C.green, C.yellow, C.purple, C.red, '#39c5cf', '#db61a2', '#e3b341'];
|
|
423
|
+
|
|
424
|
+
const toMin = ms => ms ? +(ms / 60000).toFixed(2) : 0;
|
|
425
|
+
|
|
426
|
+
function makeXLabels(rs) {
|
|
427
|
+
return rs.map(r => {
|
|
428
|
+
const d = new Date(r.startedAt);
|
|
429
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
430
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
431
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
432
|
+
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
433
|
+
return `${mm}-${dd} ${hh}:${mi}`;
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function latestRunTime(rs) {
|
|
438
|
+
if (!rs.length) return 0;
|
|
439
|
+
return Math.max(...rs.map(r => Date.parse(r.completedAt || r.startedAt || 0)).filter(Number.isFinite));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function filteredRuns() {
|
|
443
|
+
const slice = document.getElementById('rangeSelect')?.value || 'all';
|
|
444
|
+
const task = document.getElementById('taskFilter')?.value || 'all';
|
|
445
|
+
const status = document.getElementById('statusFilter')?.value || 'all';
|
|
446
|
+
// slice value is 'all' (full window) or a cutoff in ms relative to the newest run.
|
|
447
|
+
const startMs = slice === 'all' ? -Infinity : latestRunTime(RUNS) - Number(slice);
|
|
448
|
+
return RUNS.filter(r => {
|
|
449
|
+
const ts = Date.parse(r.completedAt || r.startedAt || 0);
|
|
450
|
+
if (Number.isFinite(ts) && ts < startMs) return false;
|
|
451
|
+
if (task !== 'all' && (r.taskId || 'manual') !== task) return false;
|
|
452
|
+
if (status === 'ok' && !r.ok) return false;
|
|
453
|
+
if (status === 'failed' && r.ok) return false;
|
|
454
|
+
return true;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function populateFilters() {
|
|
459
|
+
const taskSelect = document.getElementById('taskFilter');
|
|
460
|
+
const tasks = ['all', ...Array.from(new Set(RUNS.map(r => r.taskId || 'manual'))).sort()];
|
|
461
|
+
taskSelect.innerHTML = tasks.map(t => `<option value="${t}">${t}</option>`).join('');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function toggleEmpty(id, isEmpty) {
|
|
465
|
+
const el = document.getElementById(id);
|
|
466
|
+
if (el) el.classList.toggle('show', !!isEmpty);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Centered rolling average (window = ~10% of runs, min 3) — deterministic.
|
|
470
|
+
function rollingAvg(arr) {
|
|
471
|
+
const n = arr.length;
|
|
472
|
+
const w = Math.max(3, Math.floor(n / 10));
|
|
473
|
+
const half = Math.floor(w / 2);
|
|
474
|
+
return arr.map((_, i) => {
|
|
475
|
+
let sum = 0, cnt = 0;
|
|
476
|
+
for (let j = i - half; j <= i + half; j++) {
|
|
477
|
+
if (j >= 0 && j < n) { sum += arr[j]; cnt++; }
|
|
478
|
+
}
|
|
479
|
+
return cnt ? +(sum / cnt).toFixed(2) : 0;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const baseAxis = {
|
|
484
|
+
axisLine: { lineStyle: { color: C.border } },
|
|
485
|
+
axisLabel: { color: C.muted, fontSize: 10 },
|
|
486
|
+
splitLine: { lineStyle: { color: 'rgba(48,54,61,0.5)' } },
|
|
487
|
+
};
|
|
488
|
+
const baseGrid = { left: 48, right: 16, top: 28, bottom: 28, containLabel: true };
|
|
489
|
+
const denseGrid = { left: 48, right: 16, top: 28, bottom: 40, containLabel: true };
|
|
490
|
+
const baseTooltip = {
|
|
491
|
+
trigger: 'axis',
|
|
492
|
+
backgroundColor: C.surface2, borderColor: C.border, borderWidth: 1,
|
|
493
|
+
textStyle: { color: C.text, fontSize: 12 },
|
|
494
|
+
};
|
|
495
|
+
const baseLegend = { textStyle: { color: C.muted, fontSize: 11 }, top: 2, type: 'scroll' };
|
|
496
|
+
const denseZoom = [{ type: 'slider', bottom: 4, height: 18, borderColor: C.border, backgroundColor: 'rgba(33,38,45,0.6)', fillerColor: 'rgba(88,166,255,0.18)', handleStyle: { color: C.accent }, textStyle: { color: C.muted, fontSize: 9 } }];
|
|
497
|
+
|
|
498
|
+
function mkChart(id, option) {
|
|
499
|
+
const el = document.getElementById(id);
|
|
500
|
+
if (!el) return;
|
|
501
|
+
const prior = echarts.getInstanceByDom(el);
|
|
502
|
+
if (prior) prior.dispose();
|
|
503
|
+
const chart = echarts.init(el, null, { renderer: 'canvas' });
|
|
504
|
+
// animation off → deterministic first paint & no per-frame randomness
|
|
505
|
+
option.animation = false;
|
|
506
|
+
option.textStyle = { color: C.text };
|
|
507
|
+
chart.setOption(option);
|
|
508
|
+
window.addEventListener('resize', () => chart.resize());
|
|
509
|
+
return chart;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── 1. Wall time per run + rolling avg ────────────────────────────────────────
|
|
513
|
+
function renderCharts(rs) {
|
|
514
|
+
const xLabels = makeXLabels(rs);
|
|
515
|
+
toggleEmpty('emptyWallTime', !rs.length);
|
|
516
|
+
toggleEmpty('emptyPhases', !rs.length);
|
|
517
|
+
toggleEmpty('emptyStash', !rs.length);
|
|
518
|
+
toggleEmpty('emptyConsOutput', !rs.length);
|
|
519
|
+
toggleEmpty('emptySuccess', !rs.length);
|
|
520
|
+
toggleEmpty('emptyLint', !rs.length);
|
|
521
|
+
const wall = rs.map(r => toMin(r.wallTimeMs));
|
|
522
|
+
const failPoints = rs.map((r, i) => r.ok ? null : [i, toMin(r.wallTimeMs)]).filter(Boolean);
|
|
523
|
+
mkChart('chartWallTime', {
|
|
524
|
+
tooltip: baseTooltip,
|
|
525
|
+
legend: { ...baseLegend, data: ['Wall time', 'Rolling avg', 'Failed run'] },
|
|
526
|
+
grid: denseGrid,
|
|
527
|
+
dataZoom: denseZoom,
|
|
528
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
529
|
+
yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
|
|
530
|
+
series: [
|
|
531
|
+
{ name: 'Wall time', type: 'line', data: wall, showSymbol: false, smooth: true,
|
|
532
|
+
lineStyle: { color: C.accent, width: 1.5 }, areaStyle: { color: 'rgba(88,166,255,0.08)' } },
|
|
533
|
+
{ name: 'Rolling avg', type: 'line', data: rollingAvg(wall), showSymbol: false, smooth: true,
|
|
534
|
+
lineStyle: { color: C.yellow, width: 2, type: 'dashed' } },
|
|
535
|
+
{ name: 'Failed run', type: 'scatter', symbol: 'triangle', symbolSize: 12,
|
|
536
|
+
itemStyle: { color: C.red }, data: failPoints },
|
|
537
|
+
],
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// ── 2. Per-phase wall time decomposition (stacked area) ──────────────────────
|
|
541
|
+
mkChart('chartPhases', {
|
|
542
|
+
tooltip: baseTooltip,
|
|
543
|
+
legend: { ...baseLegend, data: ['Consolidation', 'Memory inference', 'Graph extraction', 'Unattributed'] },
|
|
544
|
+
grid: denseGrid,
|
|
545
|
+
dataZoom: denseZoom,
|
|
546
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
547
|
+
yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
|
|
548
|
+
series: [
|
|
549
|
+
{ name: 'Consolidation', type: 'line', stack: 'p', areaStyle: { color: 'rgba(88,166,255,0.55)' }, lineStyle: { width: 0 }, showSymbol: false, data: rs.map(r => toMin(r.consDurationMs)) },
|
|
550
|
+
{ name: 'Memory inference', type: 'line', stack: 'p', areaStyle: { color: 'rgba(63,185,80,0.55)' }, lineStyle: { width: 0 }, showSymbol: false, data: rs.map(r => toMin(r.miDurationMs)) },
|
|
551
|
+
{ name: 'Graph extraction', type: 'line', stack: 'p', areaStyle: { color: 'rgba(188,140,255,0.55)' }, lineStyle: { width: 0 }, showSymbol: false, data: rs.map(r => toMin(r.geDurationMs)) },
|
|
552
|
+
{ name: 'Unattributed', type: 'line', stack: 'p', areaStyle: { color: 'rgba(210,153,34,0.45)' }, lineStyle: { width: 0 }, showSymbol: false, data: rs.map(r => toMin(r.otherMs)) },
|
|
553
|
+
],
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// ── 3. Stash growth (dual line) ───────────────────────────────────────────────
|
|
557
|
+
mkChart('chartStash', {
|
|
558
|
+
tooltip: baseTooltip,
|
|
559
|
+
legend: { ...baseLegend, data: ['Derived', 'Eligible'] },
|
|
560
|
+
grid: denseGrid,
|
|
561
|
+
dataZoom: denseZoom,
|
|
562
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
563
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
564
|
+
series: [
|
|
565
|
+
{ name: 'Derived', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.green, width: 1.8 }, data: rs.map(r => r.derived) },
|
|
566
|
+
{ name: 'Eligible', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.accent, width: 1.8 }, data: rs.map(r => r.eligible) },
|
|
567
|
+
],
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// ── 4. Consolidation output (promoted / merged / deleted) ────────────────────
|
|
571
|
+
mkChart('chartConsOutput', {
|
|
572
|
+
tooltip: baseTooltip,
|
|
573
|
+
legend: { ...baseLegend, data: ['Promoted', 'Merged', 'Deleted'] },
|
|
574
|
+
grid: denseGrid,
|
|
575
|
+
dataZoom: denseZoom,
|
|
576
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
577
|
+
// Promoted dwarfs merged/deleted, so it gets its own right-hand axis and is
|
|
578
|
+
// drawn as a line; merged/deleted stay as bars on the left axis where their
|
|
579
|
+
// smaller magnitudes remain readable.
|
|
580
|
+
yAxis: [
|
|
581
|
+
{ type: 'value', name: 'merged / deleted', nameTextStyle: { color: C.muted, fontSize: 10 }, ...baseAxis },
|
|
582
|
+
{ type: 'value', name: 'promoted', nameTextStyle: { color: C.muted, fontSize: 10 }, position: 'right', ...baseAxis, splitLine: { show: false } },
|
|
583
|
+
],
|
|
584
|
+
series: [
|
|
585
|
+
{ name: 'Promoted', type: 'line', yAxisIndex: 1, showSymbol: false, smooth: true, lineStyle: { color: C.accent, width: 1.8 }, itemStyle: { color: C.accent }, data: rs.map(r => r.promoted) },
|
|
586
|
+
{ name: 'Merged', type: 'bar', yAxisIndex: 0, itemStyle: { color: 'rgba(63,185,80,0.75)' }, data: rs.map(r => r.merged) },
|
|
587
|
+
{ name: 'Deleted', type: 'bar', yAxisIndex: 0, itemStyle: { color: 'rgba(248,81,73,0.75)' }, data: rs.map(r => r.deleted) },
|
|
588
|
+
],
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// ── 5. Success / failures (failed runs as distinct red triangles) ────────────
|
|
592
|
+
const failScatter = rs.map((r, i) => r.ok ? null : [i, toMin(r.wallTimeMs)]).filter(Boolean);
|
|
593
|
+
mkChart('chartSuccess', {
|
|
594
|
+
tooltip: {
|
|
595
|
+
...baseTooltip,
|
|
596
|
+
formatter: p => {
|
|
597
|
+
const arr = Array.isArray(p) ? p : [p];
|
|
598
|
+
const i = arr[0].dataIndex;
|
|
599
|
+
return `${xLabels[i]}<br/>${rs[i].ok ? '✅ ok' : '❌ failed'} · ${toMin(rs[i].wallTimeMs)}m`;
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
legend: { ...baseLegend, data: ['Wall time (ok)', 'Failed run'] },
|
|
603
|
+
grid: baseGrid,
|
|
604
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
605
|
+
yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
|
|
606
|
+
series: [
|
|
607
|
+
{ name: 'Wall time (ok)', type: 'line', showSymbol: false, smooth: true,
|
|
608
|
+
lineStyle: { color: C.green, width: 1.2 }, data: rs.map(r => r.ok ? toMin(r.wallTimeMs) : null) },
|
|
609
|
+
{ name: 'Failed run', type: 'scatter', symbol: 'triangle', symbolSize: 12,
|
|
610
|
+
itemStyle: { color: C.red },
|
|
611
|
+
data: failScatter },
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// ── 6. Lint flagged vs fixed ──────────────────────────────────────────────────
|
|
616
|
+
mkChart('chartLint', {
|
|
617
|
+
tooltip: baseTooltip,
|
|
618
|
+
legend: { ...baseLegend, data: ['Flagged', 'Fixed'] },
|
|
619
|
+
grid: baseGrid,
|
|
620
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
621
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
622
|
+
series: [
|
|
623
|
+
{ name: 'Flagged', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.yellow, width: 1.6 }, areaStyle: { color: 'rgba(210,153,34,0.10)' }, data: rs.map(r => r.lintFlagged) },
|
|
624
|
+
{ name: 'Fixed', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.green, width: 1.6 }, data: rs.map(r => r.lintFixed) },
|
|
625
|
+
],
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// ── 7. Distill skip-reason breakdown (stacked bar) ───────────────────────────
|
|
629
|
+
const distillSeries = DISTILL_REASONS.map((reason, idx) => ({
|
|
630
|
+
name: reason,
|
|
631
|
+
type: 'bar',
|
|
632
|
+
stack: 'd',
|
|
633
|
+
itemStyle: { color: SERIES_PALETTE[idx % SERIES_PALETTE.length] },
|
|
634
|
+
data: rs.map(r => (r.distillByReason && r.distillByReason[reason]) || 0),
|
|
635
|
+
}));
|
|
636
|
+
const distillEmpty = !rs.length || !DISTILL_REASONS.length;
|
|
637
|
+
toggleEmpty('emptyDistill', distillEmpty);
|
|
638
|
+
mkChart('chartDistill', {
|
|
639
|
+
tooltip: baseTooltip,
|
|
640
|
+
legend: { ...baseLegend, data: DISTILL_REASONS },
|
|
641
|
+
grid: { ...denseGrid, top: 40 },
|
|
642
|
+
dataZoom: denseZoom,
|
|
643
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
644
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
645
|
+
series: distillSeries.length ? distillSeries
|
|
646
|
+
: [{ name: 'none', type: 'bar', data: rs.map(() => 0) }],
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ── 8. LLM tokens per stage (horizontal stacked bar) — window aggregate ──────
|
|
651
|
+
function humanizeStage(s) {
|
|
652
|
+
return String(s).replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
653
|
+
}
|
|
654
|
+
function renderLlmStages() {
|
|
655
|
+
const stages = Object.keys(LLM_BY_STAGE).sort();
|
|
656
|
+
toggleEmpty('emptyLlmStages', !stages.length);
|
|
657
|
+
const labels = stages.map(humanizeStage);
|
|
658
|
+
const prompt = stages.map(s => LLM_BY_STAGE[s].promptTokens || 0);
|
|
659
|
+
const completion = stages.map(s => LLM_BY_STAGE[s].completionTokens || 0);
|
|
660
|
+
const reasoning = stages.map(s => LLM_BY_STAGE[s].reasoningTokens || 0);
|
|
661
|
+
mkChart('chartLlmStages', {
|
|
662
|
+
tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
663
|
+
legend: { ...baseLegend, data: ['Prompt', 'Completion', 'Reasoning'] },
|
|
664
|
+
grid: { left: 8, right: 24, top: 28, bottom: 8, containLabel: true },
|
|
665
|
+
xAxis: { type: 'value', ...baseAxis },
|
|
666
|
+
yAxis: { type: 'category', data: labels, ...baseAxis },
|
|
667
|
+
series: [
|
|
668
|
+
{ name: 'Prompt', type: 'bar', stack: 'tok', itemStyle: { color: 'rgba(88,166,255,0.8)' }, data: prompt },
|
|
669
|
+
{ name: 'Completion', type: 'bar', stack: 'tok', itemStyle: { color: 'rgba(63,185,80,0.8)' }, data: completion },
|
|
670
|
+
{ name: 'Reasoning', type: 'bar', stack: 'tok', itemStyle: { color: 'rgba(188,140,255,0.8)' }, data: reasoning },
|
|
671
|
+
],
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ── Recent runs table ─────────────────────────────────────────────────────────
|
|
676
|
+
function renderLastRuns(rs) {
|
|
677
|
+
const tbody = document.getElementById('lastRunsTable');
|
|
678
|
+
tbody.innerHTML = '';
|
|
679
|
+
[...rs].reverse().forEach(r => {
|
|
680
|
+
const tr = document.createElement('tr');
|
|
681
|
+
const d = new Date(r.startedAt);
|
|
682
|
+
const pad = n => String(n).padStart(2, '0');
|
|
683
|
+
const ts = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
684
|
+
const dur = r.wallTimeMs ? (r.wallTimeMs / 60000).toFixed(1) + 'm' : '—';
|
|
685
|
+
const task = r.taskId || 'manual';
|
|
686
|
+
const badge = r.ok
|
|
687
|
+
? '<span class="badge-pill badge-pass" style="padding:2px 8px;font-size:11px;"><span class="dot dot-pass" style="width:6px;height:6px;"></span> ok</span>'
|
|
688
|
+
: '<span class="badge-pill badge-fail" style="padding:2px 8px;font-size:11px;"><span class="dot dot-fail" style="width:6px;height:6px;"></span> failed</span>';
|
|
689
|
+
tr.innerHTML = `
|
|
690
|
+
<td style="font-family:monospace;font-size:12px;">${ts}</td>
|
|
691
|
+
<td><code>${task}</code></td>
|
|
692
|
+
<td>${dur}</td>
|
|
693
|
+
<td style="color:var(--accent);font-weight:600;">${r.promoted || 0}</td>
|
|
694
|
+
<td>${r.merged || 0}</td>
|
|
695
|
+
<td style="color:${(r.contradicted||0)>5?'var(--yellow)':'var(--text)'};">${r.contradicted || 0}</td>
|
|
696
|
+
<td style="color:var(--green);">${r.miWritten || 0}</td>
|
|
697
|
+
<td>${r.geEntities || 0}</td>
|
|
698
|
+
<td>${r.lintFixed || 0}</td>
|
|
699
|
+
<td>${badge}</td>
|
|
700
|
+
`;
|
|
701
|
+
tbody.appendChild(tr);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function updateFilteredViews() {
|
|
706
|
+
const rs = filteredRuns();
|
|
707
|
+
renderCharts(rs);
|
|
708
|
+
renderLastRuns(rs);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
populateFilters();
|
|
712
|
+
document.getElementById('rangeSelect').addEventListener('change', updateFilteredViews);
|
|
713
|
+
document.getElementById('taskFilter').addEventListener('change', updateFilteredViews);
|
|
714
|
+
document.getElementById('statusFilter').addEventListener('change', updateFilteredViews);
|
|
715
|
+
renderLlmStages();
|
|
716
|
+
updateFilteredViews();
|
|
717
|
+
|
|
718
|
+
// Reformat server-rendered ISO timestamps to the viewer's local timezone.
|
|
719
|
+
(function() {
|
|
720
|
+
const pad = n => String(n).padStart(2, '0');
|
|
721
|
+
document.querySelectorAll('time[data-iso]').forEach(el => {
|
|
722
|
+
const d = new Date(el.dataset.iso);
|
|
723
|
+
if (!isNaN(d)) {
|
|
724
|
+
el.textContent = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
})();
|
|
728
|
+
</script>
|
|
729
|
+
</body>
|
|
730
|
+
</html>
|