akm-cli 0.8.6 → 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 +442 -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} +63 -38
  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
@@ -1,17 +1,17 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { spawnSync } from "node:child_process";
5
4
  import fs from "node:fs";
6
- import { loadConfig } from "../core/config";
7
- import { ConfigError, UsageError } from "../core/errors";
8
- import { appendEvent, readEvents } from "../core/events";
9
- import { getStateDbPathInDataDir } from "../core/paths";
10
- import { openStateDatabase, queryTaskHistory } from "../core/state-db";
11
- import { parseSinceToIso } from "../core/time";
12
- import { readSemanticStatus } from "../indexer/semantic-status";
13
- import { detectAgentCliProfiles, requireAgentProfile } from "../integrations/agent";
14
- import { getExecutionLogCandidates } from "../integrations/session-logs";
5
+ import { ConfigError, UsageError } from "../core/errors.js";
6
+ import { appendEvent, readEvents } from "../core/events.js";
7
+ import { buildTaskRunId, getLoggedRunIds, openLogsDatabase } from "../core/logs-db.js";
8
+ import { getStateDbPathInDataDir } from "../core/paths.js";
9
+ import { listExistingTableNames, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
10
+ import { parseSinceToIso } from "../core/time.js";
11
+ import { readSemanticStatus } from "../indexer/search/semantic-status.js";
12
+ import { getExecutionLogCandidates } from "../integrations/session-logs/index.js";
13
+ import { LLM_USAGE_EVENT } from "../llm/usage-persist.js";
14
+ import { HEALTH_CHECKS } from "./health/checks.js";
15
15
  const DEFAULT_SINCE_MS = 24 * 60 * 60 * 1000;
16
16
  const IMPROVE_COMPLETED_EVENT = "improve_completed";
17
17
  const HEALTH_PROBE_EVENT = "health_probe";
@@ -104,6 +104,7 @@ function createUnknownImproveMetrics() {
104
104
  ran: false,
105
105
  considered: 0,
106
106
  cacheHits: 0,
107
+ retryAttempts: 0,
107
108
  freshAttempts: 0,
108
109
  splitParents: 0,
109
110
  written: 0,
@@ -111,6 +112,7 @@ function createUnknownImproveMetrics() {
111
112
  skippedChildExists: 0,
112
113
  skippedAborted: 0,
113
114
  unaccounted: 0,
115
+ htmlErrorCount: 0,
114
116
  yieldEligibleRuns: 0,
115
117
  yieldEligibleConsidered: 0,
116
118
  yieldEligibleWritten: 0,
@@ -128,6 +130,8 @@ function createUnknownImproveMetrics() {
128
130
  cacheHitRate: 0,
129
131
  truncations: 0,
130
132
  failures: 0,
133
+ htmlErrors: 0,
134
+ retryAttempts: 0,
131
135
  durationMs: 0,
132
136
  },
133
137
  sessionExtraction: {
@@ -337,18 +341,26 @@ function projectRunMetrics(result) {
337
341
  metrics.consolidation.mergedSecondaries += toFiniteNumber(consolidation.mergedSecondaries);
338
342
  metrics.consolidation.failedChunkMemories += toFiniteNumber(consolidation.failedChunkMemories);
339
343
  // Structured emitter (new on this branch): consolidate.ts now pushes
340
- // `{op, ref, reason}` entries to `skipReasons` for every deterministic
341
- // post-LLM rejection. Pre-fix envelopes have neither field, so be
342
- // defensive.
344
+ // per-ref grouped `{ref, skips: [{op, reason}]}` entries to `skipReasons`
345
+ // for every deterministic post-LLM rejection. Each ref appears once but
346
+ // may carry multiple skips; aggregate every reason. Pre-fix envelopes have
347
+ // neither field, so be defensive.
343
348
  const skipReasons = consolidation.skipReasons;
344
349
  if (Array.isArray(skipReasons)) {
345
350
  for (const entry of skipReasons) {
346
351
  if (!entry || typeof entry !== "object")
347
352
  continue;
348
- const reason = entry.reason;
349
- if (typeof reason !== "string" || !reason.trim())
353
+ const skips = entry.skips;
354
+ if (!Array.isArray(skips))
350
355
  continue;
351
- metrics.consolidation.skipReasons[reason] = (metrics.consolidation.skipReasons[reason] ?? 0) + 1;
356
+ for (const skip of skips) {
357
+ if (!skip || typeof skip !== "object")
358
+ continue;
359
+ const reason = skip.reason;
360
+ if (typeof reason !== "string" || !reason.trim())
361
+ continue;
362
+ metrics.consolidation.skipReasons[reason] = (metrics.consolidation.skipReasons[reason] ?? 0) + 1;
363
+ }
352
364
  }
353
365
  }
354
366
  }
@@ -358,12 +370,14 @@ function projectRunMetrics(result) {
358
370
  const writtenFacts = toFiniteNumber(memoryInference.writtenFacts);
359
371
  metrics.memoryInference.considered += considered;
360
372
  metrics.memoryInference.cacheHits += toFiniteNumber(memoryInference.cacheHits);
373
+ metrics.memoryInference.retryAttempts += toFiniteNumber(memoryInference.retryAttempts);
361
374
  metrics.memoryInference.splitParents += toFiniteNumber(memoryInference.splitParents);
362
375
  metrics.memoryInference.written += writtenFacts;
363
376
  metrics.memoryInference.skippedNoFacts += toFiniteNumber(memoryInference.skippedNoFacts);
364
377
  metrics.memoryInference.skippedChildExists += toFiniteNumber(memoryInference.skippedChildExists);
365
378
  metrics.memoryInference.skippedAborted += toFiniteNumber(memoryInference.skippedAborted);
366
379
  metrics.memoryInference.unaccounted += toFiniteNumber(memoryInference.unaccounted);
380
+ metrics.memoryInference.htmlErrorCount += toFiniteNumber(memoryInference.htmlErrorCount);
367
381
  // Yield-rate gating: pre-cache-feature envelopes lack the `cacheHits`
368
382
  // field entirely. Treating their `considered` as freshAttempts (since
369
383
  // cacheHits=0) is mathematically tempting but operationally wrong —
@@ -391,6 +405,8 @@ function projectRunMetrics(result) {
391
405
  metrics.graphExtraction.cacheMisses += toFiniteNumber(telemetry.cacheMisses);
392
406
  metrics.graphExtraction.truncations += toFiniteNumber(telemetry.truncationCount);
393
407
  metrics.graphExtraction.failures += toFiniteNumber(telemetry.failureCount);
408
+ metrics.graphExtraction.htmlErrors += toFiniteNumber(telemetry.htmlErrorCount);
409
+ metrics.graphExtraction.retryAttempts += toFiniteNumber(telemetry.retryAttempts);
394
410
  }
395
411
  }
396
412
  metrics.graphExtraction.durationMs += toFiniteNumber(result.graphExtractionDurationMs);
@@ -520,6 +536,7 @@ function mergeImproveMetrics(dst, src) {
520
536
  dst.memoryInference.skippedChildExists += src.memoryInference.skippedChildExists;
521
537
  dst.memoryInference.skippedAborted += src.memoryInference.skippedAborted;
522
538
  dst.memoryInference.unaccounted += src.memoryInference.unaccounted;
539
+ dst.memoryInference.htmlErrorCount += src.memoryInference.htmlErrorCount;
523
540
  dst.memoryInference.yieldEligibleRuns += src.memoryInference.yieldEligibleRuns;
524
541
  dst.memoryInference.yieldEligibleConsidered += src.memoryInference.yieldEligibleConsidered;
525
542
  dst.memoryInference.yieldEligibleWritten += src.memoryInference.yieldEligibleWritten;
@@ -531,6 +548,7 @@ function mergeImproveMetrics(dst, src) {
531
548
  dst.graphExtraction.cacheMisses += src.graphExtraction.cacheMisses;
532
549
  dst.graphExtraction.truncations += src.graphExtraction.truncations;
533
550
  dst.graphExtraction.failures += src.graphExtraction.failures;
551
+ dst.graphExtraction.htmlErrors += src.graphExtraction.htmlErrors;
534
552
  dst.graphExtraction.durationMs += src.graphExtraction.durationMs;
535
553
  dst.sessionExtraction.sessionsScanned += src.sessionExtraction.sessionsScanned;
536
554
  dst.sessionExtraction.sessionsExtracted += src.sessionExtraction.sessionsExtracted;
@@ -539,15 +557,9 @@ function mergeImproveMetrics(dst, src) {
539
557
  dst.sessionExtraction.warnings += src.sessionExtraction.warnings;
540
558
  dst.sessionExtraction.durationMs += src.sessionExtraction.durationMs;
541
559
  }
542
- function loadImproveRunRows(db, since, until) {
543
- const sql = until
544
- ? "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND started_at < ? AND dry_run = 0 ORDER BY started_at DESC"
545
- : "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND dry_run = 0 ORDER BY started_at DESC";
546
- return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
547
- }
548
560
  function summarizeImproveRuns(db, since, until) {
549
561
  const accum = createUnknownImproveMetrics();
550
- const rows = loadImproveRunRows(db, since, until);
562
+ const rows = queryImproveRuns(db, since, until);
551
563
  // Per-phase wall-time samples. Each entry is one envelope's durationMs for
552
564
  // that phase. Phases that did not run on a given envelope are simply
553
565
  // omitted (NOT counted as 0) so the median/p95 reflect actual phase work.
@@ -666,10 +678,7 @@ function loadTaskIntervals(db, since, until) {
666
678
  const untilMs = until ? new Date(until).getTime() : Number.POSITIVE_INFINITY;
667
679
  const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
668
680
  const widenedUntil = Number.isFinite(untilMs) ? new Date(untilMs + 5 * 60 * 1000).toISOString() : undefined;
669
- const sql = widenedUntil
670
- ? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at"
671
- : "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
672
- const rows = (widenedUntil ? db.prepare(sql).all(widenedSince, widenedUntil) : db.prepare(sql).all(widenedSince));
681
+ const rows = queryCompletedTaskIntervals(db, widenedSince, widenedUntil);
673
682
  const intervals = [];
674
683
  for (const row of rows) {
675
684
  const startMs = new Date(row.started_at).getTime();
@@ -702,18 +711,27 @@ function findContainingTaskInterval(timestampMs, intervals) {
702
711
  return undefined;
703
712
  }
704
713
  function buildPerRunSummaries(db, since, until) {
705
- const rows = loadImproveRunRows(db, since, until);
714
+ const rows = queryImproveRuns(db, since, until);
706
715
  const taskIntervals = loadTaskIntervals(db, since, until);
707
716
  const summaries = [];
708
717
  for (const row of rows) {
709
718
  const startMs = new Date(row.started_at).getTime();
710
719
  const endMs = new Date(row.completed_at).getTime();
711
- // Prefer the task_history interval (which has distinct start/end timestamps).
712
- // Fall back to the improve_runs row's own delta (usually 0 because
713
- // recordImproveRun writes started_at == completed_at == end-of-run timestamp).
714
- const fallbackWallMs = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs ? endMs - startMs : 0;
715
- const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
716
- const wallTimeMs = interval?.durationMs ?? fallbackWallMs;
720
+ // Prefer the improve_runs row's own (completed_at - started_at) delta:
721
+ // recordImproveRun now persists distinct start/end timestamps, so the
722
+ // row's own delta is the authoritative per-run wall time even for
723
+ // manually-invoked `akm improve` runs with no enclosing task_history.
724
+ // Only fall back to the task_history containing-interval join for legacy/
725
+ // backfill rows where started_at == completed_at (row delta is 0).
726
+ const hasRowDelta = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs;
727
+ let wallTimeMs;
728
+ if (hasRowDelta) {
729
+ wallTimeMs = endMs - startMs;
730
+ }
731
+ else {
732
+ const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
733
+ wallTimeMs = interval?.durationMs ?? 0;
734
+ }
717
735
  summaries.push(projectImproveRunSummary(row, wallTimeMs));
718
736
  }
719
737
  return summaries;
@@ -759,108 +777,11 @@ function probeStateDbRoundTrip(stateDbPath) {
759
777
  }
760
778
  return { ok: true, durationMs };
761
779
  }
762
- function runAgentProbe() {
763
- const config = loadConfig();
764
- // v2: check profiles.agent first
765
- if (config.profiles?.agent) {
766
- const defaultName = config.defaults?.agent;
767
- const profileCount = Object.keys(config.profiles.agent).length;
768
- if (profileCount === 0) {
769
- return {
770
- name: "agent-profile",
771
- kind: "deterministic",
772
- status: "unknown",
773
- confidence: "high",
774
- message: "No agent profiles configured in profiles.agent.",
775
- };
776
- }
777
- const profileName = defaultName ?? Object.keys(config.profiles.agent)[0];
778
- const profile = config.profiles.agent[profileName];
779
- return {
780
- name: "agent-profile",
781
- kind: "deterministic",
782
- status: "pass",
783
- confidence: "high",
784
- message: `v2 agent profile "${profileName}" configured (platform: ${profile?.platform ?? "unknown"}).`,
785
- evidence: { profile: profileName, platform: profile?.platform, profileCount },
786
- };
787
- }
788
- if (!config.profiles?.agent && !config.defaults?.agent) {
789
- return {
790
- name: "agent-profile",
791
- kind: "deterministic",
792
- status: "unknown",
793
- confidence: "high",
794
- message: "No agent config present.",
795
- };
796
- }
797
- let profile;
798
- try {
799
- profile = requireAgentProfile(config);
800
- }
801
- catch (error) {
802
- return {
803
- name: "agent-profile",
804
- kind: "deterministic",
805
- status: "warn",
806
- confidence: "high",
807
- message: error instanceof Error ? error.message : String(error),
808
- };
809
- }
810
- if (profile.sdkMode === true) {
811
- return {
812
- name: "agent-profile",
813
- kind: "deterministic",
814
- status: profile.model ? "pass" : "warn",
815
- confidence: "high",
816
- message: profile.model
817
- ? `SDK mode profile "${profile.name}" is configured.`
818
- : `SDK mode profile "${profile.name}" has no explicit model.`,
819
- evidence: { profile: profile.name, sdkMode: true, model: profile.model ?? null },
820
- };
821
- }
822
- const detections = detectAgentCliProfiles(config);
823
- const detection = detections.find((entry) => entry.name === profile.name);
824
- if (!detection?.available) {
825
- return {
826
- name: "agent-profile",
827
- kind: "deterministic",
828
- status: "fail",
829
- confidence: "high",
830
- message: `Default agent profile "${profile.name}" is not available on PATH.`,
831
- evidence: { profile: profile.name, bin: profile.bin },
832
- };
833
- }
834
- const version = spawnSync(profile.bin, ["--version"], { encoding: "utf8", timeout: 5_000 });
835
- if ((version.status ?? 1) !== 0) {
836
- return {
837
- name: "agent-profile",
838
- kind: "deterministic",
839
- status: "warn",
840
- confidence: "medium",
841
- message: `Agent binary "${profile.bin}" was found but \`--version\` failed.`,
842
- evidence: {
843
- profile: profile.name,
844
- bin: profile.bin,
845
- exitCode: version.status ?? null,
846
- stderr: (version.stderr ?? "").trim(),
847
- },
848
- };
849
- }
850
- return {
851
- name: "agent-profile",
852
- kind: "deterministic",
853
- status: "pass",
854
- confidence: "high",
855
- message: `Agent profile "${profile.name}" is available.`,
856
- evidence: { profile: profile.name, bin: profile.bin, version: (version.stdout ?? "").trim() },
857
- };
858
- }
859
780
  /**
860
781
  * Parse a `--window-compare <duration>` shorthand into two adjacent windows
861
782
  * (current, prior). Duration syntax matches {@link parseHealthSince}.
862
783
  */
863
- function resolveWindowCompare(duration) {
784
+ function resolveWindowCompare(duration, now = () => Date.now()) {
864
785
  const trimmed = duration.trim();
865
786
  const durationMatch = trimmed.match(/^(\d+)([dhm])$/i);
866
787
  if (!durationMatch) {
@@ -873,10 +794,10 @@ function resolveWindowCompare(duration) {
873
794
  }
874
795
  const multiplier = unit === "h" ? 60 * 60 * 1000 : unit === "m" ? 60 * 1000 : 24 * 60 * 60 * 1000;
875
796
  const ms = amount * multiplier;
876
- const now = Date.now();
877
- const currentSince = new Date(now - ms).toISOString();
878
- const currentUntil = new Date(now).toISOString();
879
- const priorSince = new Date(now - 2 * ms).toISOString();
797
+ const nowMs = now();
798
+ const currentSince = new Date(nowMs - ms).toISOString();
799
+ const currentUntil = new Date(nowMs).toISOString();
800
+ const priorSince = new Date(nowMs - 2 * ms).toISOString();
880
801
  const priorUntil = currentSince;
881
802
  return [
882
803
  { name: "current", since: currentSince, until: currentUntil },
@@ -924,8 +845,10 @@ const INTERESTING_DELTA_PATHS = [
924
845
  "improve.memoryInference.written",
925
846
  "improve.memoryInference.yieldRate",
926
847
  "improve.memoryInference.skippedNoFacts",
848
+ "improve.memoryInference.htmlErrorCount",
927
849
  "improve.graphExtraction.cacheHitRate",
928
850
  "improve.graphExtraction.failures",
851
+ "improve.graphExtraction.htmlErrors",
929
852
  "improve.sessionExtraction.sessionsScanned",
930
853
  "improve.sessionExtraction.proposalsCreated",
931
854
  "improve.autoAccept.promoted",
@@ -961,17 +884,87 @@ function computeDeltas(first, last) {
961
884
  }
962
885
  return out;
963
886
  }
964
- function buildWindowMetrics(db, stateDbPath, since, until) {
887
+ /**
888
+ * Partition task_history rows into "should have a log" (non-null log_path) and
889
+ * "log is actually backed". A run counts as backed when logs.db holds rows for
890
+ * its run_id (#579 — the DB is the primary record); rows written before logs.db
891
+ * existed fall back to the transitional on-disk file check. `logsDb` may be
892
+ * undefined when logs.db could not be opened — then only the file check runs.
893
+ */
894
+ function partitionLogBackedRows(taskRows, logsDb) {
895
+ const withLogs = taskRows.filter((row) => row.log_path !== null);
896
+ const loggedRunIds = logsDb
897
+ ? getLoggedRunIds(logsDb, withLogs.map((row) => buildTaskRunId(row.task_id, row.started_at)))
898
+ : new Set();
899
+ const backed = withLogs.filter((row) => loggedRunIds.has(buildTaskRunId(row.task_id, row.started_at)) ||
900
+ (row.log_path !== null && fs.existsSync(row.log_path)));
901
+ return { withLogs, backed };
902
+ }
903
+ /** Stage key used for `llm_usage` events recorded outside any stage scope. */
904
+ const UNATTRIBUTED_STAGE = "unattributed";
905
+ function emptyLlmUsageStageAggregate() {
906
+ return {
907
+ calls: 0,
908
+ totalDurationMs: 0,
909
+ promptTokens: 0,
910
+ completionTokens: 0,
911
+ totalTokens: 0,
912
+ reasoningTokens: 0,
913
+ };
914
+ }
915
+ function emptyLlmUsageAggregate() {
916
+ return { ...emptyLlmUsageStageAggregate(), byStage: {} };
917
+ }
918
+ /**
919
+ * Aggregate `llm_usage` events (#576) into a window total plus a per-stage
920
+ * breakdown of call count, wall-time, and token usage. Token fields absent from
921
+ * a best-effort record contribute 0. Calls with no `stage` land under
922
+ * {@link UNATTRIBUTED_STAGE}.
923
+ */
924
+ function summarizeLlmUsage(events) {
925
+ const aggregate = emptyLlmUsageAggregate();
926
+ for (const event of events) {
927
+ const meta = event.metadata ?? {};
928
+ const stageKey = typeof meta.stage === "string" && meta.stage ? meta.stage : UNATTRIBUTED_STAGE;
929
+ let stage = aggregate.byStage[stageKey];
930
+ if (!stage) {
931
+ stage = emptyLlmUsageStageAggregate();
932
+ aggregate.byStage[stageKey] = stage;
933
+ }
934
+ const durationMs = toFiniteNumber(meta.durationMs);
935
+ const promptTokens = toFiniteNumber(meta.promptTokens);
936
+ const completionTokens = toFiniteNumber(meta.completionTokens);
937
+ const totalTokens = toFiniteNumber(meta.totalTokens);
938
+ const reasoningTokens = toFiniteNumber(meta.reasoningTokens);
939
+ for (const target of [aggregate, stage]) {
940
+ target.calls += 1;
941
+ target.totalDurationMs += durationMs;
942
+ target.promptTokens += promptTokens;
943
+ target.completionTokens += completionTokens;
944
+ target.totalTokens += totalTokens;
945
+ target.reasoningTokens += reasoningTokens;
946
+ }
947
+ }
948
+ return aggregate;
949
+ }
950
+ function readLlmUsageAggregate(stateDbPath, since, until) {
951
+ const events = readEvents({ since, type: LLM_USAGE_EVENT }, { dbPath: stateDbPath }).events.filter((event) => {
952
+ if (until === undefined)
953
+ return true;
954
+ return new Date(event.ts ?? since).getTime() < new Date(until).getTime();
955
+ });
956
+ return summarizeLlmUsage(events);
957
+ }
958
+ function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now(), logsDb) {
965
959
  const taskRows = queryTaskHistory(db, { since }).filter((row) => {
966
960
  const startMs = new Date(row.started_at).getTime();
967
961
  const untilMs = new Date(until).getTime();
968
962
  return !Number.isFinite(untilMs) || startMs < untilMs;
969
963
  });
970
- const taskRowsWithLogs = taskRows.filter((row) => row.log_path !== null);
971
- const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
964
+ const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
972
965
  const failedTaskRows = taskRows.filter((row) => row.status === "failed");
973
966
  const activeRows = taskRows.filter((row) => row.status === "active");
974
- const stuckActiveRuns = activeRows.filter((row) => Date.now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
967
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
975
968
  const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
976
969
  const promptFailures = promptRows.filter((row) => {
977
970
  const detail = parseTaskMetadata(row).detail;
@@ -1002,6 +995,7 @@ function buildWindowMetrics(db, stateDbPath, since, until) {
1002
995
  stuckActiveRuns,
1003
996
  logBackingRate: roundRate(logBackingRate),
1004
997
  probeRoundTripMs: null,
998
+ llmUsage: readLlmUsageAggregate(stateDbPath, since, until),
1005
999
  };
1006
1000
  return { improve: improveSummary, metrics, runs: runCount };
1007
1001
  }
@@ -1027,8 +1021,9 @@ function validateAkmHealthOptions(options) {
1027
1021
  }
1028
1022
  export function akmHealth(options = {}) {
1029
1023
  validateAkmHealthOptions(options);
1024
+ const now = options.now ?? (() => Date.now());
1030
1025
  const since = parseHealthSince(options.since);
1031
- const stateDbPath = getStateDbPathInDataDir();
1026
+ const stateDbPath = options.stateDbPath ?? getStateDbPathInDataDir();
1032
1027
  const hardChecks = [];
1033
1028
  const advisories = [];
1034
1029
  const getExecutionLogCandidatesFn = options.getExecutionLogCandidatesFn ?? getExecutionLogCandidates;
@@ -1039,38 +1034,27 @@ export function akmHealth(options = {}) {
1039
1034
  catch (error) {
1040
1035
  throw new ConfigError(`Unable to open state.db: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
1041
1036
  }
1037
+ // logs.db backs the log-backing metric (#579). Best-effort: when it cannot
1038
+ // be opened, partitionLogBackedRows falls back to the on-disk file check, so
1039
+ // health never hard-fails on a missing/locked logs database.
1040
+ let logsDb;
1041
+ try {
1042
+ logsDb = openLogsDatabase(options.logsDbPath);
1043
+ }
1044
+ catch {
1045
+ logsDb = undefined;
1046
+ }
1042
1047
  try {
1043
- const tables = db
1044
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('events', 'task_history', 'proposals', 'schema_migrations') ORDER BY name")
1045
- .all();
1048
+ const tables = listExistingTableNames(db, ["events", "task_history", "proposals", "schema_migrations"]);
1046
1049
  const tableNames = tables.map((row) => row.name).sort();
1047
1050
  const requiredTables = ["events", "proposals", "schema_migrations", "task_history"];
1048
1051
  const missingTables = requiredTables.filter((name) => !tableNames.includes(name));
1049
- hardChecks.push({
1050
- name: "state-db-schema",
1051
- kind: "deterministic",
1052
- status: missingTables.length === 0 ? "pass" : "fail",
1053
- confidence: "high",
1054
- message: missingTables.length === 0
1055
- ? "state.db opened and required tables are present."
1056
- : `state.db is missing required tables: ${missingTables.join(", ")}`,
1057
- evidence: { path: stateDbPath, tables: tableNames },
1058
- });
1059
1052
  const probe = probeStateDbRoundTrip(stateDbPath);
1060
- hardChecks.push({
1061
- name: "state-db-round-trip",
1062
- kind: "deterministic",
1063
- status: probe.ok ? "pass" : "fail",
1064
- confidence: "high",
1065
- message: probe.ok ? "state.db append/read round-trip succeeded." : `state.db round-trip failed: ${probe.error}`,
1066
- evidence: { path: stateDbPath, durationMs: probe.durationMs },
1067
- });
1068
1053
  const taskRows = queryTaskHistory(db, { since });
1069
- const taskRowsWithLogs = taskRows.filter((row) => row.log_path !== null);
1070
- const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
1054
+ const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
1071
1055
  const failedTaskRows = taskRows.filter((row) => row.status === "failed");
1072
1056
  const activeRows = taskRows.filter((row) => row.status === "active");
1073
- const stuckActiveRuns = activeRows.filter((row) => Date.now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
1057
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
1074
1058
  const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
1075
1059
  const promptFailures = promptRows.filter((row) => {
1076
1060
  const detail = parseTaskMetadata(row).detail;
@@ -1079,51 +1063,7 @@ export function akmHealth(options = {}) {
1079
1063
  const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
1080
1064
  const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
1081
1065
  const agentFailureRate = promptRows.length === 0 ? 0 : promptFailures.length / promptRows.length;
1082
- hardChecks.push({
1083
- name: "task-history-read",
1084
- kind: "deterministic",
1085
- status: "pass",
1086
- confidence: "high",
1087
- message: `Read ${taskRows.length} task-history row(s) since ${since}.`,
1088
- evidence: { rows: taskRows.length, since },
1089
- });
1090
- hardChecks.push({
1091
- name: "task-log-backing",
1092
- kind: "deterministic",
1093
- status: logBackingRate === 1 ? "pass" : "fail",
1094
- confidence: "high",
1095
- message: logBackingRate === 1
1096
- ? "Every task_history log_path resolved on disk."
1097
- : `${taskRowsWithLogs.length - existingLogRows.length} task log(s) referenced in task_history are missing.`,
1098
- evidence: { totalWithLogs: taskRowsWithLogs.length, existingLogs: existingLogRows.length },
1099
- });
1100
- hardChecks.push({
1101
- name: "active-runs",
1102
- kind: "deterministic",
1103
- status: stuckActiveRuns === 0 ? "pass" : "warn",
1104
- confidence: "high",
1105
- message: stuckActiveRuns === 0
1106
- ? "No active task runs exceeded the stale threshold."
1107
- : `${stuckActiveRuns} active task run(s) are older than ${Math.round(ACTIVE_RUN_WARN_MS / 60000)} minutes.`,
1108
- evidence: { stuckActiveRuns },
1109
- });
1110
- hardChecks.push(runAgentProbe());
1111
1066
  const semanticStatus = readSemanticStatus();
1112
- advisories.push({
1113
- name: "semantic-search-runtime",
1114
- kind: "deterministic",
1115
- status: !semanticStatus ||
1116
- semanticStatus.status === "pending" ||
1117
- semanticStatus.status === "ready-js" ||
1118
- semanticStatus.status === "ready-vec"
1119
- ? "pass"
1120
- : "warn",
1121
- confidence: "medium",
1122
- message: semanticStatus
1123
- ? `Semantic search status: ${semanticStatus.status}`
1124
- : "No semantic-search runtime status recorded yet.",
1125
- evidence: semanticStatus ? { ...semanticStatus } : undefined,
1126
- });
1127
1067
  const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
1128
1068
  const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
1129
1069
  const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
@@ -1139,7 +1079,7 @@ export function akmHealth(options = {}) {
1139
1079
  improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
1140
1080
  let sessionLogEntries = [];
1141
1081
  try {
1142
- const sinceDays = Math.max(0, Math.ceil((Date.now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
1082
+ const sinceDays = Math.max(0, Math.ceil((now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
1143
1083
  sessionLogEntries = getExecutionLogCandidatesFn(sinceDays).map((entry) => ({
1144
1084
  topic: entry.topic,
1145
1085
  frequency: entry.frequency,
@@ -1150,65 +1090,40 @@ export function akmHealth(options = {}) {
1150
1090
  catch {
1151
1091
  sessionLogEntries = [];
1152
1092
  }
1153
- // session-log-failures: demoted to informational the ERROR_PATTERNS regex
1154
- // scans pre-LLM session text and produces false positives on diagnostic
1155
- // conversation. It does not gate the real extraction pipeline (akmExtract).
1156
- // Never triggers warn; kept for backward-compat visibility only.
1157
- advisories.push({
1158
- name: "session-log-failures",
1159
- kind: "heuristic",
1160
- status: "pass",
1161
- confidence: "low",
1162
- message: sessionLogEntries.length === 0
1163
- ? "No repeated external session-log failure patterns were detected."
1164
- : `${sessionLogEntries.length} raw session-log keyword match(es) detected (pre-LLM, informational only).`,
1165
- evidence: { candidates: sessionLogEntries.slice(0, 5) },
1166
- });
1167
- const sx = improveSummary.sessionExtraction;
1168
- const sxWarnReasons = [];
1169
- if (sx.warnings > 0)
1170
- sxWarnReasons.push(`${sx.warnings} harness error(s)`);
1171
- if (sx.ran && sx.sessionsScanned >= 5 && sx.proposalsCreated === 0)
1172
- sxWarnReasons.push("no proposals generated across scanned sessions");
1173
- advisories.push({
1174
- name: "session-extraction",
1175
- kind: "heuristic",
1176
- status: sxWarnReasons.length > 0 ? "warn" : "pass",
1177
- confidence: sx.ran ? "medium" : "low",
1178
- message: sx.ran
1179
- ? sxWarnReasons.length > 0
1180
- ? `Session extraction degraded: ${sxWarnReasons.join("; ")}.`
1181
- : `Session extraction healthy: ${sx.sessionsScanned} scanned, ${sx.sessionsExtracted} extracted, ${sx.proposalsCreated} proposal(s) created.`
1182
- : "Session extraction not active (feature disabled or no harness available).",
1183
- evidence: {
1184
- ran: sx.ran,
1185
- sessionsScanned: sx.sessionsScanned,
1186
- sessionsExtracted: sx.sessionsExtracted,
1187
- sessionsSkipped: sx.sessionsSkipped,
1188
- proposalsCreated: sx.proposalsCreated,
1189
- warnings: sx.warnings,
1190
- durationMs: sx.durationMs,
1191
- },
1192
- });
1193
- const aa = improveSummary.autoAccept;
1194
- advisories.push({
1195
- name: "auto-accept-validation",
1196
- kind: "heuristic",
1197
- status: aa.validationFailed > 0 ? "warn" : "pass",
1198
- confidence: aa.promoted + aa.validationFailed > 0 ? "high" : "low",
1199
- message: aa.validationFailed > 0
1200
- ? `${aa.validationFailed} proposal(s) passed confidence threshold but failed auto-accept validation (truncated description, invalid frontmatter, etc.) — they remain in the queue for manual review.`
1201
- : aa.promoted > 0
1202
- ? `Auto-accept healthy: ${aa.promoted} proposal(s) promoted, 0 validation failures.`
1203
- : "Auto-accept gate did not run (disabled or no proposals above threshold).",
1204
- evidence: { promoted: aa.promoted, validationFailed: aa.validationFailed },
1205
- });
1093
+ // Run the ordered health-check registry. Each check projects the shared
1094
+ // context computed above into one HealthCheckResult; `channel` routes it to
1095
+ // hardChecks or advisories. Declaration order in HEALTH_CHECKS is the
1096
+ // emission order see src/commands/health/checks.ts.
1097
+ const checkContext = {
1098
+ stateDbPath,
1099
+ since,
1100
+ tableNames,
1101
+ missingTables,
1102
+ probe,
1103
+ taskRowCount: taskRows.length,
1104
+ taskRowsWithLogsCount: taskRowsWithLogs.length,
1105
+ existingLogRowsCount: existingLogRows.length,
1106
+ logBackingRate,
1107
+ stuckActiveRuns,
1108
+ semanticStatus,
1109
+ sessionLogEntries,
1110
+ sessionExtraction: improveSummary.sessionExtraction,
1111
+ autoAccept: improveSummary.autoAccept,
1112
+ };
1113
+ for (const check of HEALTH_CHECKS) {
1114
+ const result = check.run(checkContext);
1115
+ if (check.channel === "hard")
1116
+ hardChecks.push(result);
1117
+ else
1118
+ advisories.push(result);
1119
+ }
1206
1120
  const metrics = {
1207
1121
  taskFailRate: roundRate(taskFailRate),
1208
1122
  agentFailureRate: roundRate(agentFailureRate),
1209
1123
  stuckActiveRuns,
1210
1124
  logBackingRate: roundRate(logBackingRate),
1211
1125
  probeRoundTripMs: probe.durationMs,
1126
+ llmUsage: readLlmUsageAggregate(stateDbPath, since),
1212
1127
  };
1213
1128
  const hardFailure = hardChecks.some((check) => check.status === "fail");
1214
1129
  const deterministicWarnings = [...hardChecks, ...advisories].some((check) => check.status === "warn" && check.kind === "deterministic");
@@ -1216,7 +1131,7 @@ export function akmHealth(options = {}) {
1216
1131
  // ── Window-compare mode (Phase 3) ─────────────────────────────────────
1217
1132
  let windowSpecs;
1218
1133
  if (options.windowCompare) {
1219
- windowSpecs = resolveWindowCompare(options.windowCompare);
1134
+ windowSpecs = resolveWindowCompare(options.windowCompare, now);
1220
1135
  }
1221
1136
  else if (options.windows && options.windows.length > 0) {
1222
1137
  windowSpecs = options.windows;
@@ -1229,8 +1144,8 @@ export function akmHealth(options = {}) {
1229
1144
  if (windowSpecs && db) {
1230
1145
  windowResults = windowSpecs.map((spec) => {
1231
1146
  const winSince = parseHealthSince(spec.since);
1232
- const winUntil = spec.until ? parseHealthSince(spec.until) : new Date().toISOString();
1233
- const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil);
1147
+ const winUntil = spec.until ? parseHealthSince(spec.until) : new Date(now()).toISOString();
1148
+ const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil, now, logsDb);
1234
1149
  return {
1235
1150
  name: spec.name,
1236
1151
  since: winSince,
@@ -1280,6 +1195,14 @@ export function akmHealth(options = {}) {
1280
1195
  }
1281
1196
  finally {
1282
1197
  db.close();
1198
+ if (logsDb) {
1199
+ try {
1200
+ logsDb.close();
1201
+ }
1202
+ catch {
1203
+ // best-effort
1204
+ }
1205
+ }
1283
1206
  }
1284
1207
  }
1285
1208
  // ── Markdown renderers ───────────────────────────────────────────────────────