akm-cli 0.9.0-beta.6 → 0.9.0-rc.0

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 (326) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  39. package/dist/assets/templates/html/health.html +281 -111
  40. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  41. package/dist/assets/wiki/schema-template.md +4 -4
  42. package/dist/cli/clack.js +56 -0
  43. package/dist/cli/config-migrate.js +7 -1
  44. package/dist/cli/confirm.js +1 -1
  45. package/dist/cli/parse-args.js +46 -1
  46. package/dist/cli/shared.js +28 -0
  47. package/dist/cli.js +25 -14
  48. package/dist/commands/agent/agent-dispatch.js +3 -2
  49. package/dist/commands/agent/agent-support.js +0 -7
  50. package/dist/commands/agent/contribute-cli.js +26 -7
  51. package/dist/commands/config-cli.js +26 -13
  52. package/dist/commands/env/child-env.js +47 -0
  53. package/dist/commands/env/env-cli.js +220 -227
  54. package/dist/commands/env/env.js +14 -67
  55. package/dist/commands/env/secret-cli.js +140 -138
  56. package/dist/commands/feedback-cli.js +153 -147
  57. package/dist/commands/graph/graph-cli.js +5 -13
  58. package/dist/commands/graph/graph.js +76 -72
  59. package/dist/commands/health/advisories.js +151 -0
  60. package/dist/commands/health/checks.js +103 -16
  61. package/dist/commands/health/html-report.js +447 -81
  62. package/dist/commands/health/improve-metrics.js +771 -0
  63. package/dist/commands/health/llm-usage.js +65 -0
  64. package/dist/commands/health/md-report.js +103 -0
  65. package/dist/commands/health/metrics.js +278 -0
  66. package/dist/commands/health/stash-exposure.js +46 -0
  67. package/dist/commands/health/surfaces.js +216 -0
  68. package/dist/commands/health/task-runs.js +135 -0
  69. package/dist/commands/health/types.js +26 -0
  70. package/dist/commands/health/windows.js +195 -0
  71. package/dist/commands/health.js +91 -1083
  72. package/dist/commands/improve/anti-collapse.js +170 -0
  73. package/dist/commands/improve/calibration.js +161 -0
  74. package/dist/commands/improve/collapse-detector.js +421 -0
  75. package/dist/commands/improve/consolidate/chunking.js +141 -0
  76. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  77. package/dist/commands/improve/consolidate/merge.js +145 -0
  78. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  79. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  80. package/dist/commands/improve/consolidate.js +1313 -1278
  81. package/dist/commands/improve/dedup.js +482 -0
  82. package/dist/commands/improve/distill/content-repair.js +202 -0
  83. package/dist/commands/improve/distill/promote-memory.js +229 -0
  84. package/dist/commands/improve/distill/quality-gate.js +236 -0
  85. package/dist/commands/improve/distill-guards.js +127 -0
  86. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  87. package/dist/commands/improve/distill.js +243 -599
  88. package/dist/commands/improve/eligibility.js +434 -0
  89. package/dist/commands/improve/encoding-salience.js +205 -0
  90. package/dist/commands/improve/extract-cli.js +179 -59
  91. package/dist/commands/improve/extract-prompt.js +55 -4
  92. package/dist/commands/improve/extract-watch.js +140 -0
  93. package/dist/commands/improve/extract.js +409 -43
  94. package/dist/commands/improve/feedback-valence.js +54 -0
  95. package/dist/commands/improve/hot-probation.js +45 -0
  96. package/dist/commands/improve/improve-auto-accept.js +160 -7
  97. package/dist/commands/improve/improve-cli.js +115 -73
  98. package/dist/commands/improve/improve-profiles.js +32 -8
  99. package/dist/commands/improve/improve-result-file.js +15 -25
  100. package/dist/commands/improve/improve-session.js +58 -0
  101. package/dist/commands/improve/improve.js +510 -2537
  102. package/dist/commands/improve/locks.js +154 -0
  103. package/dist/commands/improve/loop-stages.js +1100 -0
  104. package/dist/commands/improve/memory/memory-belief.js +14 -15
  105. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  106. package/dist/commands/improve/memory/memory-improve.js +27 -27
  107. package/dist/commands/improve/outcome-loop.js +270 -0
  108. package/dist/commands/improve/preparation.js +2002 -0
  109. package/dist/commands/improve/proactive-maintenance.js +115 -0
  110. package/dist/commands/improve/procedural.js +398 -0
  111. package/dist/commands/improve/recombine.js +818 -0
  112. package/dist/commands/improve/reflect-noise.js +0 -0
  113. package/dist/commands/improve/reflect.js +212 -45
  114. package/dist/commands/improve/salience.js +455 -0
  115. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  116. package/dist/commands/improve/shared.js +51 -0
  117. package/dist/commands/improve/triage.js +93 -0
  118. package/dist/commands/lint/agent-linter.js +19 -24
  119. package/dist/commands/lint/base-linter.js +173 -60
  120. package/dist/commands/lint/command-linter.js +19 -24
  121. package/dist/commands/lint/env-key-rules.js +38 -1
  122. package/dist/commands/lint/fact-linter.js +39 -0
  123. package/dist/commands/lint/index.js +31 -13
  124. package/dist/commands/lint/memory-linter.js +1 -1
  125. package/dist/commands/lint/registry.js +7 -2
  126. package/dist/commands/lint/task-linter.js +3 -3
  127. package/dist/commands/lint/workflow-linter.js +26 -1
  128. package/dist/commands/observability-cli.js +4 -4
  129. package/dist/commands/proposal/drain-policies.js +13 -4
  130. package/dist/commands/proposal/drain.js +45 -51
  131. package/dist/commands/proposal/legacy-import.js +115 -0
  132. package/dist/commands/proposal/proposal-cli.js +24 -34
  133. package/dist/commands/proposal/proposal.js +7 -1
  134. package/dist/commands/proposal/propose.js +8 -3
  135. package/dist/commands/proposal/repository.js +829 -0
  136. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  137. package/dist/commands/proposal/validators/proposals.js +93 -882
  138. package/dist/commands/read/curate.js +419 -103
  139. package/dist/commands/read/knowledge.js +10 -3
  140. package/dist/commands/read/remember-cli.js +133 -138
  141. package/dist/commands/read/search-cli.js +15 -8
  142. package/dist/commands/read/search.js +22 -11
  143. package/dist/commands/read/show.js +106 -14
  144. package/dist/commands/registry-cli.js +76 -87
  145. package/dist/commands/remember.js +11 -12
  146. package/dist/commands/sources/add-cli.js +91 -95
  147. package/dist/commands/sources/history.js +1 -1
  148. package/dist/commands/sources/init.js +66 -18
  149. package/dist/commands/sources/installed-stashes.js +11 -3
  150. package/dist/commands/sources/schema-repair.js +44 -46
  151. package/dist/commands/sources/self-update.js +2 -2
  152. package/dist/commands/sources/source-add.js +7 -3
  153. package/dist/commands/sources/sources-cli.js +3 -3
  154. package/dist/commands/sources/stash-cli.js +29 -41
  155. package/dist/commands/sources/stash-skeleton.js +57 -8
  156. package/dist/commands/tasks/default-tasks.js +15 -2
  157. package/dist/commands/tasks/tasks-cli.js +20 -29
  158. package/dist/commands/tasks/tasks.js +39 -11
  159. package/dist/commands/wiki-cli.js +23 -38
  160. package/dist/commands/workflow-cli.js +15 -1
  161. package/dist/core/asset/asset-registry.js +3 -1
  162. package/dist/core/asset/asset-spec.js +21 -4
  163. package/dist/core/asset/frontmatter.js +188 -167
  164. package/dist/core/asset/markdown.js +8 -0
  165. package/dist/core/authoring-rules.js +92 -0
  166. package/dist/core/common.js +4 -23
  167. package/dist/core/concurrent.js +10 -1
  168. package/dist/core/config/config-io.js +10 -1
  169. package/dist/core/config/config-migration.js +18 -40
  170. package/dist/core/config/config-schema.js +389 -58
  171. package/dist/core/config/config-types.js +3 -3
  172. package/dist/core/config/config.js +67 -22
  173. package/dist/core/deep-merge.js +38 -0
  174. package/dist/core/errors.js +1 -0
  175. package/dist/core/eval/rank-metrics.js +113 -0
  176. package/dist/core/events.js +4 -7
  177. package/dist/core/improve-types.js +47 -8
  178. package/dist/core/logs-db.js +14 -75
  179. package/dist/core/parse.js +36 -16
  180. package/dist/core/paths.js +21 -18
  181. package/dist/core/standards/resolve-standards-context.js +87 -0
  182. package/dist/core/standards/resolve-stash-standards.js +99 -0
  183. package/dist/core/standards/resolve-type-conventions.js +66 -0
  184. package/dist/core/state/migrations.js +770 -0
  185. package/dist/core/state-db.js +142 -1091
  186. package/dist/core/structured.js +69 -0
  187. package/dist/core/time.js +53 -0
  188. package/dist/core/warn.js +21 -0
  189. package/dist/core/write-source.js +37 -0
  190. package/dist/indexer/db/db.js +356 -780
  191. package/dist/indexer/db/entry-mapper.js +41 -0
  192. package/dist/indexer/db/graph-db.js +129 -86
  193. package/dist/indexer/db/llm-cache.js +2 -2
  194. package/dist/indexer/db/schema.js +516 -0
  195. package/dist/indexer/ensure-index.js +103 -24
  196. package/dist/indexer/feedback/utility-policy.js +75 -0
  197. package/dist/indexer/graph/graph-boost.js +51 -41
  198. package/dist/indexer/graph/graph-extraction.js +207 -4
  199. package/dist/indexer/index-writer-lock.js +106 -0
  200. package/dist/indexer/index-written-assets.js +105 -0
  201. package/dist/indexer/indexer.js +291 -310
  202. package/dist/indexer/passes/dir-staleness.js +114 -0
  203. package/dist/indexer/passes/memory-inference.js +13 -5
  204. package/dist/indexer/passes/metadata.js +20 -0
  205. package/dist/indexer/read-preflight.js +23 -0
  206. package/dist/indexer/search/db-search.js +89 -13
  207. package/dist/indexer/search/fts-query.js +51 -0
  208. package/dist/indexer/search/ranking-contributors.js +95 -9
  209. package/dist/indexer/search/ranking.js +79 -3
  210. package/dist/indexer/search/search-fields.js +6 -0
  211. package/dist/indexer/search/search-source.js +32 -21
  212. package/dist/indexer/search/semantic-status.js +4 -0
  213. package/dist/indexer/walk/matchers.js +9 -0
  214. package/dist/indexer/walk/walker.js +21 -13
  215. package/dist/integrations/agent/builders.js +39 -13
  216. package/dist/integrations/agent/config.js +20 -59
  217. package/dist/integrations/agent/detect.js +9 -0
  218. package/dist/integrations/agent/index.js +3 -19
  219. package/dist/integrations/agent/model-aliases.js +7 -2
  220. package/dist/integrations/agent/profiles.js +7 -1
  221. package/dist/integrations/agent/prompts.js +75 -9
  222. package/dist/integrations/agent/runner-dispatch.js +59 -0
  223. package/dist/integrations/agent/runner.js +13 -9
  224. package/dist/integrations/agent/spawn.js +69 -67
  225. package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
  226. package/dist/integrations/harnesses/claude/index.js +2 -0
  227. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  228. package/dist/integrations/harnesses/index.js +2 -3
  229. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  230. package/dist/integrations/harnesses/opencode/index.js +2 -0
  231. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  232. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  233. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
  234. package/dist/integrations/harnesses/types.js +1 -0
  235. package/dist/integrations/session-logs/index.js +16 -0
  236. package/dist/llm/call-ai.js +2 -2
  237. package/dist/llm/client.js +57 -15
  238. package/dist/llm/embedder.js +67 -4
  239. package/dist/llm/embedders/cache.js +3 -1
  240. package/dist/llm/embedders/deterministic.js +66 -0
  241. package/dist/llm/embedders/local.js +73 -3
  242. package/dist/llm/feature-gate.js +16 -15
  243. package/dist/llm/graph-extract.js +67 -44
  244. package/dist/llm/memory-infer-impl.js +138 -0
  245. package/dist/llm/memory-infer.js +1 -127
  246. package/dist/llm/metadata-enhance.js +44 -31
  247. package/dist/llm/structured-call.js +49 -0
  248. package/dist/migrate-storage-node.mjs +8 -0
  249. package/dist/output/context.js +5 -5
  250. package/dist/output/renderers.js +85 -14
  251. package/dist/output/shapes/curate.js +14 -2
  252. package/dist/output/shapes/helpers.js +0 -3
  253. package/dist/output/shapes/passthrough.js +2 -1
  254. package/dist/output/text/helpers.js +29 -1
  255. package/dist/output/text/workflow.js +1 -0
  256. package/dist/registry/providers/skills-sh.js +21 -147
  257. package/dist/registry/providers/static-index.js +15 -157
  258. package/dist/registry/resolve.js +27 -9
  259. package/dist/runtime.js +25 -1
  260. package/dist/scripts/migrate-storage.js +2718 -2354
  261. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
  262. package/dist/setup/detect.js +9 -0
  263. package/dist/setup/legacy-config.js +106 -0
  264. package/dist/setup/prompt.js +57 -0
  265. package/dist/setup/providers.js +14 -0
  266. package/dist/setup/registry-stash-loader.js +12 -0
  267. package/dist/setup/semantic-assets.js +124 -0
  268. package/dist/setup/setup.js +52 -1614
  269. package/dist/setup/steps/connection.js +734 -0
  270. package/dist/setup/steps/output.js +31 -0
  271. package/dist/setup/steps/platforms.js +124 -0
  272. package/dist/setup/steps/semantic.js +27 -0
  273. package/dist/setup/steps/sources.js +222 -0
  274. package/dist/setup/steps/stashdir.js +42 -0
  275. package/dist/setup/steps/tasks.js +152 -0
  276. package/dist/sources/include.js +6 -2
  277. package/dist/sources/providers/filesystem.js +0 -1
  278. package/dist/sources/providers/git-install.js +210 -0
  279. package/dist/sources/providers/git-provider.js +234 -0
  280. package/dist/sources/providers/git-stash.js +248 -0
  281. package/dist/sources/providers/git.js +10 -661
  282. package/dist/sources/providers/npm.js +2 -6
  283. package/dist/sources/providers/provider-utils.js +13 -7
  284. package/dist/sources/providers/sync-from-ref.js +9 -1
  285. package/dist/sources/providers/tar-utils.js +16 -8
  286. package/dist/sources/providers/website.js +9 -5
  287. package/dist/sources/website-ingest.js +187 -29
  288. package/dist/sources/wiki-fetchers/registry.js +53 -0
  289. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  290. package/dist/storage/database.js +45 -10
  291. package/dist/storage/managed-db.js +82 -0
  292. package/dist/storage/repositories/canaries-repository.js +107 -0
  293. package/dist/storage/repositories/consolidation-repository.js +38 -0
  294. package/dist/storage/repositories/embeddings-repository.js +72 -0
  295. package/dist/storage/repositories/events-repository.js +187 -0
  296. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  297. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  298. package/dist/storage/repositories/index-db.js +14 -8
  299. package/dist/storage/repositories/proposals-repository.js +220 -0
  300. package/dist/storage/repositories/recombine-repository.js +213 -0
  301. package/dist/storage/repositories/registry-cache.js +93 -0
  302. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  303. package/dist/storage/repositories/task-history-repository.js +93 -0
  304. package/dist/storage/sqlite-pragmas.js +146 -0
  305. package/dist/tasks/backends/cron.js +1 -1
  306. package/dist/tasks/backends/index.js +9 -0
  307. package/dist/tasks/backends/launchd.js +1 -1
  308. package/dist/tasks/backends/schtasks.js +1 -1
  309. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  310. package/dist/tasks/runner.js +15 -13
  311. package/dist/text-import-hook.mjs +0 -0
  312. package/dist/wiki/wiki.js +52 -11
  313. package/dist/workflows/cli.js +1 -0
  314. package/dist/workflows/db.js +3 -4
  315. package/dist/workflows/runtime/runs.js +43 -118
  316. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  317. package/dist/workflows/validate-summary.js +2 -7
  318. package/docs/README.md +69 -18
  319. package/docs/data-and-telemetry.md +5 -4
  320. package/docs/migration/release-notes/0.7.0.md +1 -1
  321. package/docs/migration/release-notes/0.9.0.md +39 -0
  322. package/package.json +10 -10
  323. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  324. package/dist/commands/db-cli.js +0 -23
  325. package/dist/indexer/db/db-backup.js +0 -376
  326. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -17,12 +17,13 @@
17
17
  --surface2:#21262d;
18
18
  --border: #30363d;
19
19
  --text: #e6edf3;
20
- --muted: #8b949e;
20
+ --muted: #9ea8b5;
21
21
  --accent: #58a6ff;
22
22
  --green: #3fb950;
23
23
  --yellow: #d29922;
24
24
  --red: #f85149;
25
25
  --purple: #bc8cff;
26
+ --neutral: #8b949e;
26
27
  }
27
28
 
28
29
  body {
@@ -43,7 +44,8 @@
43
44
  gap: 12px;
44
45
  flex-wrap: wrap;
45
46
  }
46
- header .logo { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; flex-shrink: 0; }
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; }
47
49
  header .meta { color: var(--muted); font-size: 13px; min-width: 0; }
48
50
  header .meta .title { font-weight: 600; color: var(--text); }
49
51
  header .badges { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
@@ -73,6 +75,8 @@
73
75
  h2 { font-size: 16px; font-weight: 600; margin-bottom: 16px; }
74
76
  h3 { font-size: 13px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
75
77
 
78
+ abbr[title] { text-decoration: underline dotted; cursor: help; text-underline-offset: 2px; }
79
+
76
80
  /* ── Executive summary (Discord card) ───────────────────────────────── */
77
81
  .exec {
78
82
  background: linear-gradient(180deg, var(--surface) 0%, #12161c 100%);
@@ -80,6 +84,17 @@
80
84
  border-radius: 10px; padding: 18px 22px; margin-bottom: 28px;
81
85
  }
82
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; }
83
98
  .exec .exec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
84
99
  @media (max-width: 900px) { .exec .exec-grid { grid-template-columns: 1fr; gap: 16px; } }
85
100
  .exec . blk-title, .exec h4 {
@@ -110,10 +125,21 @@
110
125
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
111
126
  gap: 10px; margin-bottom: 28px;
112
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; }
113
139
  @media (max-width: 600px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 20px; } }
114
140
  .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; }
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); }
117
143
  .kpi-card .sub { font-size: 11px; color: var(--muted); margin-top: 4px; }
118
144
  @media (max-width: 600px) { .kpi-card .value { font-size: 22px; } }
119
145
  .kpi-card.green .value { color: var(--green); }
@@ -121,6 +147,10 @@
121
147
  .kpi-card.red .value { color: var(--red); }
122
148
  .kpi-card.blue .value { color: var(--accent); }
123
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); }
124
154
 
125
155
  /* ── Chart panels ───────────────────────────────────────────────────── */
126
156
  .charts-grid {
@@ -132,11 +162,20 @@
132
162
  .charts-grid { grid-template-columns: 1fr; gap: 10px; }
133
163
  .chart-panel.full-width { grid-column: auto; }
134
164
  }
135
- .chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
165
+ .chart-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; position: relative; }
136
166
  .chart-panel.full-width { grid-column: 1 / -1; }
137
167
  .chart-panel .ec { width: 100%; height: 240px; }
138
168
  .chart-panel.full-width .ec { height: 280px; }
169
+ .chart-panel .ec.tall { height: 300px; }
170
+ .chart-panel.full-width .ec.tall { height: 320px; }
139
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; }
140
179
 
141
180
  /* ── Tables ─────────────────────────────────────────────────────────── */
142
181
  .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@@ -160,8 +199,12 @@
160
199
  .tag { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
161
200
  .tag-extract { background: rgba(88,166,255,0.15); color: var(--accent); }
162
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); }
163
206
 
164
- /* ── Advisories ─────────────────────────────────────────────────────── */
207
+ /* ── Advisories / Action items ──────────────────────────────────────── */
165
208
  .advisory-list { display: flex; flex-direction: column; gap: 10px; }
166
209
  .advisory {
167
210
  display: flex; gap: 12px;
@@ -173,6 +216,7 @@
173
216
  .advisory-icon { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
174
217
  .advisory-body .title { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
175
218
  .advisory-body .desc { font-size: 12px; color: var(--muted); word-break: break-word; }
219
+ .advisory-body .remedy { font-size: 12px; margin-top: 6px; }
176
220
 
177
221
  /* ── Trend indicators ───────────────────────────────────────────────── */
178
222
  .trend { font-size: 12px; }
@@ -193,13 +237,6 @@
193
237
  font-size: 11px; background: var(--surface2);
194
238
  padding: 2px 6px; border-radius: 4px; color: var(--accent); word-break: break-all;
195
239
  }
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
240
  /* ── Footer ─────────────────────────────────────────────────────────── */
204
241
  footer {
205
242
  border-top: 1px solid var(--border); padding: 14px 32px;
@@ -212,7 +249,7 @@
212
249
  <body>
213
250
 
214
251
  <header>
215
- <div class="logo">AKM</div>
252
+ <div class="logo">AKM<span class="logo-version">v%%AKM_VERSION%%</span></div>
216
253
  <div class="meta">
217
254
  <div class="title">Improve Health Report</div>
218
255
  <div>Window: %%WINDOW%% &nbsp;·&nbsp; %%SINCE_HUMAN%% &nbsp;·&nbsp; %%RUN_COUNT%% runs</div>
@@ -229,75 +266,104 @@
229
266
  %%EXEC_SUMMARY_HTML%%
230
267
  </section>
231
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
+
232
277
  <!-- KPI Row -->
233
278
  <div class="kpi-grid">
234
279
  %%KPI_CARDS_HTML%%
235
280
  </div>
236
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
+
237
316
  <!-- Charts -->
238
317
  <div class="section">
239
318
  <h2>Run Trend Lines (%%WINDOW%%)</h2>
240
319
  <div class="charts-grid">
241
320
 
242
321
  <div class="chart-panel full-width">
243
- <h3>Wall Time per Run (min) — with rolling average</h3>
244
- <div id="chartWallTime" class="ec"></div>
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>
245
325
  </div>
246
326
 
247
327
  <div class="chart-panel full-width">
248
328
  <h3>Per-Phase Wall Time Decomposition (stacked, min)</h3>
249
- <div id="chartPhases" class="ec"></div>
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>
250
337
  </div>
251
338
 
252
339
  <div class="chart-panel">
253
340
  <h3>Stash Growth (derived vs eligible)</h3>
254
- <div id="chartStash" class="ec"></div>
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>
255
343
  </div>
256
344
 
257
345
  <div class="chart-panel">
258
- <h3>Consolidation Output (promoted / merged / deleted)</h3>
259
- <div id="chartConsOutput" class="ec"></div>
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>
260
349
  </div>
261
350
 
262
351
  <div class="chart-panel">
263
352
  <h3>Success / Failures per Run</h3>
264
- <div id="chartSuccess" class="ec"></div>
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>
265
355
  </div>
266
356
 
267
357
  <div class="chart-panel">
268
358
  <h3>Lint Flagged vs Fixed</h3>
269
- <div id="chartLint" class="ec"></div>
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>
270
361
  </div>
271
362
 
272
363
  <div class="chart-panel full-width">
273
364
  <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>
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>
301
367
  </div>
302
368
 
303
369
  </div>
@@ -318,22 +384,14 @@
318
384
  </div>
319
385
  </div>
320
386
 
321
- <!-- What to watch -->
387
+ <!-- Recent runs -->
322
388
  <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>
389
+ <h2>Recent Runs</h2>
332
390
  <div class="summary-table">
333
391
  <div class="table-wrap">
334
392
  <table>
335
393
  <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>
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>
337
395
  </thead>
338
396
  <tbody id="lastRunsTable"></tbody>
339
397
  </table>
@@ -341,47 +399,73 @@
341
399
  </div>
342
400
  </div>
343
401
 
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
402
  </main>
353
403
 
354
404
  <footer>
355
- <span>AKM Health Report &nbsp;·&nbsp; Window: %%WINDOW%% &nbsp;·&nbsp; %%REPORT_TITLE%%</span>
356
- <span>Generated %%GENERATED_AT%%</span>
405
+ <span>AKM v%%AKM_VERSION%% &nbsp;·&nbsp; Window: %%WINDOW%% &nbsp;·&nbsp; %%REPORT_TITLE%%</span>
406
+ <span>Generated <time data-iso="%%GENERATED_AT%%">%%GENERATED_AT%%</time></span>
357
407
  </footer>
358
408
 
359
409
  <script>
360
410
  // ── Injected run data ─────────────────────────────────────────────────────────
361
411
  %%RUNS_JS_CONST%%
362
412
  const DISTILL_REASONS = %%DISTILL_REASONS_JSON%%;
413
+ const LLM_BY_STAGE = %%LLM_BY_STAGE_JSON%%;
363
414
 
364
415
  // ── Theme constants ───────────────────────────────────────────────────────────
365
416
  const C = {
366
417
  bg: '#161b22', surface2: '#21262d', border: '#30363d',
367
- text: '#e6edf3', muted: '#8b949e',
418
+ text: '#e6edf3', muted: '#9ea8b5',
368
419
  accent: '#58a6ff', green: '#3fb950', yellow: '#d29922',
369
420
  red: '#f85149', purple: '#bc8cff',
370
421
  };
371
422
  const SERIES_PALETTE = [C.accent, C.green, C.yellow, C.purple, C.red, '#39c5cf', '#db61a2', '#e3b341'];
372
423
 
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
424
  const toMin = ms => ms ? +(ms / 60000).toFixed(2) : 0;
384
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
+
385
469
  // Centered rolling average (window = ~10% of runs, min 3) — deterministic.
386
470
  function rollingAvg(arr) {
387
471
  const n = arr.length;
@@ -402,16 +486,20 @@ const baseAxis = {
402
486
  splitLine: { lineStyle: { color: 'rgba(48,54,61,0.5)' } },
403
487
  };
404
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 };
405
490
  const baseTooltip = {
406
491
  trigger: 'axis',
407
492
  backgroundColor: C.surface2, borderColor: C.border, borderWidth: 1,
408
493
  textStyle: { color: C.text, fontSize: 12 },
409
494
  };
410
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 } }];
411
497
 
412
498
  function mkChart(id, option) {
413
499
  const el = document.getElementById(id);
414
500
  if (!el) return;
501
+ const prior = echarts.getInstanceByDom(el);
502
+ if (prior) prior.dispose();
415
503
  const chart = echarts.init(el, null, { renderer: 'canvas' });
416
504
  // animation off → deterministic first paint & no per-frame randomness
417
505
  option.animation = false;
@@ -422,11 +510,21 @@ function mkChart(id, option) {
422
510
  }
423
511
 
424
512
  // ── 1. Wall time per run + rolling avg ────────────────────────────────────────
425
- const wall = RUNS.map(r => toMin(r.wallTimeMs));
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);
426
523
  mkChart('chartWallTime', {
427
524
  tooltip: baseTooltip,
428
- legend: { ...baseLegend, data: ['Wall time', 'Rolling avg'] },
429
- grid: baseGrid,
525
+ legend: { ...baseLegend, data: ['Wall time', 'Rolling avg', 'Failed run'] },
526
+ grid: denseGrid,
527
+ dataZoom: denseZoom,
430
528
  xAxis: { type: 'category', data: xLabels, ...baseAxis },
431
529
  yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
432
530
  series: [
@@ -434,21 +532,24 @@ mkChart('chartWallTime', {
434
532
  lineStyle: { color: C.accent, width: 1.5 }, areaStyle: { color: 'rgba(88,166,255,0.08)' } },
435
533
  { name: 'Rolling avg', type: 'line', data: rollingAvg(wall), showSymbol: false, smooth: true,
436
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 },
437
537
  ],
438
538
  });
439
539
 
440
540
  // ── 2. Per-phase wall time decomposition (stacked area) ──────────────────────
441
541
  mkChart('chartPhases', {
442
542
  tooltip: baseTooltip,
443
- legend: { ...baseLegend, data: ['Consolidation', 'Memory inference', 'Graph extraction', 'Other / LLM'] },
444
- grid: baseGrid,
543
+ legend: { ...baseLegend, data: ['Consolidation', 'Memory inference', 'Graph extraction', 'Unattributed'] },
544
+ grid: denseGrid,
545
+ dataZoom: denseZoom,
445
546
  xAxis: { type: 'category', data: xLabels, ...baseAxis },
446
547
  yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
447
548
  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)) },
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)) },
452
553
  ],
453
554
  });
454
555
 
@@ -456,12 +557,13 @@ mkChart('chartPhases', {
456
557
  mkChart('chartStash', {
457
558
  tooltip: baseTooltip,
458
559
  legend: { ...baseLegend, data: ['Derived', 'Eligible'] },
459
- grid: baseGrid,
560
+ grid: denseGrid,
561
+ dataZoom: denseZoom,
460
562
  xAxis: { type: 'category', data: xLabels, ...baseAxis },
461
563
  yAxis: { type: 'value', ...baseAxis },
462
564
  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) },
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) },
465
567
  ],
466
568
  });
467
569
 
@@ -469,24 +571,32 @@ mkChart('chartStash', {
469
571
  mkChart('chartConsOutput', {
470
572
  tooltip: baseTooltip,
471
573
  legend: { ...baseLegend, data: ['Promoted', 'Merged', 'Deleted'] },
472
- grid: baseGrid,
574
+ grid: denseGrid,
575
+ dataZoom: denseZoom,
473
576
  xAxis: { type: 'category', data: xLabels, ...baseAxis },
474
- yAxis: { type: 'value', ...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
+ ],
475
584
  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) },
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) },
479
588
  ],
480
589
  });
481
590
 
482
- // ── 5. Success / failures (failed runs as distinct red scatter) ──────────────
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);
483
593
  mkChart('chartSuccess', {
484
594
  tooltip: {
485
595
  ...baseTooltip,
486
596
  formatter: p => {
487
597
  const arr = Array.isArray(p) ? p : [p];
488
598
  const i = arr[0].dataIndex;
489
- return `${xLabels[i]}<br/>${RUNS[i].ok ? '✅ ok' : '❌ failed'} · ${toMin(RUNS[i].wallTimeMs)}m`;
599
+ return `${xLabels[i]}<br/>${rs[i].ok ? '✅ ok' : '❌ failed'} · ${toMin(rs[i].wallTimeMs)}m`;
490
600
  },
491
601
  },
492
602
  legend: { ...baseLegend, data: ['Wall time (ok)', 'Failed run'] },
@@ -495,10 +605,10 @@ mkChart('chartSuccess', {
495
605
  yAxis: { type: 'value', name: 'min', nameTextStyle: { color: C.muted }, ...baseAxis },
496
606
  series: [
497
607
  { 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,
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,
500
610
  itemStyle: { color: C.red },
501
- data: RUNS.map((r, i) => r.ok ? null : [i, toMin(r.wallTimeMs)]) },
611
+ data: failScatter },
502
612
  ],
503
613
  });
504
614
 
@@ -510,8 +620,8 @@ mkChart('chartLint', {
510
620
  xAxis: { type: 'category', data: xLabels, ...baseAxis },
511
621
  yAxis: { type: 'value', ...baseAxis },
512
622
  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) },
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) },
515
625
  ],
516
626
  });
517
627
 
@@ -521,29 +631,64 @@ const distillSeries = DISTILL_REASONS.map((reason, idx) => ({
521
631
  type: 'bar',
522
632
  stack: 'd',
523
633
  itemStyle: { color: SERIES_PALETTE[idx % SERIES_PALETTE.length] },
524
- data: RUNS.map(r => (r.distillByReason && r.distillByReason[reason]) || 0),
634
+ data: rs.map(r => (r.distillByReason && r.distillByReason[reason]) || 0),
525
635
  }));
636
+ const distillEmpty = !rs.length || !DISTILL_REASONS.length;
637
+ toggleEmpty('emptyDistill', distillEmpty);
526
638
  mkChart('chartDistill', {
527
639
  tooltip: baseTooltip,
528
640
  legend: { ...baseLegend, data: DISTILL_REASONS },
529
- grid: { ...baseGrid, top: 40 },
641
+ grid: { ...denseGrid, top: 40 },
642
+ dataZoom: denseZoom,
530
643
  xAxis: { type: 'category', data: xLabels, ...baseAxis },
531
644
  yAxis: { type: 'value', ...baseAxis },
532
645
  series: distillSeries.length ? distillSeries
533
- : [{ name: 'none', type: 'bar', data: RUNS.map(() => 0) }],
646
+ : [{ name: 'none', type: 'bar', data: rs.map(() => 0) }],
534
647
  });
648
+ }
535
649
 
536
- // ── Last 10 runs table ────────────────────────────────────────────────────────
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) {
537
677
  const tbody = document.getElementById('lastRunsTable');
538
- RUNS.slice(-10).forEach(r => {
678
+ tbody.innerHTML = '';
679
+ [...rs].reverse().forEach(r => {
539
680
  const tr = document.createElement('tr');
540
- const ts = new Date(r.startedAt).toISOString().replace('T', ' ').slice(0, 16);
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())}`;
541
684
  const dur = r.wallTimeMs ? (r.wallTimeMs / 60000).toFixed(1) + 'm' : '—';
685
+ const task = r.taskId || 'manual';
542
686
  const badge = r.ok
543
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>&nbsp;ok</span>'
544
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>&nbsp;failed</span>';
545
689
  tr.innerHTML = `
546
690
  <td style="font-family:monospace;font-size:12px;">${ts}</td>
691
+ <td><code>${task}</code></td>
547
692
  <td>${dur}</td>
548
693
  <td style="color:var(--accent);font-weight:600;">${r.promoted || 0}</td>
549
694
  <td>${r.merged || 0}</td>
@@ -555,6 +700,31 @@ RUNS.slice(-10).forEach(r => {
555
700
  `;
556
701
  tbody.appendChild(tr);
557
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
+ })();
558
728
  </script>
559
729
  </body>
560
730
  </html>