akm-cli 0.8.1 → 0.9.0-beta.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 (318) hide show
  1. package/CHANGELOG.md +258 -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/stash-skeleton/README.md +76 -0
  9. package/dist/assets/tasks/core/backup.yml +4 -0
  10. package/dist/assets/tasks/core/extract.yml +4 -0
  11. package/dist/assets/tasks/core/improve.yml +4 -0
  12. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  13. package/dist/assets/tasks/core/sync.yml +4 -0
  14. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  15. package/dist/assets/tasks/core/version-check.yml +4 -0
  16. package/dist/cli/config-migrate.js +6 -6
  17. package/dist/cli/config-validate.js +4 -4
  18. package/dist/cli/confirm.js +3 -3
  19. package/dist/cli/parse-args.js +1 -1
  20. package/dist/cli/shared.js +51 -14
  21. package/dist/cli-node.mjs +26 -0
  22. package/dist/cli.js +171 -3857
  23. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  24. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  25. package/dist/commands/agent/contribute-cli.js +200 -0
  26. package/dist/commands/completions.js +1 -1
  27. package/dist/commands/config-cli.js +240 -3
  28. package/dist/commands/config-edit.js +344 -0
  29. package/dist/commands/db-cli.js +2 -2
  30. package/dist/commands/env/env-cli.js +529 -0
  31. package/dist/commands/env/env.js +410 -0
  32. package/dist/commands/env/secret-cli.js +259 -0
  33. package/dist/commands/{secret.js → env/secret.js} +6 -47
  34. package/dist/commands/events.js +4 -4
  35. package/dist/commands/feedback-cli.js +18 -34
  36. package/dist/commands/graph/graph-cli.js +132 -0
  37. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  38. package/dist/commands/health/checks.js +279 -0
  39. package/dist/commands/health.js +101 -249
  40. package/dist/commands/{consolidate.js → improve/consolidate.js} +52 -40
  41. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  42. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  43. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  44. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  45. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  46. package/dist/commands/{extract.js → improve/extract.js} +185 -26
  47. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
  48. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +45 -23
  49. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  50. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +10 -5
  51. package/dist/commands/{improve.js → improve/improve.js} +536 -248
  52. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  53. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  54. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  55. package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
  56. package/dist/commands/improve/session-asset.js +248 -0
  57. package/dist/commands/lint/agent-linter.js +1 -1
  58. package/dist/commands/lint/base-linter.js +55 -37
  59. package/dist/commands/lint/command-linter.js +1 -1
  60. package/dist/commands/lint/default-linter.js +1 -1
  61. package/dist/commands/lint/env-key-rules.js +1 -1
  62. package/dist/commands/lint/index.js +19 -25
  63. package/dist/commands/lint/knowledge-linter.js +1 -1
  64. package/dist/commands/lint/memory-linter.js +1 -1
  65. package/dist/commands/lint/registry.js +8 -8
  66. package/dist/commands/lint/skill-linter.js +1 -1
  67. package/dist/commands/lint/task-linter.js +1 -1
  68. package/dist/commands/lint/workflow-linter.js +1 -1
  69. package/dist/commands/lint.js +1 -1
  70. package/dist/commands/observability-cli.js +244 -0
  71. package/dist/commands/{proposal-drain-policies.js → proposal/drain-policies.js} +3 -3
  72. package/dist/commands/{proposal-drain.js → proposal/drain.js} +15 -10
  73. package/dist/commands/proposal/proposal-cli.js +478 -0
  74. package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
  75. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  76. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  77. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  78. package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
  79. package/dist/commands/{curate.js → read/curate.js} +7 -7
  80. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  81. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  82. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  83. package/dist/commands/read/search-cli.js +207 -0
  84. package/dist/commands/{search.js → read/search.js} +22 -27
  85. package/dist/commands/{show.js → read/show.js} +77 -44
  86. package/dist/commands/registry-cli.js +8 -8
  87. package/dist/commands/remember.js +8 -8
  88. package/dist/commands/sources/add-cli.js +293 -0
  89. package/dist/commands/{history.js → sources/history.js} +27 -25
  90. package/dist/commands/{info.js → sources/info.js} +6 -6
  91. package/dist/commands/{init.js → sources/init.js} +10 -5
  92. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  93. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  94. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  95. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  96. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  97. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  98. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  99. package/dist/commands/sources/sources-cli.js +305 -0
  100. package/dist/commands/sources/stash-cli.js +219 -0
  101. package/dist/commands/sources/stash-skeleton.js +79 -0
  102. package/dist/commands/tasks/default-tasks.js +173 -0
  103. package/dist/commands/tasks/tasks-cli.js +210 -0
  104. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  105. package/dist/commands/wiki-cli.js +307 -0
  106. package/dist/commands/workflow-cli.js +329 -0
  107. package/dist/core/action-contributors.js +1 -1
  108. package/dist/core/assert.js +40 -0
  109. package/dist/core/asset/asset-create.js +54 -0
  110. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  111. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  112. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  113. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  114. package/dist/core/asset/stash-meta.js +110 -0
  115. package/dist/core/best-effort.js +64 -0
  116. package/dist/core/common.js +32 -18
  117. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  118. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  119. package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
  120. package/dist/core/config/config-types.js +16 -0
  121. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  122. package/dist/core/{config.js → config/config.js} +10 -8
  123. package/dist/core/env-secret-ref.js +90 -0
  124. package/dist/core/errors.js +13 -3
  125. package/dist/core/events.js +27 -4
  126. package/dist/core/file-lock.js +1 -1
  127. package/dist/core/improve-types.js +48 -0
  128. package/dist/core/lesson-lint.js +2 -2
  129. package/dist/core/paths.js +2 -2
  130. package/dist/{setup/ripgrep-install.js → core/ripgrep/install.js} +2 -2
  131. package/dist/{setup/ripgrep-resolve.js → core/ripgrep/resolve.js} +2 -2
  132. package/dist/core/state-db.js +88 -46
  133. package/dist/core/text-truncation.js +148 -0
  134. package/dist/core/time.js +1 -1
  135. package/dist/core/write-source.js +98 -85
  136. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  137. package/dist/indexer/{db.js → db/db.js} +126 -116
  138. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  139. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  140. package/dist/indexer/ensure-index.js +4 -4
  141. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  142. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  143. package/dist/indexer/indexer.js +37 -30
  144. package/dist/indexer/init.js +54 -0
  145. package/dist/indexer/manifest.js +10 -10
  146. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
  147. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  148. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  149. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  150. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  151. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  152. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  153. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  154. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  155. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  156. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  157. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  158. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  159. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  160. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  161. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  162. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  163. package/dist/integrations/agent/builder-shared.js +39 -0
  164. package/dist/integrations/agent/builders.js +14 -81
  165. package/dist/integrations/agent/config.js +6 -4
  166. package/dist/integrations/agent/detect.js +1 -1
  167. package/dist/integrations/agent/index.js +23 -8
  168. package/dist/integrations/agent/prompts.js +2 -3
  169. package/dist/integrations/agent/runner.js +22 -3
  170. package/dist/integrations/agent/spawn.js +9 -10
  171. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  172. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  173. package/dist/integrations/harnesses/claude/index.js +64 -0
  174. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
  175. package/dist/integrations/harnesses/index.js +144 -0
  176. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  177. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  178. package/dist/integrations/harnesses/opencode/index.js +59 -0
  179. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  180. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  181. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  182. package/dist/integrations/harnesses/types.js +43 -0
  183. package/dist/integrations/lockfile.js +7 -16
  184. package/dist/integrations/session-logs/index.js +82 -9
  185. package/dist/llm/call-ai.js +4 -4
  186. package/dist/llm/client.js +131 -6
  187. package/dist/llm/embedder.js +6 -6
  188. package/dist/llm/embedders/local.js +9 -22
  189. package/dist/llm/embedders/remote.js +2 -2
  190. package/dist/llm/embedders/types.js +1 -1
  191. package/dist/llm/graph-extract.js +31 -12
  192. package/dist/llm/index-passes.js +1 -1
  193. package/dist/llm/memory-infer.js +12 -5
  194. package/dist/llm/metadata-enhance.js +2 -2
  195. package/dist/output/context.js +6 -44
  196. package/dist/output/renderers.js +88 -58
  197. package/dist/output/shapes/curate.js +7 -3
  198. package/dist/output/shapes/distill.js +7 -3
  199. package/dist/output/shapes/env-list.js +18 -16
  200. package/dist/output/shapes/events.js +5 -4
  201. package/dist/output/shapes/helpers.js +2 -4
  202. package/dist/output/shapes/history.js +7 -3
  203. package/dist/output/shapes/passthrough.js +8 -11
  204. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  205. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  206. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  207. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  208. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  209. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  210. package/dist/output/shapes/registry-search.js +7 -3
  211. package/dist/output/shapes/registry.js +12 -0
  212. package/dist/output/shapes/search.js +7 -3
  213. package/dist/output/shapes/secret-list.js +18 -16
  214. package/dist/output/shapes/show.js +7 -3
  215. package/dist/output/shapes.js +55 -30
  216. package/dist/output/text/add.js +2 -3
  217. package/dist/output/text/clone.js +2 -3
  218. package/dist/output/text/config.js +2 -3
  219. package/dist/output/text/curate.js +4 -3
  220. package/dist/output/text/distill.js +2 -3
  221. package/dist/output/text/enable-disable.js +5 -4
  222. package/dist/output/text/env.js +13 -0
  223. package/dist/output/text/events.js +5 -4
  224. package/dist/output/text/feedback.js +4 -3
  225. package/dist/output/text/helpers.js +54 -39
  226. package/dist/output/text/history.js +2 -3
  227. package/dist/output/text/import.js +2 -3
  228. package/dist/output/text/index.js +2 -3
  229. package/dist/output/text/info.js +2 -3
  230. package/dist/output/text/init.js +2 -3
  231. package/dist/output/text/list.js +2 -3
  232. package/dist/output/text/proposal/producer.js +9 -0
  233. package/dist/output/text/proposal/proposal.js +13 -0
  234. package/dist/output/text/registry-commands.js +8 -7
  235. package/dist/output/text/registry.js +12 -0
  236. package/dist/output/text/remember.js +4 -3
  237. package/dist/output/text/remove.js +2 -3
  238. package/dist/output/text/save.js +2 -3
  239. package/dist/output/text/search.js +4 -3
  240. package/dist/output/text/show.js +4 -3
  241. package/dist/output/text/update.js +2 -3
  242. package/dist/output/text/upgrade.js +2 -3
  243. package/dist/output/text/wiki.js +12 -11
  244. package/dist/output/text/workflow.js +12 -10
  245. package/dist/output/text.js +66 -32
  246. package/dist/registry/build-index.js +11 -10
  247. package/dist/registry/factory.js +1 -1
  248. package/dist/registry/origin-resolve.js +1 -1
  249. package/dist/registry/providers/index.js +2 -2
  250. package/dist/registry/providers/skills-sh.js +91 -72
  251. package/dist/registry/providers/static-index.js +75 -52
  252. package/dist/registry/resolve.js +3 -3
  253. package/dist/runtime.js +242 -0
  254. package/dist/scripts/migrate-storage.js +1594 -673
  255. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
  256. package/dist/setup/detect.js +338 -9
  257. package/dist/setup/harness-config-import.js +56 -0
  258. package/dist/setup/registry-stash-loader.js +99 -0
  259. package/dist/setup/setup.js +664 -96
  260. package/dist/sources/include.js +1 -1
  261. package/dist/sources/provider-factory.js +2 -2
  262. package/dist/sources/providers/filesystem.js +3 -3
  263. package/dist/sources/providers/git.js +9 -9
  264. package/dist/sources/providers/index.js +4 -4
  265. package/dist/sources/providers/npm.js +6 -6
  266. package/dist/sources/providers/provider-utils.js +13 -20
  267. package/dist/sources/providers/sync-from-ref.js +5 -5
  268. package/dist/sources/providers/tar-utils.js +2 -2
  269. package/dist/sources/providers/website.js +2 -2
  270. package/dist/sources/resolve.js +5 -5
  271. package/dist/sources/website-ingest.js +5 -5
  272. package/dist/storage/database.js +102 -0
  273. package/dist/storage/engines/sqlite-migrations.js +42 -0
  274. package/dist/storage/locations.js +25 -0
  275. package/dist/storage/repositories/index-db.js +43 -0
  276. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  277. package/dist/tasks/backends/cron.js +4 -4
  278. package/dist/tasks/backends/exec-utils.js +32 -0
  279. package/dist/tasks/backends/index.js +3 -3
  280. package/dist/tasks/backends/launchd.js +7 -14
  281. package/dist/tasks/backends/schtasks.js +7 -16
  282. package/dist/tasks/embedded.js +71 -0
  283. package/dist/tasks/parser.js +2 -2
  284. package/dist/tasks/resolveAkmBin.js +1 -1
  285. package/dist/tasks/runner.js +28 -15
  286. package/dist/tasks/schedule.js +1 -1
  287. package/dist/tasks/validator.js +7 -7
  288. package/dist/text-import-hook.mjs +51 -0
  289. package/dist/version.js +2 -1
  290. package/dist/wiki/wiki.js +7 -7
  291. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  292. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  293. package/dist/workflows/cli.js +1 -1
  294. package/dist/workflows/db.js +50 -32
  295. package/dist/workflows/parser.js +4 -4
  296. package/dist/workflows/renderer.js +5 -5
  297. package/dist/workflows/runtime/agent-identity.js +56 -0
  298. package/dist/workflows/runtime/checkin.js +57 -0
  299. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  300. package/dist/workflows/validate-summary.js +82 -0
  301. package/docs/README.md +1 -1
  302. package/docs/data-and-telemetry.md +6 -6
  303. package/package.json +16 -8
  304. package/dist/commands/add-cli.js +0 -279
  305. package/dist/commands/env.js +0 -213
  306. package/dist/integrations/agent/sdk-runner.js +0 -126
  307. package/dist/output/shapes/vault-list.js +0 -19
  308. package/dist/output/text/proposal-producer.js +0 -8
  309. package/dist/output/text/proposal.js +0 -12
  310. package/dist/output/text/vault.js +0 -16
  311. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  312. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  313. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  314. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  315. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  316. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  317. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  318. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -1,17 +1,15 @@
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 { getStateDbPathInDataDir } from "../core/paths.js";
8
+ import { listExistingTableNames, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
9
+ import { parseSinceToIso } from "../core/time.js";
10
+ import { readSemanticStatus } from "../indexer/search/semantic-status.js";
11
+ import { getExecutionLogCandidates } from "../integrations/session-logs/index.js";
12
+ import { HEALTH_CHECKS } from "./health/checks.js";
15
13
  const DEFAULT_SINCE_MS = 24 * 60 * 60 * 1000;
16
14
  const IMPROVE_COMPLETED_EVENT = "improve_completed";
17
15
  const HEALTH_PROBE_EVENT = "health_probe";
@@ -71,6 +69,7 @@ function createUnknownImproveMetrics() {
71
69
  graphExtraction: 0,
72
70
  error: 0,
73
71
  },
72
+ autoAccept: { promoted: 0, validationFailed: 0 },
74
73
  reflectsWithErrorContext: 0,
75
74
  coverageGapCount: 0,
76
75
  evalCasesWritten: 0,
@@ -103,6 +102,7 @@ function createUnknownImproveMetrics() {
103
102
  ran: false,
104
103
  considered: 0,
105
104
  cacheHits: 0,
105
+ retryAttempts: 0,
106
106
  freshAttempts: 0,
107
107
  splitParents: 0,
108
108
  written: 0,
@@ -110,6 +110,7 @@ function createUnknownImproveMetrics() {
110
110
  skippedChildExists: 0,
111
111
  skippedAborted: 0,
112
112
  unaccounted: 0,
113
+ htmlErrorCount: 0,
113
114
  yieldEligibleRuns: 0,
114
115
  yieldEligibleConsidered: 0,
115
116
  yieldEligibleWritten: 0,
@@ -127,6 +128,8 @@ function createUnknownImproveMetrics() {
127
128
  cacheHitRate: 0,
128
129
  truncations: 0,
129
130
  failures: 0,
131
+ htmlErrors: 0,
132
+ retryAttempts: 0,
130
133
  durationMs: 0,
131
134
  },
132
135
  sessionExtraction: {
@@ -293,6 +296,8 @@ function projectRunMetrics(result) {
293
296
  }
294
297
  }
295
298
  }
299
+ metrics.autoAccept.promoted += toFiniteNumber(result.gateAutoAcceptedCount);
300
+ metrics.autoAccept.validationFailed += toFiniteNumber(result.gateAutoAcceptFailedCount);
296
301
  metrics.reflectsWithErrorContext += toFiniteNumber(result.reflectsWithErrorContext);
297
302
  if (Array.isArray(result.coverageGaps))
298
303
  metrics.coverageGapCount += result.coverageGaps.length;
@@ -334,18 +339,26 @@ function projectRunMetrics(result) {
334
339
  metrics.consolidation.mergedSecondaries += toFiniteNumber(consolidation.mergedSecondaries);
335
340
  metrics.consolidation.failedChunkMemories += toFiniteNumber(consolidation.failedChunkMemories);
336
341
  // Structured emitter (new on this branch): consolidate.ts now pushes
337
- // `{op, ref, reason}` entries to `skipReasons` for every deterministic
338
- // post-LLM rejection. Pre-fix envelopes have neither field, so be
339
- // defensive.
342
+ // per-ref grouped `{ref, skips: [{op, reason}]}` entries to `skipReasons`
343
+ // for every deterministic post-LLM rejection. Each ref appears once but
344
+ // may carry multiple skips; aggregate every reason. Pre-fix envelopes have
345
+ // neither field, so be defensive.
340
346
  const skipReasons = consolidation.skipReasons;
341
347
  if (Array.isArray(skipReasons)) {
342
348
  for (const entry of skipReasons) {
343
349
  if (!entry || typeof entry !== "object")
344
350
  continue;
345
- const reason = entry.reason;
346
- if (typeof reason !== "string" || !reason.trim())
351
+ const skips = entry.skips;
352
+ if (!Array.isArray(skips))
347
353
  continue;
348
- metrics.consolidation.skipReasons[reason] = (metrics.consolidation.skipReasons[reason] ?? 0) + 1;
354
+ for (const skip of skips) {
355
+ if (!skip || typeof skip !== "object")
356
+ continue;
357
+ const reason = skip.reason;
358
+ if (typeof reason !== "string" || !reason.trim())
359
+ continue;
360
+ metrics.consolidation.skipReasons[reason] = (metrics.consolidation.skipReasons[reason] ?? 0) + 1;
361
+ }
349
362
  }
350
363
  }
351
364
  }
@@ -355,12 +368,14 @@ function projectRunMetrics(result) {
355
368
  const writtenFacts = toFiniteNumber(memoryInference.writtenFacts);
356
369
  metrics.memoryInference.considered += considered;
357
370
  metrics.memoryInference.cacheHits += toFiniteNumber(memoryInference.cacheHits);
371
+ metrics.memoryInference.retryAttempts += toFiniteNumber(memoryInference.retryAttempts);
358
372
  metrics.memoryInference.splitParents += toFiniteNumber(memoryInference.splitParents);
359
373
  metrics.memoryInference.written += writtenFacts;
360
374
  metrics.memoryInference.skippedNoFacts += toFiniteNumber(memoryInference.skippedNoFacts);
361
375
  metrics.memoryInference.skippedChildExists += toFiniteNumber(memoryInference.skippedChildExists);
362
376
  metrics.memoryInference.skippedAborted += toFiniteNumber(memoryInference.skippedAborted);
363
377
  metrics.memoryInference.unaccounted += toFiniteNumber(memoryInference.unaccounted);
378
+ metrics.memoryInference.htmlErrorCount += toFiniteNumber(memoryInference.htmlErrorCount);
364
379
  // Yield-rate gating: pre-cache-feature envelopes lack the `cacheHits`
365
380
  // field entirely. Treating their `considered` as freshAttempts (since
366
381
  // cacheHits=0) is mathematically tempting but operationally wrong —
@@ -388,6 +403,8 @@ function projectRunMetrics(result) {
388
403
  metrics.graphExtraction.cacheMisses += toFiniteNumber(telemetry.cacheMisses);
389
404
  metrics.graphExtraction.truncations += toFiniteNumber(telemetry.truncationCount);
390
405
  metrics.graphExtraction.failures += toFiniteNumber(telemetry.failureCount);
406
+ metrics.graphExtraction.htmlErrors += toFiniteNumber(telemetry.htmlErrorCount);
407
+ metrics.graphExtraction.retryAttempts += toFiniteNumber(telemetry.retryAttempts);
391
408
  }
392
409
  }
393
410
  metrics.graphExtraction.durationMs += toFiniteNumber(result.graphExtractionDurationMs);
@@ -481,6 +498,8 @@ function mergeImproveMetrics(dst, src) {
481
498
  dst.actions.memoryInference += src.actions.memoryInference;
482
499
  dst.actions.graphExtraction += src.actions.graphExtraction;
483
500
  dst.actions.error += src.actions.error;
501
+ dst.autoAccept.promoted += src.autoAccept.promoted;
502
+ dst.autoAccept.validationFailed += src.autoAccept.validationFailed;
484
503
  dst.reflectsWithErrorContext += src.reflectsWithErrorContext;
485
504
  dst.coverageGapCount += src.coverageGapCount;
486
505
  dst.evalCasesWritten += src.evalCasesWritten;
@@ -515,6 +534,7 @@ function mergeImproveMetrics(dst, src) {
515
534
  dst.memoryInference.skippedChildExists += src.memoryInference.skippedChildExists;
516
535
  dst.memoryInference.skippedAborted += src.memoryInference.skippedAborted;
517
536
  dst.memoryInference.unaccounted += src.memoryInference.unaccounted;
537
+ dst.memoryInference.htmlErrorCount += src.memoryInference.htmlErrorCount;
518
538
  dst.memoryInference.yieldEligibleRuns += src.memoryInference.yieldEligibleRuns;
519
539
  dst.memoryInference.yieldEligibleConsidered += src.memoryInference.yieldEligibleConsidered;
520
540
  dst.memoryInference.yieldEligibleWritten += src.memoryInference.yieldEligibleWritten;
@@ -526,6 +546,7 @@ function mergeImproveMetrics(dst, src) {
526
546
  dst.graphExtraction.cacheMisses += src.graphExtraction.cacheMisses;
527
547
  dst.graphExtraction.truncations += src.graphExtraction.truncations;
528
548
  dst.graphExtraction.failures += src.graphExtraction.failures;
549
+ dst.graphExtraction.htmlErrors += src.graphExtraction.htmlErrors;
529
550
  dst.graphExtraction.durationMs += src.graphExtraction.durationMs;
530
551
  dst.sessionExtraction.sessionsScanned += src.sessionExtraction.sessionsScanned;
531
552
  dst.sessionExtraction.sessionsExtracted += src.sessionExtraction.sessionsExtracted;
@@ -534,15 +555,9 @@ function mergeImproveMetrics(dst, src) {
534
555
  dst.sessionExtraction.warnings += src.sessionExtraction.warnings;
535
556
  dst.sessionExtraction.durationMs += src.sessionExtraction.durationMs;
536
557
  }
537
- function loadImproveRunRows(db, since, until) {
538
- const sql = until
539
- ? "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"
540
- : "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";
541
- return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
542
- }
543
558
  function summarizeImproveRuns(db, since, until) {
544
559
  const accum = createUnknownImproveMetrics();
545
- const rows = loadImproveRunRows(db, since, until);
560
+ const rows = queryImproveRuns(db, since, until);
546
561
  // Per-phase wall-time samples. Each entry is one envelope's durationMs for
547
562
  // that phase. Phases that did not run on a given envelope are simply
548
563
  // omitted (NOT counted as 0) so the median/p95 reflect actual phase work.
@@ -661,10 +676,7 @@ function loadTaskIntervals(db, since, until) {
661
676
  const untilMs = until ? new Date(until).getTime() : Number.POSITIVE_INFINITY;
662
677
  const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
663
678
  const widenedUntil = Number.isFinite(untilMs) ? new Date(untilMs + 5 * 60 * 1000).toISOString() : undefined;
664
- const sql = widenedUntil
665
- ? "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"
666
- : "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";
667
- const rows = (widenedUntil ? db.prepare(sql).all(widenedSince, widenedUntil) : db.prepare(sql).all(widenedSince));
679
+ const rows = queryCompletedTaskIntervals(db, widenedSince, widenedUntil);
668
680
  const intervals = [];
669
681
  for (const row of rows) {
670
682
  const startMs = new Date(row.started_at).getTime();
@@ -697,18 +709,27 @@ function findContainingTaskInterval(timestampMs, intervals) {
697
709
  return undefined;
698
710
  }
699
711
  function buildPerRunSummaries(db, since, until) {
700
- const rows = loadImproveRunRows(db, since, until);
712
+ const rows = queryImproveRuns(db, since, until);
701
713
  const taskIntervals = loadTaskIntervals(db, since, until);
702
714
  const summaries = [];
703
715
  for (const row of rows) {
704
716
  const startMs = new Date(row.started_at).getTime();
705
717
  const endMs = new Date(row.completed_at).getTime();
706
- // Prefer the task_history interval (which has distinct start/end timestamps).
707
- // Fall back to the improve_runs row's own delta (usually 0 because
708
- // recordImproveRun writes started_at == completed_at == end-of-run timestamp).
709
- const fallbackWallMs = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs ? endMs - startMs : 0;
710
- const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
711
- const wallTimeMs = interval?.durationMs ?? fallbackWallMs;
718
+ // Prefer the improve_runs row's own (completed_at - started_at) delta:
719
+ // recordImproveRun now persists distinct start/end timestamps, so the
720
+ // row's own delta is the authoritative per-run wall time even for
721
+ // manually-invoked `akm improve` runs with no enclosing task_history.
722
+ // Only fall back to the task_history containing-interval join for legacy/
723
+ // backfill rows where started_at == completed_at (row delta is 0).
724
+ const hasRowDelta = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs;
725
+ let wallTimeMs;
726
+ if (hasRowDelta) {
727
+ wallTimeMs = endMs - startMs;
728
+ }
729
+ else {
730
+ const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
731
+ wallTimeMs = interval?.durationMs ?? 0;
732
+ }
712
733
  summaries.push(projectImproveRunSummary(row, wallTimeMs));
713
734
  }
714
735
  return summaries;
@@ -754,108 +775,11 @@ function probeStateDbRoundTrip(stateDbPath) {
754
775
  }
755
776
  return { ok: true, durationMs };
756
777
  }
757
- function runAgentProbe() {
758
- const config = loadConfig();
759
- // v2: check profiles.agent first
760
- if (config.profiles?.agent) {
761
- const defaultName = config.defaults?.agent;
762
- const profileCount = Object.keys(config.profiles.agent).length;
763
- if (profileCount === 0) {
764
- return {
765
- name: "agent-profile",
766
- kind: "deterministic",
767
- status: "unknown",
768
- confidence: "high",
769
- message: "No agent profiles configured in profiles.agent.",
770
- };
771
- }
772
- const profileName = defaultName ?? Object.keys(config.profiles.agent)[0];
773
- const profile = config.profiles.agent[profileName];
774
- return {
775
- name: "agent-profile",
776
- kind: "deterministic",
777
- status: "pass",
778
- confidence: "high",
779
- message: `v2 agent profile "${profileName}" configured (platform: ${profile?.platform ?? "unknown"}).`,
780
- evidence: { profile: profileName, platform: profile?.platform, profileCount },
781
- };
782
- }
783
- if (!config.profiles?.agent && !config.defaults?.agent) {
784
- return {
785
- name: "agent-profile",
786
- kind: "deterministic",
787
- status: "unknown",
788
- confidence: "high",
789
- message: "No agent config present.",
790
- };
791
- }
792
- let profile;
793
- try {
794
- profile = requireAgentProfile(config);
795
- }
796
- catch (error) {
797
- return {
798
- name: "agent-profile",
799
- kind: "deterministic",
800
- status: "warn",
801
- confidence: "high",
802
- message: error instanceof Error ? error.message : String(error),
803
- };
804
- }
805
- if (profile.sdkMode === true) {
806
- return {
807
- name: "agent-profile",
808
- kind: "deterministic",
809
- status: profile.model ? "pass" : "warn",
810
- confidence: "high",
811
- message: profile.model
812
- ? `SDK mode profile "${profile.name}" is configured.`
813
- : `SDK mode profile "${profile.name}" has no explicit model.`,
814
- evidence: { profile: profile.name, sdkMode: true, model: profile.model ?? null },
815
- };
816
- }
817
- const detections = detectAgentCliProfiles(config);
818
- const detection = detections.find((entry) => entry.name === profile.name);
819
- if (!detection?.available) {
820
- return {
821
- name: "agent-profile",
822
- kind: "deterministic",
823
- status: "fail",
824
- confidence: "high",
825
- message: `Default agent profile "${profile.name}" is not available on PATH.`,
826
- evidence: { profile: profile.name, bin: profile.bin },
827
- };
828
- }
829
- const version = spawnSync(profile.bin, ["--version"], { encoding: "utf8", timeout: 5_000 });
830
- if ((version.status ?? 1) !== 0) {
831
- return {
832
- name: "agent-profile",
833
- kind: "deterministic",
834
- status: "warn",
835
- confidence: "medium",
836
- message: `Agent binary "${profile.bin}" was found but \`--version\` failed.`,
837
- evidence: {
838
- profile: profile.name,
839
- bin: profile.bin,
840
- exitCode: version.status ?? null,
841
- stderr: (version.stderr ?? "").trim(),
842
- },
843
- };
844
- }
845
- return {
846
- name: "agent-profile",
847
- kind: "deterministic",
848
- status: "pass",
849
- confidence: "high",
850
- message: `Agent profile "${profile.name}" is available.`,
851
- evidence: { profile: profile.name, bin: profile.bin, version: (version.stdout ?? "").trim() },
852
- };
853
- }
854
778
  /**
855
779
  * Parse a `--window-compare <duration>` shorthand into two adjacent windows
856
780
  * (current, prior). Duration syntax matches {@link parseHealthSince}.
857
781
  */
858
- function resolveWindowCompare(duration) {
782
+ function resolveWindowCompare(duration, now = () => Date.now()) {
859
783
  const trimmed = duration.trim();
860
784
  const durationMatch = trimmed.match(/^(\d+)([dhm])$/i);
861
785
  if (!durationMatch) {
@@ -868,10 +792,10 @@ function resolveWindowCompare(duration) {
868
792
  }
869
793
  const multiplier = unit === "h" ? 60 * 60 * 1000 : unit === "m" ? 60 * 1000 : 24 * 60 * 60 * 1000;
870
794
  const ms = amount * multiplier;
871
- const now = Date.now();
872
- const currentSince = new Date(now - ms).toISOString();
873
- const currentUntil = new Date(now).toISOString();
874
- const priorSince = new Date(now - 2 * ms).toISOString();
795
+ const nowMs = now();
796
+ const currentSince = new Date(nowMs - ms).toISOString();
797
+ const currentUntil = new Date(nowMs).toISOString();
798
+ const priorSince = new Date(nowMs - 2 * ms).toISOString();
875
799
  const priorUntil = currentSince;
876
800
  return [
877
801
  { name: "current", since: currentSince, until: currentUntil },
@@ -919,10 +843,14 @@ const INTERESTING_DELTA_PATHS = [
919
843
  "improve.memoryInference.written",
920
844
  "improve.memoryInference.yieldRate",
921
845
  "improve.memoryInference.skippedNoFacts",
846
+ "improve.memoryInference.htmlErrorCount",
922
847
  "improve.graphExtraction.cacheHitRate",
923
848
  "improve.graphExtraction.failures",
849
+ "improve.graphExtraction.htmlErrors",
924
850
  "improve.sessionExtraction.sessionsScanned",
925
851
  "improve.sessionExtraction.proposalsCreated",
852
+ "improve.autoAccept.promoted",
853
+ "improve.autoAccept.validationFailed",
926
854
  "improve.wallTime.medianMs",
927
855
  "improve.wallTime.p95Ms",
928
856
  ];
@@ -954,7 +882,7 @@ function computeDeltas(first, last) {
954
882
  }
955
883
  return out;
956
884
  }
957
- function buildWindowMetrics(db, stateDbPath, since, until) {
885
+ function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now()) {
958
886
  const taskRows = queryTaskHistory(db, { since }).filter((row) => {
959
887
  const startMs = new Date(row.started_at).getTime();
960
888
  const untilMs = new Date(until).getTime();
@@ -964,7 +892,7 @@ function buildWindowMetrics(db, stateDbPath, since, until) {
964
892
  const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
965
893
  const failedTaskRows = taskRows.filter((row) => row.status === "failed");
966
894
  const activeRows = taskRows.filter((row) => row.status === "active");
967
- const stuckActiveRuns = activeRows.filter((row) => Date.now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
895
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
968
896
  const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
969
897
  const promptFailures = promptRows.filter((row) => {
970
898
  const detail = parseTaskMetadata(row).detail;
@@ -1020,8 +948,9 @@ function validateAkmHealthOptions(options) {
1020
948
  }
1021
949
  export function akmHealth(options = {}) {
1022
950
  validateAkmHealthOptions(options);
951
+ const now = options.now ?? (() => Date.now());
1023
952
  const since = parseHealthSince(options.since);
1024
- const stateDbPath = getStateDbPathInDataDir();
953
+ const stateDbPath = options.stateDbPath ?? getStateDbPathInDataDir();
1025
954
  const hardChecks = [];
1026
955
  const advisories = [];
1027
956
  const getExecutionLogCandidatesFn = options.getExecutionLogCandidatesFn ?? getExecutionLogCandidates;
@@ -1033,37 +962,17 @@ export function akmHealth(options = {}) {
1033
962
  throw new ConfigError(`Unable to open state.db: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
1034
963
  }
1035
964
  try {
1036
- const tables = db
1037
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('events', 'task_history', 'proposals', 'schema_migrations') ORDER BY name")
1038
- .all();
965
+ const tables = listExistingTableNames(db, ["events", "task_history", "proposals", "schema_migrations"]);
1039
966
  const tableNames = tables.map((row) => row.name).sort();
1040
967
  const requiredTables = ["events", "proposals", "schema_migrations", "task_history"];
1041
968
  const missingTables = requiredTables.filter((name) => !tableNames.includes(name));
1042
- hardChecks.push({
1043
- name: "state-db-schema",
1044
- kind: "deterministic",
1045
- status: missingTables.length === 0 ? "pass" : "fail",
1046
- confidence: "high",
1047
- message: missingTables.length === 0
1048
- ? "state.db opened and required tables are present."
1049
- : `state.db is missing required tables: ${missingTables.join(", ")}`,
1050
- evidence: { path: stateDbPath, tables: tableNames },
1051
- });
1052
969
  const probe = probeStateDbRoundTrip(stateDbPath);
1053
- hardChecks.push({
1054
- name: "state-db-round-trip",
1055
- kind: "deterministic",
1056
- status: probe.ok ? "pass" : "fail",
1057
- confidence: "high",
1058
- message: probe.ok ? "state.db append/read round-trip succeeded." : `state.db round-trip failed: ${probe.error}`,
1059
- evidence: { path: stateDbPath, durationMs: probe.durationMs },
1060
- });
1061
970
  const taskRows = queryTaskHistory(db, { since });
1062
971
  const taskRowsWithLogs = taskRows.filter((row) => row.log_path !== null);
1063
972
  const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
1064
973
  const failedTaskRows = taskRows.filter((row) => row.status === "failed");
1065
974
  const activeRows = taskRows.filter((row) => row.status === "active");
1066
- const stuckActiveRuns = activeRows.filter((row) => Date.now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
975
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
1067
976
  const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
1068
977
  const promptFailures = promptRows.filter((row) => {
1069
978
  const detail = parseTaskMetadata(row).detail;
@@ -1072,51 +981,7 @@ export function akmHealth(options = {}) {
1072
981
  const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
1073
982
  const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
1074
983
  const agentFailureRate = promptRows.length === 0 ? 0 : promptFailures.length / promptRows.length;
1075
- hardChecks.push({
1076
- name: "task-history-read",
1077
- kind: "deterministic",
1078
- status: "pass",
1079
- confidence: "high",
1080
- message: `Read ${taskRows.length} task-history row(s) since ${since}.`,
1081
- evidence: { rows: taskRows.length, since },
1082
- });
1083
- hardChecks.push({
1084
- name: "task-log-backing",
1085
- kind: "deterministic",
1086
- status: logBackingRate === 1 ? "pass" : "fail",
1087
- confidence: "high",
1088
- message: logBackingRate === 1
1089
- ? "Every task_history log_path resolved on disk."
1090
- : `${taskRowsWithLogs.length - existingLogRows.length} task log(s) referenced in task_history are missing.`,
1091
- evidence: { totalWithLogs: taskRowsWithLogs.length, existingLogs: existingLogRows.length },
1092
- });
1093
- hardChecks.push({
1094
- name: "active-runs",
1095
- kind: "deterministic",
1096
- status: stuckActiveRuns === 0 ? "pass" : "warn",
1097
- confidence: "high",
1098
- message: stuckActiveRuns === 0
1099
- ? "No active task runs exceeded the stale threshold."
1100
- : `${stuckActiveRuns} active task run(s) are older than ${Math.round(ACTIVE_RUN_WARN_MS / 60000)} minutes.`,
1101
- evidence: { stuckActiveRuns },
1102
- });
1103
- hardChecks.push(runAgentProbe());
1104
984
  const semanticStatus = readSemanticStatus();
1105
- advisories.push({
1106
- name: "semantic-search-runtime",
1107
- kind: "deterministic",
1108
- status: !semanticStatus ||
1109
- semanticStatus.status === "pending" ||
1110
- semanticStatus.status === "ready-js" ||
1111
- semanticStatus.status === "ready-vec"
1112
- ? "pass"
1113
- : "warn",
1114
- confidence: "medium",
1115
- message: semanticStatus
1116
- ? `Semantic search status: ${semanticStatus.status}`
1117
- : "No semantic-search runtime status recorded yet.",
1118
- evidence: semanticStatus ? { ...semanticStatus } : undefined,
1119
- });
1120
985
  const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
1121
986
  const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
1122
987
  const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
@@ -1132,7 +997,7 @@ export function akmHealth(options = {}) {
1132
997
  improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
1133
998
  let sessionLogEntries = [];
1134
999
  try {
1135
- const sinceDays = Math.max(0, Math.ceil((Date.now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
1000
+ const sinceDays = Math.max(0, Math.ceil((now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
1136
1001
  sessionLogEntries = getExecutionLogCandidatesFn(sinceDays).map((entry) => ({
1137
1002
  topic: entry.topic,
1138
1003
  frequency: entry.frequency,
@@ -1143,46 +1008,33 @@ export function akmHealth(options = {}) {
1143
1008
  catch {
1144
1009
  sessionLogEntries = [];
1145
1010
  }
1146
- // session-log-failures: demoted to informational the ERROR_PATTERNS regex
1147
- // scans pre-LLM session text and produces false positives on diagnostic
1148
- // conversation. It does not gate the real extraction pipeline (akmExtract).
1149
- // Never triggers warn; kept for backward-compat visibility only.
1150
- advisories.push({
1151
- name: "session-log-failures",
1152
- kind: "heuristic",
1153
- status: "pass",
1154
- confidence: "low",
1155
- message: sessionLogEntries.length === 0
1156
- ? "No repeated external session-log failure patterns were detected."
1157
- : `${sessionLogEntries.length} raw session-log keyword match(es) detected (pre-LLM, informational only).`,
1158
- evidence: { candidates: sessionLogEntries.slice(0, 5) },
1159
- });
1160
- const sx = improveSummary.sessionExtraction;
1161
- const sxWarnReasons = [];
1162
- if (sx.warnings > 0)
1163
- sxWarnReasons.push(`${sx.warnings} harness error(s)`);
1164
- if (sx.ran && sx.sessionsScanned >= 5 && sx.proposalsCreated === 0)
1165
- sxWarnReasons.push("no proposals generated across scanned sessions");
1166
- advisories.push({
1167
- name: "session-extraction",
1168
- kind: "heuristic",
1169
- status: sxWarnReasons.length > 0 ? "warn" : "pass",
1170
- confidence: sx.ran ? "medium" : "low",
1171
- message: sx.ran
1172
- ? sxWarnReasons.length > 0
1173
- ? `Session extraction degraded: ${sxWarnReasons.join("; ")}.`
1174
- : `Session extraction healthy: ${sx.sessionsScanned} scanned, ${sx.sessionsExtracted} extracted, ${sx.proposalsCreated} proposal(s) created.`
1175
- : "Session extraction not active (feature disabled or no harness available).",
1176
- evidence: {
1177
- ran: sx.ran,
1178
- sessionsScanned: sx.sessionsScanned,
1179
- sessionsExtracted: sx.sessionsExtracted,
1180
- sessionsSkipped: sx.sessionsSkipped,
1181
- proposalsCreated: sx.proposalsCreated,
1182
- warnings: sx.warnings,
1183
- durationMs: sx.durationMs,
1184
- },
1185
- });
1011
+ // Run the ordered health-check registry. Each check projects the shared
1012
+ // context computed above into one HealthCheckResult; `channel` routes it to
1013
+ // hardChecks or advisories. Declaration order in HEALTH_CHECKS is the
1014
+ // emission order see src/commands/health/checks.ts.
1015
+ const checkContext = {
1016
+ stateDbPath,
1017
+ since,
1018
+ tableNames,
1019
+ missingTables,
1020
+ probe,
1021
+ taskRowCount: taskRows.length,
1022
+ taskRowsWithLogsCount: taskRowsWithLogs.length,
1023
+ existingLogRowsCount: existingLogRows.length,
1024
+ logBackingRate,
1025
+ stuckActiveRuns,
1026
+ semanticStatus,
1027
+ sessionLogEntries,
1028
+ sessionExtraction: improveSummary.sessionExtraction,
1029
+ autoAccept: improveSummary.autoAccept,
1030
+ };
1031
+ for (const check of HEALTH_CHECKS) {
1032
+ const result = check.run(checkContext);
1033
+ if (check.channel === "hard")
1034
+ hardChecks.push(result);
1035
+ else
1036
+ advisories.push(result);
1037
+ }
1186
1038
  const metrics = {
1187
1039
  taskFailRate: roundRate(taskFailRate),
1188
1040
  agentFailureRate: roundRate(agentFailureRate),
@@ -1196,7 +1048,7 @@ export function akmHealth(options = {}) {
1196
1048
  // ── Window-compare mode (Phase 3) ─────────────────────────────────────
1197
1049
  let windowSpecs;
1198
1050
  if (options.windowCompare) {
1199
- windowSpecs = resolveWindowCompare(options.windowCompare);
1051
+ windowSpecs = resolveWindowCompare(options.windowCompare, now);
1200
1052
  }
1201
1053
  else if (options.windows && options.windows.length > 0) {
1202
1054
  windowSpecs = options.windows;
@@ -1209,8 +1061,8 @@ export function akmHealth(options = {}) {
1209
1061
  if (windowSpecs && db) {
1210
1062
  windowResults = windowSpecs.map((spec) => {
1211
1063
  const winSince = parseHealthSince(spec.since);
1212
- const winUntil = spec.until ? parseHealthSince(spec.until) : new Date().toISOString();
1213
- const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil);
1064
+ const winUntil = spec.until ? parseHealthSince(spec.until) : new Date(now()).toISOString();
1065
+ const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil, now);
1214
1066
  return {
1215
1067
  name: spec.name,
1216
1068
  since: winSince,