akm-cli 0.9.0-beta.2 → 0.9.0-beta.4
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 +248 -0
- package/dist/assets/templates/html/default.html +78 -0
- package/dist/assets/templates/html/health.html +560 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/shared.js +21 -5
- package/dist/cli.js +36 -5
- package/dist/commands/health/html-report.js +448 -0
- package/dist/commands/health.js +97 -6
- package/dist/commands/improve/consolidate.js +15 -2
- package/dist/commands/improve/extract.js +38 -2
- package/dist/commands/improve/improve-auto-accept.js +27 -1
- package/dist/commands/improve/improve.js +167 -53
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +25 -0
- package/dist/commands/proposal/drain.js +73 -6
- package/dist/commands/proposal/proposal-cli.js +22 -10
- package/dist/commands/proposal/proposal.js +12 -1
- package/dist/commands/proposal/validators/proposals.js +361 -338
- package/dist/commands/remember.js +6 -2
- package/dist/core/config/config-schema.js +5 -0
- package/dist/core/logs-db.js +304 -0
- package/dist/core/state-db.js +107 -14
- package/dist/indexer/db/db.js +2 -2
- package/dist/indexer/passes/memory-inference.js +61 -22
- package/dist/integrations/harnesses/claude/session-log.js +16 -4
- package/dist/llm/client.js +15 -0
- 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/helpers.js +17 -1
- package/dist/output/text/helpers.js +69 -1
- package/dist/scripts/migrate-storage.js +65 -14
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +14 -2
- package/dist/tasks/runner.js +99 -16
- package/dist/workflows/db.js +4 -0
- package/package.json +2 -1
|
@@ -0,0 +1,560 @@
|
|
|
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: #8b949e;
|
|
21
|
+
--accent: #58a6ff;
|
|
22
|
+
--green: #3fb950;
|
|
23
|
+
--yellow: #d29922;
|
|
24
|
+
--red: #f85149;
|
|
25
|
+
--purple: #bc8cff;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
body {
|
|
29
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', sans-serif;
|
|
30
|
+
background: var(--bg);
|
|
31
|
+
color: var(--text);
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
line-height: 1.6;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ── Header ─────────────────────────────────────────────────────────── */
|
|
37
|
+
header {
|
|
38
|
+
background: var(--surface);
|
|
39
|
+
border-bottom: 1px solid var(--border);
|
|
40
|
+
padding: 16px 20px;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 12px;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
}
|
|
46
|
+
header .logo { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; flex-shrink: 0; }
|
|
47
|
+
header .meta { color: var(--muted); font-size: 13px; min-width: 0; }
|
|
48
|
+
header .meta .title { font-weight: 600; color: var(--text); }
|
|
49
|
+
header .badges { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
50
|
+
@media (max-width: 600px) {
|
|
51
|
+
header { padding: 12px 16px; gap: 8px; }
|
|
52
|
+
header .badges { margin-left: 0; width: 100%; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── Badges ─────────────────────────────────────────────────────────── */
|
|
56
|
+
.badge-pill {
|
|
57
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
58
|
+
padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;
|
|
59
|
+
}
|
|
60
|
+
.badge-warn { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid rgba(210,153,34,0.3); }
|
|
61
|
+
.badge-pass { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid rgba(63,185,80,0.3); }
|
|
62
|
+
.badge-fail { background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid rgba(248,81,73,0.3); }
|
|
63
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
64
|
+
.dot-warn { background: var(--yellow); }
|
|
65
|
+
.dot-pass { background: var(--green); }
|
|
66
|
+
.dot-fail { background: var(--red); }
|
|
67
|
+
|
|
68
|
+
/* ── Layout ─────────────────────────────────────────────────────────── */
|
|
69
|
+
main { padding: 20px 32px; max-width: 1400px; margin: 0 auto; }
|
|
70
|
+
@media (max-width: 768px) { main { padding: 16px; } }
|
|
71
|
+
|
|
72
|
+
.section { margin-bottom: 32px; }
|
|
73
|
+
h2 { font-size: 16px; font-weight: 600; margin-bottom: 16px; }
|
|
74
|
+
h3 { font-size: 13px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
|
|
75
|
+
|
|
76
|
+
/* ── Executive summary (Discord card) ───────────────────────────────── */
|
|
77
|
+
.exec {
|
|
78
|
+
background: linear-gradient(180deg, var(--surface) 0%, #12161c 100%);
|
|
79
|
+
border: 1px solid var(--border); border-left: 4px solid var(--accent);
|
|
80
|
+
border-radius: 10px; padding: 18px 22px; margin-bottom: 28px;
|
|
81
|
+
}
|
|
82
|
+
.exec h2 { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
|
|
83
|
+
.exec .exec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
|
|
84
|
+
@media (max-width: 900px) { .exec .exec-grid { grid-template-columns: 1fr; gap: 16px; } }
|
|
85
|
+
.exec . blk-title, .exec h4 {
|
|
86
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px;
|
|
87
|
+
color: var(--muted); margin-bottom: 8px; font-weight: 600;
|
|
88
|
+
}
|
|
89
|
+
.exec ul { list-style: none; }
|
|
90
|
+
.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); }
|
|
91
|
+
.exec li:last-child { border-bottom: none; }
|
|
92
|
+
.exec li .k { color: var(--muted); }
|
|
93
|
+
.exec li .v { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
|
|
94
|
+
.trend-pill {
|
|
95
|
+
display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px;
|
|
96
|
+
border-radius: 12px; font-size: 11px; font-weight: 600;
|
|
97
|
+
}
|
|
98
|
+
.trend-pill.up { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
99
|
+
.trend-pill.down { background: rgba(248,81,73,0.15); color: var(--red); }
|
|
100
|
+
.trend-pill.flat { background: rgba(139,148,158,0.12);color: var(--muted); }
|
|
101
|
+
.exec .overall {
|
|
102
|
+
margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
|
|
103
|
+
font-size: 13px; color: var(--muted);
|
|
104
|
+
}
|
|
105
|
+
.exec .overall b { color: var(--text); }
|
|
106
|
+
|
|
107
|
+
/* ── KPI cards ──────────────────────────────────────────────────────── */
|
|
108
|
+
.kpi-grid {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
111
|
+
gap: 10px; margin-bottom: 28px;
|
|
112
|
+
}
|
|
113
|
+
@media (max-width: 600px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 20px; } }
|
|
114
|
+
.kpi-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; }
|
|
115
|
+
.kpi-card .label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
|
|
116
|
+
.kpi-card .value { font-size: 26px; font-weight: 700; line-height: 1; }
|
|
117
|
+
.kpi-card .sub { font-size: 11px; color: var(--muted); margin-top: 4px; }
|
|
118
|
+
@media (max-width: 600px) { .kpi-card .value { font-size: 22px; } }
|
|
119
|
+
.kpi-card.green .value { color: var(--green); }
|
|
120
|
+
.kpi-card.yellow .value { color: var(--yellow); }
|
|
121
|
+
.kpi-card.red .value { color: var(--red); }
|
|
122
|
+
.kpi-card.blue .value { color: var(--accent); }
|
|
123
|
+
.kpi-card.purple .value { color: var(--purple); }
|
|
124
|
+
|
|
125
|
+
/* ── Chart panels ───────────────────────────────────────────────────── */
|
|
126
|
+
.charts-grid {
|
|
127
|
+
display: grid;
|
|
128
|
+
grid-template-columns: repeat(2, 1fr);
|
|
129
|
+
gap: 14px; margin-bottom: 28px;
|
|
130
|
+
}
|
|
131
|
+
@media (max-width: 768px) {
|
|
132
|
+
.charts-grid { grid-template-columns: 1fr; gap: 10px; }
|
|
133
|
+
.chart-panel.full-width { grid-column: auto; }
|
|
134
|
+
}
|
|
135
|
+
.chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
136
|
+
.chart-panel.full-width { grid-column: 1 / -1; }
|
|
137
|
+
.chart-panel .ec { width: 100%; height: 240px; }
|
|
138
|
+
.chart-panel.full-width .ec { height: 280px; }
|
|
139
|
+
@media (max-width: 600px) { .chart-panel .ec { height: 200px; } }
|
|
140
|
+
|
|
141
|
+
/* ── Tables ─────────────────────────────────────────────────────────── */
|
|
142
|
+
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
143
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 480px; }
|
|
144
|
+
thead th {
|
|
145
|
+
text-align: left; padding: 8px 12px;
|
|
146
|
+
font-size: 11px; font-weight: 600; color: var(--muted);
|
|
147
|
+
text-transform: uppercase; letter-spacing: 0.4px;
|
|
148
|
+
border-bottom: 1px solid var(--border); white-space: nowrap;
|
|
149
|
+
}
|
|
150
|
+
tbody tr { border-bottom: 1px solid var(--border); }
|
|
151
|
+
tbody tr:hover { background: var(--surface2); }
|
|
152
|
+
tbody td { padding: 9px 12px; color: var(--text); }
|
|
153
|
+
@media (max-width: 600px) {
|
|
154
|
+
table { font-size: 12px; }
|
|
155
|
+
thead th, tbody td { padding: 7px 10px; }
|
|
156
|
+
}
|
|
157
|
+
@media (hover: none) { tbody tr:active { background: var(--surface2); } }
|
|
158
|
+
|
|
159
|
+
/* ── Tags ───────────────────────────────────────────────────────────── */
|
|
160
|
+
.tag { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
|
161
|
+
.tag-extract { background: rgba(88,166,255,0.15); color: var(--accent); }
|
|
162
|
+
.tag-consolidate { background: rgba(188,140,255,0.15); color: var(--purple); }
|
|
163
|
+
|
|
164
|
+
/* ── Advisories ─────────────────────────────────────────────────────── */
|
|
165
|
+
.advisory-list { display: flex; flex-direction: column; gap: 10px; }
|
|
166
|
+
.advisory {
|
|
167
|
+
display: flex; gap: 12px;
|
|
168
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
169
|
+
border-radius: 8px; padding: 14px 16px;
|
|
170
|
+
}
|
|
171
|
+
.advisory.warn { border-left: 3px solid var(--yellow); }
|
|
172
|
+
.advisory.fail { border-left: 3px solid var(--red); }
|
|
173
|
+
.advisory-icon { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
|
|
174
|
+
.advisory-body .title { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
|
|
175
|
+
.advisory-body .desc { font-size: 12px; color: var(--muted); word-break: break-word; }
|
|
176
|
+
|
|
177
|
+
/* ── Trend indicators ───────────────────────────────────────────────── */
|
|
178
|
+
.trend { font-size: 12px; }
|
|
179
|
+
.trend-up { color: var(--green); }
|
|
180
|
+
.trend-down { color: var(--red); }
|
|
181
|
+
.trend-flat { color: var(--muted); }
|
|
182
|
+
|
|
183
|
+
/* ── Two-col ─────────────────────────────────────────────────────────── */
|
|
184
|
+
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
185
|
+
@media (max-width: 900px) { .two-col { grid-template-columns: 1fr; } }
|
|
186
|
+
|
|
187
|
+
/* ── Summary table wrapper ──────────────────────────────────────────── */
|
|
188
|
+
.summary-table { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
189
|
+
|
|
190
|
+
/* ── Code / command block ───────────────────────────────────────────── */
|
|
191
|
+
code {
|
|
192
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
193
|
+
font-size: 11px; background: var(--surface2);
|
|
194
|
+
padding: 2px 6px; border-radius: 4px; color: var(--accent); word-break: break-all;
|
|
195
|
+
}
|
|
196
|
+
.command-block {
|
|
197
|
+
background: var(--surface2); border: 1px solid var(--border); border-radius: 6px;
|
|
198
|
+
padding: 12px 16px; font-family: 'SFMono-Regular', Consolas, monospace;
|
|
199
|
+
font-size: 12px; color: var(--muted); line-height: 1.8; overflow-x: auto;
|
|
200
|
+
}
|
|
201
|
+
.command-block span { color: var(--accent); }
|
|
202
|
+
|
|
203
|
+
/* ── Footer ─────────────────────────────────────────────────────────── */
|
|
204
|
+
footer {
|
|
205
|
+
border-top: 1px solid var(--border); padding: 14px 32px;
|
|
206
|
+
color: var(--muted); font-size: 12px;
|
|
207
|
+
display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px;
|
|
208
|
+
}
|
|
209
|
+
@media (max-width: 600px) { footer { padding: 12px 16px; flex-direction: column; } }
|
|
210
|
+
</style>
|
|
211
|
+
</head>
|
|
212
|
+
<body>
|
|
213
|
+
|
|
214
|
+
<header>
|
|
215
|
+
<div class="logo">AKM</div>
|
|
216
|
+
<div class="meta">
|
|
217
|
+
<div class="title">Improve Health Report</div>
|
|
218
|
+
<div>Window: %%WINDOW%% · %%SINCE_HUMAN%% · %%RUN_COUNT%% runs</div>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="badges">
|
|
221
|
+
%%STATUS_BADGE_HTML%%
|
|
222
|
+
</div>
|
|
223
|
+
</header>
|
|
224
|
+
|
|
225
|
+
<main>
|
|
226
|
+
|
|
227
|
+
<!-- Executive Summary (HTML rendering of the Discord/improve-health text report) -->
|
|
228
|
+
<section class="exec">
|
|
229
|
+
%%EXEC_SUMMARY_HTML%%
|
|
230
|
+
</section>
|
|
231
|
+
|
|
232
|
+
<!-- KPI Row -->
|
|
233
|
+
<div class="kpi-grid">
|
|
234
|
+
%%KPI_CARDS_HTML%%
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<!-- Charts -->
|
|
238
|
+
<div class="section">
|
|
239
|
+
<h2>Run Trend Lines (%%WINDOW%%)</h2>
|
|
240
|
+
<div class="charts-grid">
|
|
241
|
+
|
|
242
|
+
<div class="chart-panel full-width">
|
|
243
|
+
<h3>Wall Time per Run (min) — with rolling average</h3>
|
|
244
|
+
<div id="chartWallTime" class="ec"></div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div class="chart-panel full-width">
|
|
248
|
+
<h3>Per-Phase Wall Time Decomposition (stacked, min)</h3>
|
|
249
|
+
<div id="chartPhases" class="ec"></div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="chart-panel">
|
|
253
|
+
<h3>Stash Growth (derived vs eligible)</h3>
|
|
254
|
+
<div id="chartStash" class="ec"></div>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="chart-panel">
|
|
258
|
+
<h3>Consolidation Output (promoted / merged / deleted)</h3>
|
|
259
|
+
<div id="chartConsOutput" class="ec"></div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div class="chart-panel">
|
|
263
|
+
<h3>Success / Failures per Run</h3>
|
|
264
|
+
<div id="chartSuccess" class="ec"></div>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="chart-panel">
|
|
268
|
+
<h3>Lint Flagged vs Fixed</h3>
|
|
269
|
+
<div id="chartLint" class="ec"></div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div class="chart-panel full-width">
|
|
273
|
+
<h3>Distill Skip-Reason Breakdown (stacked)</h3>
|
|
274
|
+
<div id="chartDistill" class="ec"></div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<!-- Summary + Advisories -->
|
|
281
|
+
<div class="section">
|
|
282
|
+
<h2>%%WINDOW%% Aggregate Summary</h2>
|
|
283
|
+
<div class="two-col">
|
|
284
|
+
|
|
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
|
+
|
|
296
|
+
<div>
|
|
297
|
+
<h3 style="margin-bottom:12px;">Decision Quality & Advisories</h3>
|
|
298
|
+
<div class="advisory-list">
|
|
299
|
+
%%ADVISORY_CARDS_HTML%%
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<!-- Proposals -->
|
|
307
|
+
<div class="section">
|
|
308
|
+
<h2>Pending Proposals (%%PROPOSAL_COUNT%%)</h2>
|
|
309
|
+
<div class="summary-table">
|
|
310
|
+
<div class="table-wrap">
|
|
311
|
+
<table>
|
|
312
|
+
<thead><tr><th>#</th><th>Ref</th><th>Source</th><th>Created</th></tr></thead>
|
|
313
|
+
<tbody>
|
|
314
|
+
%%PROPOSAL_ROWS_HTML%%
|
|
315
|
+
</tbody>
|
|
316
|
+
</table>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<!-- What to watch -->
|
|
322
|
+
<div class="section">
|
|
323
|
+
<h2>What to Watch Next</h2>
|
|
324
|
+
<div class="advisory-list">
|
|
325
|
+
%%WATCH_ITEMS_HTML%%
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<!-- Last 10 runs -->
|
|
330
|
+
<div class="section">
|
|
331
|
+
<h2>Last 10 Runs</h2>
|
|
332
|
+
<div class="summary-table">
|
|
333
|
+
<div class="table-wrap">
|
|
334
|
+
<table>
|
|
335
|
+
<thead>
|
|
336
|
+
<tr><th>Started (UTC)</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>
|
|
337
|
+
</thead>
|
|
338
|
+
<tbody id="lastRunsTable"></tbody>
|
|
339
|
+
</table>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<!-- Command set -->
|
|
345
|
+
<div class="section">
|
|
346
|
+
<h2>Command Set Used</h2>
|
|
347
|
+
<div class="command-block">
|
|
348
|
+
%%COMMANDS_HTML%%
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
</main>
|
|
353
|
+
|
|
354
|
+
<footer>
|
|
355
|
+
<span>AKM Health Report · Window: %%WINDOW%% · %%REPORT_TITLE%%</span>
|
|
356
|
+
<span>Generated %%GENERATED_AT%%</span>
|
|
357
|
+
</footer>
|
|
358
|
+
|
|
359
|
+
<script>
|
|
360
|
+
// ── Injected run data ─────────────────────────────────────────────────────────
|
|
361
|
+
%%RUNS_JS_CONST%%
|
|
362
|
+
const DISTILL_REASONS = %%DISTILL_REASONS_JSON%%;
|
|
363
|
+
|
|
364
|
+
// ── Theme constants ───────────────────────────────────────────────────────────
|
|
365
|
+
const C = {
|
|
366
|
+
bg: '#161b22', surface2: '#21262d', border: '#30363d',
|
|
367
|
+
text: '#e6edf3', muted: '#8b949e',
|
|
368
|
+
accent: '#58a6ff', green: '#3fb950', yellow: '#d29922',
|
|
369
|
+
red: '#f85149', purple: '#bc8cff',
|
|
370
|
+
};
|
|
371
|
+
const SERIES_PALETTE = [C.accent, C.green, C.yellow, C.purple, C.red, '#39c5cf', '#db61a2', '#e3b341'];
|
|
372
|
+
|
|
373
|
+
// x-axis category labels = run startedAt (UTC, sorted ascending by collect.py)
|
|
374
|
+
const xLabels = RUNS.map(r => {
|
|
375
|
+
const d = new Date(r.startedAt);
|
|
376
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
377
|
+
const dd = String(d.getUTCDate()).padStart(2, '0');
|
|
378
|
+
const hh = String(d.getUTCHours()).padStart(2, '0');
|
|
379
|
+
const mi = String(d.getUTCMinutes()).padStart(2, '0');
|
|
380
|
+
return `${mm}-${dd} ${hh}:${mi}`;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const toMin = ms => ms ? +(ms / 60000).toFixed(2) : 0;
|
|
384
|
+
|
|
385
|
+
// Centered rolling average (window = ~10% of runs, min 3) — deterministic.
|
|
386
|
+
function rollingAvg(arr) {
|
|
387
|
+
const n = arr.length;
|
|
388
|
+
const w = Math.max(3, Math.floor(n / 10));
|
|
389
|
+
const half = Math.floor(w / 2);
|
|
390
|
+
return arr.map((_, i) => {
|
|
391
|
+
let sum = 0, cnt = 0;
|
|
392
|
+
for (let j = i - half; j <= i + half; j++) {
|
|
393
|
+
if (j >= 0 && j < n) { sum += arr[j]; cnt++; }
|
|
394
|
+
}
|
|
395
|
+
return cnt ? +(sum / cnt).toFixed(2) : 0;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const baseAxis = {
|
|
400
|
+
axisLine: { lineStyle: { color: C.border } },
|
|
401
|
+
axisLabel: { color: C.muted, fontSize: 10 },
|
|
402
|
+
splitLine: { lineStyle: { color: 'rgba(48,54,61,0.5)' } },
|
|
403
|
+
};
|
|
404
|
+
const baseGrid = { left: 48, right: 16, top: 28, bottom: 28, containLabel: true };
|
|
405
|
+
const baseTooltip = {
|
|
406
|
+
trigger: 'axis',
|
|
407
|
+
backgroundColor: C.surface2, borderColor: C.border, borderWidth: 1,
|
|
408
|
+
textStyle: { color: C.text, fontSize: 12 },
|
|
409
|
+
};
|
|
410
|
+
const baseLegend = { textStyle: { color: C.muted, fontSize: 11 }, top: 2, type: 'scroll' };
|
|
411
|
+
|
|
412
|
+
function mkChart(id, option) {
|
|
413
|
+
const el = document.getElementById(id);
|
|
414
|
+
if (!el) return;
|
|
415
|
+
const chart = echarts.init(el, null, { renderer: 'canvas' });
|
|
416
|
+
// animation off → deterministic first paint & no per-frame randomness
|
|
417
|
+
option.animation = false;
|
|
418
|
+
option.textStyle = { color: C.text };
|
|
419
|
+
chart.setOption(option);
|
|
420
|
+
window.addEventListener('resize', () => chart.resize());
|
|
421
|
+
return chart;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ── 1. Wall time per run + rolling avg ────────────────────────────────────────
|
|
425
|
+
const wall = RUNS.map(r => toMin(r.wallTimeMs));
|
|
426
|
+
mkChart('chartWallTime', {
|
|
427
|
+
tooltip: baseTooltip,
|
|
428
|
+
legend: { ...baseLegend, data: ['Wall time', 'Rolling avg'] },
|
|
429
|
+
grid: baseGrid,
|
|
430
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
431
|
+
yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
|
|
432
|
+
series: [
|
|
433
|
+
{ name: 'Wall time', type: 'line', data: wall, showSymbol: false, smooth: true,
|
|
434
|
+
lineStyle: { color: C.accent, width: 1.5 }, areaStyle: { color: 'rgba(88,166,255,0.08)' } },
|
|
435
|
+
{ name: 'Rolling avg', type: 'line', data: rollingAvg(wall), showSymbol: false, smooth: true,
|
|
436
|
+
lineStyle: { color: C.yellow, width: 2, type: 'dashed' } },
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// ── 2. Per-phase wall time decomposition (stacked area) ──────────────────────
|
|
441
|
+
mkChart('chartPhases', {
|
|
442
|
+
tooltip: baseTooltip,
|
|
443
|
+
legend: { ...baseLegend, data: ['Consolidation', 'Memory inference', 'Graph extraction', 'Other / LLM'] },
|
|
444
|
+
grid: baseGrid,
|
|
445
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
446
|
+
yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
|
|
447
|
+
series: [
|
|
448
|
+
{ name: 'Consolidation', type: 'line', stack: 'p', areaStyle: { color: 'rgba(88,166,255,0.55)' }, lineStyle: { width: 0 }, showSymbol: false, data: RUNS.map(r => toMin(r.consDurationMs)) },
|
|
449
|
+
{ name: 'Memory inference', type: 'line', stack: 'p', areaStyle: { color: 'rgba(63,185,80,0.55)' }, lineStyle: { width: 0 }, showSymbol: false, data: RUNS.map(r => toMin(r.miDurationMs)) },
|
|
450
|
+
{ name: 'Graph extraction', type: 'line', stack: 'p', areaStyle: { color: 'rgba(188,140,255,0.55)' }, lineStyle: { width: 0 }, showSymbol: false, data: RUNS.map(r => toMin(r.geDurationMs)) },
|
|
451
|
+
{ name: 'Other / LLM', type: 'line', stack: 'p', areaStyle: { color: 'rgba(210,153,34,0.45)' }, lineStyle: { width: 0 }, showSymbol: false, data: RUNS.map(r => toMin(r.otherMs)) },
|
|
452
|
+
],
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// ── 3. Stash growth (dual line) ───────────────────────────────────────────────
|
|
456
|
+
mkChart('chartStash', {
|
|
457
|
+
tooltip: baseTooltip,
|
|
458
|
+
legend: { ...baseLegend, data: ['Derived', 'Eligible'] },
|
|
459
|
+
grid: baseGrid,
|
|
460
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
461
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
462
|
+
series: [
|
|
463
|
+
{ name: 'Derived', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.green, width: 1.8 }, data: RUNS.map(r => r.derived) },
|
|
464
|
+
{ name: 'Eligible', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.accent, width: 1.8 }, data: RUNS.map(r => r.eligible) },
|
|
465
|
+
],
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// ── 4. Consolidation output (promoted / merged / deleted) ────────────────────
|
|
469
|
+
mkChart('chartConsOutput', {
|
|
470
|
+
tooltip: baseTooltip,
|
|
471
|
+
legend: { ...baseLegend, data: ['Promoted', 'Merged', 'Deleted'] },
|
|
472
|
+
grid: baseGrid,
|
|
473
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
474
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
475
|
+
series: [
|
|
476
|
+
{ name: 'Promoted', type: 'bar', itemStyle: { color: 'rgba(88,166,255,0.75)' }, data: RUNS.map(r => r.promoted) },
|
|
477
|
+
{ name: 'Merged', type: 'bar', itemStyle: { color: 'rgba(63,185,80,0.75)' }, data: RUNS.map(r => r.merged) },
|
|
478
|
+
{ name: 'Deleted', type: 'bar', itemStyle: { color: 'rgba(248,81,73,0.75)' }, data: RUNS.map(r => r.deleted) },
|
|
479
|
+
],
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ── 5. Success / failures (failed runs as distinct red scatter) ──────────────
|
|
483
|
+
mkChart('chartSuccess', {
|
|
484
|
+
tooltip: {
|
|
485
|
+
...baseTooltip,
|
|
486
|
+
formatter: p => {
|
|
487
|
+
const arr = Array.isArray(p) ? p : [p];
|
|
488
|
+
const i = arr[0].dataIndex;
|
|
489
|
+
return `${xLabels[i]}<br/>${RUNS[i].ok ? '✅ ok' : '❌ failed'} · ${toMin(RUNS[i].wallTimeMs)}m`;
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
legend: { ...baseLegend, data: ['Wall time (ok)', 'Failed run'] },
|
|
493
|
+
grid: baseGrid,
|
|
494
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
495
|
+
yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
|
|
496
|
+
series: [
|
|
497
|
+
{ name: 'Wall time (ok)', type: 'line', showSymbol: false, smooth: true,
|
|
498
|
+
lineStyle: { color: C.green, width: 1.2 }, data: RUNS.map(r => r.ok ? toMin(r.wallTimeMs) : null) },
|
|
499
|
+
{ name: 'Failed run', type: 'scatter', symbolSize: 9,
|
|
500
|
+
itemStyle: { color: C.red },
|
|
501
|
+
data: RUNS.map((r, i) => r.ok ? null : [i, toMin(r.wallTimeMs)]) },
|
|
502
|
+
],
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// ── 6. Lint flagged vs fixed ──────────────────────────────────────────────────
|
|
506
|
+
mkChart('chartLint', {
|
|
507
|
+
tooltip: baseTooltip,
|
|
508
|
+
legend: { ...baseLegend, data: ['Flagged', 'Fixed'] },
|
|
509
|
+
grid: baseGrid,
|
|
510
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
511
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
512
|
+
series: [
|
|
513
|
+
{ name: 'Flagged', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.yellow, width: 1.6 }, areaStyle: { color: 'rgba(210,153,34,0.10)' }, data: RUNS.map(r => r.lintFlagged) },
|
|
514
|
+
{ name: 'Fixed', type: 'line', showSymbol: false, smooth: true, lineStyle: { color: C.green, width: 1.6 }, data: RUNS.map(r => r.lintFixed) },
|
|
515
|
+
],
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// ── 7. Distill skip-reason breakdown (stacked bar) ───────────────────────────
|
|
519
|
+
const distillSeries = DISTILL_REASONS.map((reason, idx) => ({
|
|
520
|
+
name: reason,
|
|
521
|
+
type: 'bar',
|
|
522
|
+
stack: 'd',
|
|
523
|
+
itemStyle: { color: SERIES_PALETTE[idx % SERIES_PALETTE.length] },
|
|
524
|
+
data: RUNS.map(r => (r.distillByReason && r.distillByReason[reason]) || 0),
|
|
525
|
+
}));
|
|
526
|
+
mkChart('chartDistill', {
|
|
527
|
+
tooltip: baseTooltip,
|
|
528
|
+
legend: { ...baseLegend, data: DISTILL_REASONS },
|
|
529
|
+
grid: { ...baseGrid, top: 40 },
|
|
530
|
+
xAxis: { type: 'category', data: xLabels, ...baseAxis },
|
|
531
|
+
yAxis: { type: 'value', ...baseAxis },
|
|
532
|
+
series: distillSeries.length ? distillSeries
|
|
533
|
+
: [{ name: 'none', type: 'bar', data: RUNS.map(() => 0) }],
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// ── Last 10 runs table ────────────────────────────────────────────────────────
|
|
537
|
+
const tbody = document.getElementById('lastRunsTable');
|
|
538
|
+
RUNS.slice(-10).forEach(r => {
|
|
539
|
+
const tr = document.createElement('tr');
|
|
540
|
+
const ts = new Date(r.startedAt).toISOString().replace('T', ' ').slice(0, 16);
|
|
541
|
+
const dur = r.wallTimeMs ? (r.wallTimeMs / 60000).toFixed(1) + 'm' : '—';
|
|
542
|
+
const badge = r.ok
|
|
543
|
+
? '<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>'
|
|
544
|
+
: '<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>';
|
|
545
|
+
tr.innerHTML = `
|
|
546
|
+
<td style="font-family:monospace;font-size:12px;">${ts}</td>
|
|
547
|
+
<td>${dur}</td>
|
|
548
|
+
<td style="color:var(--accent);font-weight:600;">${r.promoted || 0}</td>
|
|
549
|
+
<td>${r.merged || 0}</td>
|
|
550
|
+
<td style="color:${(r.contradicted||0)>5?'var(--yellow)':'var(--text)'};">${r.contradicted || 0}</td>
|
|
551
|
+
<td style="color:var(--green);">${r.miWritten || 0}</td>
|
|
552
|
+
<td>${r.geEntities || 0}</td>
|
|
553
|
+
<td>${r.lintFixed || 0}</td>
|
|
554
|
+
<td>${badge}</td>
|
|
555
|
+
`;
|
|
556
|
+
tbody.appendChild(tr);
|
|
557
|
+
});
|
|
558
|
+
</script>
|
|
559
|
+
</body>
|
|
560
|
+
</html>
|