akm-cli 0.8.7 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -0,0 +1,78 @@
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>akm %%COMMAND%%</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: #0d1117;
12
+ --surface: #161b22;
13
+ --border: #30363d;
14
+ --text: #e6edf3;
15
+ --muted: #8b949e;
16
+ --accent: #58a6ff;
17
+ }
18
+
19
+ body {
20
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', sans-serif;
21
+ background: var(--bg);
22
+ color: var(--text);
23
+ font-size: 14px;
24
+ line-height: 1.6;
25
+ padding: 24px;
26
+ }
27
+
28
+ header {
29
+ max-width: 980px;
30
+ margin: 0 auto 16px;
31
+ display: flex;
32
+ align-items: baseline;
33
+ gap: 12px;
34
+ flex-wrap: wrap;
35
+ }
36
+ header .logo { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; }
37
+ header .command { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted); }
38
+
39
+ main { max-width: 980px; margin: 0 auto; }
40
+
41
+ pre {
42
+ background: var(--surface);
43
+ border: 1px solid var(--border);
44
+ border-radius: 8px;
45
+ padding: 16px 20px;
46
+ overflow-x: auto;
47
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
48
+ font-size: 13px;
49
+ white-space: pre-wrap;
50
+ word-break: break-word;
51
+ }
52
+
53
+ footer {
54
+ max-width: 980px;
55
+ margin: 16px auto 0;
56
+ color: var(--muted);
57
+ font-size: 12px;
58
+ display: flex;
59
+ justify-content: space-between;
60
+ gap: 12px;
61
+ flex-wrap: wrap;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <header>
67
+ <span class="logo">akm</span>
68
+ <span class="command">%%COMMAND%%</span>
69
+ </header>
70
+ <main>
71
+ <pre>%%CONTENT_JSON%%</pre>
72
+ </main>
73
+ <footer>
74
+ <span>akm — Agent Knowledge Management</span>
75
+ <span>Generated %%GENERATED_AT%%</span>
76
+ </footer>
77
+ </body>
78
+ </html>
@@ -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%% &nbsp;·&nbsp; %%SINCE_HUMAN%% &nbsp;·&nbsp; %%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 &amp; 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 &nbsp;·&nbsp; Window: %%WINDOW%% &nbsp;·&nbsp; %%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>&nbsp;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>&nbsp;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>