akm-cli 0.8.2 → 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 (316) hide show
  1. package/CHANGELOG.md +187 -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/cli/config-migrate.js +6 -6
  16. package/dist/cli/config-validate.js +4 -4
  17. package/dist/cli/confirm.js +3 -3
  18. package/dist/cli/parse-args.js +1 -1
  19. package/dist/cli/shared.js +51 -14
  20. package/dist/cli-node.mjs +26 -0
  21. package/dist/cli.js +171 -3862
  22. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  23. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  24. package/dist/commands/agent/contribute-cli.js +200 -0
  25. package/dist/commands/completions.js +1 -1
  26. package/dist/commands/config-cli.js +240 -3
  27. package/dist/commands/config-edit.js +344 -0
  28. package/dist/commands/db-cli.js +2 -2
  29. package/dist/commands/env/env-cli.js +529 -0
  30. package/dist/commands/env/env.js +410 -0
  31. package/dist/commands/env/secret-cli.js +259 -0
  32. package/dist/commands/{secret.js → env/secret.js} +6 -47
  33. package/dist/commands/events.js +4 -4
  34. package/dist/commands/feedback-cli.js +18 -34
  35. package/dist/commands/graph/graph-cli.js +132 -0
  36. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  37. package/dist/commands/health/checks.js +279 -0
  38. package/dist/commands/health.js +94 -262
  39. package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
  40. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  41. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  42. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  43. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  44. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  45. package/dist/commands/{extract.js → improve/extract.js} +185 -26
  46. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
  47. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  48. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  49. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  50. package/dist/commands/{improve.js → improve/improve.js} +509 -245
  51. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  52. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  53. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  54. package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
  55. package/dist/commands/improve/session-asset.js +248 -0
  56. package/dist/commands/lint/agent-linter.js +1 -1
  57. package/dist/commands/lint/base-linter.js +55 -37
  58. package/dist/commands/lint/command-linter.js +1 -1
  59. package/dist/commands/lint/default-linter.js +1 -1
  60. package/dist/commands/lint/env-key-rules.js +1 -1
  61. package/dist/commands/lint/index.js +19 -25
  62. package/dist/commands/lint/knowledge-linter.js +1 -1
  63. package/dist/commands/lint/memory-linter.js +1 -1
  64. package/dist/commands/lint/registry.js +8 -8
  65. package/dist/commands/lint/skill-linter.js +1 -1
  66. package/dist/commands/lint/task-linter.js +1 -1
  67. package/dist/commands/lint/workflow-linter.js +1 -1
  68. package/dist/commands/lint.js +1 -1
  69. package/dist/commands/observability-cli.js +244 -0
  70. package/dist/commands/proposal/drain-policies.js +3 -3
  71. package/dist/commands/proposal/drain.js +15 -10
  72. package/dist/commands/proposal/proposal-cli.js +478 -0
  73. package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
  74. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  75. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  76. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  77. package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
  78. package/dist/commands/{curate.js → read/curate.js} +7 -7
  79. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  80. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  81. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  82. package/dist/commands/read/search-cli.js +207 -0
  83. package/dist/commands/{search.js → read/search.js} +22 -27
  84. package/dist/commands/{show.js → read/show.js} +31 -45
  85. package/dist/commands/registry-cli.js +8 -8
  86. package/dist/commands/remember.js +8 -8
  87. package/dist/commands/sources/add-cli.js +293 -0
  88. package/dist/commands/{history.js → sources/history.js} +27 -25
  89. package/dist/commands/{info.js → sources/info.js} +6 -6
  90. package/dist/commands/{init.js → sources/init.js} +6 -6
  91. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  92. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  93. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  94. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  95. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  96. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  97. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  98. package/dist/commands/sources/sources-cli.js +305 -0
  99. package/dist/commands/sources/stash-cli.js +219 -0
  100. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  101. package/dist/commands/tasks/default-tasks.js +173 -0
  102. package/dist/commands/tasks/tasks-cli.js +210 -0
  103. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  104. package/dist/commands/wiki-cli.js +307 -0
  105. package/dist/commands/workflow-cli.js +329 -0
  106. package/dist/core/action-contributors.js +1 -1
  107. package/dist/core/assert.js +40 -0
  108. package/dist/core/asset/asset-create.js +54 -0
  109. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  110. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  111. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  112. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  113. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  114. package/dist/core/best-effort.js +64 -0
  115. package/dist/core/common.js +32 -18
  116. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  117. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  118. package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
  119. package/dist/core/config/config-types.js +16 -0
  120. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  121. package/dist/core/{config.js → config/config.js} +10 -8
  122. package/dist/core/env-secret-ref.js +90 -0
  123. package/dist/core/errors.js +13 -3
  124. package/dist/core/events.js +27 -4
  125. package/dist/core/file-lock.js +1 -1
  126. package/dist/core/improve-types.js +48 -0
  127. package/dist/core/lesson-lint.js +2 -2
  128. package/dist/core/paths.js +2 -2
  129. package/dist/core/ripgrep/install.js +2 -2
  130. package/dist/core/ripgrep/resolve.js +2 -2
  131. package/dist/core/state-db.js +88 -46
  132. package/dist/core/text-truncation.js +148 -0
  133. package/dist/core/time.js +1 -1
  134. package/dist/core/write-source.js +98 -85
  135. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  136. package/dist/indexer/{db.js → db/db.js} +126 -116
  137. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  138. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  139. package/dist/indexer/ensure-index.js +4 -4
  140. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  141. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  142. package/dist/indexer/indexer.js +37 -30
  143. package/dist/indexer/init.js +54 -0
  144. package/dist/indexer/manifest.js +10 -10
  145. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
  146. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  147. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  148. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  149. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  150. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  151. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  152. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  153. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  154. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  155. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  156. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  157. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  158. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  159. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  160. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  161. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  162. package/dist/integrations/agent/builder-shared.js +39 -0
  163. package/dist/integrations/agent/builders.js +14 -81
  164. package/dist/integrations/agent/config.js +6 -4
  165. package/dist/integrations/agent/detect.js +1 -1
  166. package/dist/integrations/agent/index.js +23 -8
  167. package/dist/integrations/agent/prompts.js +2 -3
  168. package/dist/integrations/agent/runner.js +22 -3
  169. package/dist/integrations/agent/spawn.js +9 -10
  170. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  171. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  172. package/dist/integrations/harnesses/claude/index.js +64 -0
  173. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
  174. package/dist/integrations/harnesses/index.js +144 -0
  175. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  176. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  177. package/dist/integrations/harnesses/opencode/index.js +59 -0
  178. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  179. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  180. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  181. package/dist/integrations/harnesses/types.js +43 -0
  182. package/dist/integrations/lockfile.js +7 -16
  183. package/dist/integrations/session-logs/index.js +82 -9
  184. package/dist/llm/call-ai.js +4 -4
  185. package/dist/llm/client.js +131 -6
  186. package/dist/llm/embedder.js +6 -6
  187. package/dist/llm/embedders/local.js +9 -22
  188. package/dist/llm/embedders/remote.js +2 -2
  189. package/dist/llm/embedders/types.js +1 -1
  190. package/dist/llm/graph-extract.js +31 -12
  191. package/dist/llm/index-passes.js +1 -1
  192. package/dist/llm/memory-infer.js +12 -5
  193. package/dist/llm/metadata-enhance.js +2 -2
  194. package/dist/output/context.js +6 -44
  195. package/dist/output/renderers.js +88 -58
  196. package/dist/output/shapes/curate.js +7 -3
  197. package/dist/output/shapes/distill.js +7 -3
  198. package/dist/output/shapes/env-list.js +18 -16
  199. package/dist/output/shapes/events.js +5 -4
  200. package/dist/output/shapes/helpers.js +2 -4
  201. package/dist/output/shapes/history.js +7 -3
  202. package/dist/output/shapes/passthrough.js +8 -11
  203. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  204. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  205. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  206. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  207. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  208. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  209. package/dist/output/shapes/registry-search.js +7 -3
  210. package/dist/output/shapes/registry.js +12 -0
  211. package/dist/output/shapes/search.js +7 -3
  212. package/dist/output/shapes/secret-list.js +18 -16
  213. package/dist/output/shapes/show.js +7 -3
  214. package/dist/output/shapes.js +55 -30
  215. package/dist/output/text/add.js +2 -3
  216. package/dist/output/text/clone.js +2 -3
  217. package/dist/output/text/config.js +2 -3
  218. package/dist/output/text/curate.js +4 -3
  219. package/dist/output/text/distill.js +2 -3
  220. package/dist/output/text/enable-disable.js +5 -4
  221. package/dist/output/text/env.js +13 -0
  222. package/dist/output/text/events.js +5 -4
  223. package/dist/output/text/feedback.js +4 -3
  224. package/dist/output/text/helpers.js +54 -39
  225. package/dist/output/text/history.js +2 -3
  226. package/dist/output/text/import.js +2 -3
  227. package/dist/output/text/index.js +2 -3
  228. package/dist/output/text/info.js +2 -3
  229. package/dist/output/text/init.js +2 -3
  230. package/dist/output/text/list.js +2 -3
  231. package/dist/output/text/proposal/producer.js +9 -0
  232. package/dist/output/text/proposal/proposal.js +13 -0
  233. package/dist/output/text/registry-commands.js +8 -7
  234. package/dist/output/text/registry.js +12 -0
  235. package/dist/output/text/remember.js +4 -3
  236. package/dist/output/text/remove.js +2 -3
  237. package/dist/output/text/save.js +2 -3
  238. package/dist/output/text/search.js +4 -3
  239. package/dist/output/text/show.js +4 -3
  240. package/dist/output/text/update.js +2 -3
  241. package/dist/output/text/upgrade.js +2 -3
  242. package/dist/output/text/wiki.js +12 -11
  243. package/dist/output/text/workflow.js +12 -10
  244. package/dist/output/text.js +66 -32
  245. package/dist/registry/build-index.js +11 -10
  246. package/dist/registry/factory.js +1 -1
  247. package/dist/registry/origin-resolve.js +1 -1
  248. package/dist/registry/providers/index.js +2 -2
  249. package/dist/registry/providers/skills-sh.js +91 -72
  250. package/dist/registry/providers/static-index.js +75 -52
  251. package/dist/registry/resolve.js +3 -3
  252. package/dist/runtime.js +242 -0
  253. package/dist/scripts/migrate-storage.js +1594 -673
  254. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
  255. package/dist/setup/detect.js +311 -9
  256. package/dist/setup/harness-config-import.js +6 -120
  257. package/dist/setup/setup.js +454 -43
  258. package/dist/sources/include.js +1 -1
  259. package/dist/sources/provider-factory.js +2 -2
  260. package/dist/sources/providers/filesystem.js +3 -3
  261. package/dist/sources/providers/git.js +9 -9
  262. package/dist/sources/providers/index.js +4 -4
  263. package/dist/sources/providers/npm.js +6 -6
  264. package/dist/sources/providers/provider-utils.js +13 -20
  265. package/dist/sources/providers/sync-from-ref.js +5 -5
  266. package/dist/sources/providers/tar-utils.js +2 -2
  267. package/dist/sources/providers/website.js +2 -2
  268. package/dist/sources/resolve.js +5 -5
  269. package/dist/sources/website-ingest.js +5 -5
  270. package/dist/storage/database.js +102 -0
  271. package/dist/storage/engines/sqlite-migrations.js +42 -0
  272. package/dist/storage/locations.js +25 -0
  273. package/dist/storage/repositories/index-db.js +43 -0
  274. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  275. package/dist/tasks/backends/cron.js +4 -4
  276. package/dist/tasks/backends/exec-utils.js +32 -0
  277. package/dist/tasks/backends/index.js +3 -3
  278. package/dist/tasks/backends/launchd.js +7 -14
  279. package/dist/tasks/backends/schtasks.js +7 -16
  280. package/dist/tasks/embedded.js +71 -0
  281. package/dist/tasks/parser.js +2 -2
  282. package/dist/tasks/resolveAkmBin.js +1 -1
  283. package/dist/tasks/runner.js +28 -15
  284. package/dist/tasks/schedule.js +1 -1
  285. package/dist/tasks/validator.js +7 -7
  286. package/dist/text-import-hook.mjs +51 -0
  287. package/dist/version.js +2 -1
  288. package/dist/wiki/wiki.js +7 -7
  289. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  290. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  291. package/dist/workflows/cli.js +1 -1
  292. package/dist/workflows/db.js +50 -32
  293. package/dist/workflows/parser.js +4 -4
  294. package/dist/workflows/renderer.js +5 -5
  295. package/dist/workflows/runtime/agent-identity.js +56 -0
  296. package/dist/workflows/runtime/checkin.js +57 -0
  297. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  298. package/dist/workflows/validate-summary.js +82 -0
  299. package/docs/README.md +1 -1
  300. package/docs/data-and-telemetry.md +6 -6
  301. package/package.json +16 -8
  302. package/dist/commands/add-cli.js +0 -279
  303. package/dist/commands/env.js +0 -213
  304. package/dist/integrations/agent/sdk-runner.js +0 -126
  305. package/dist/output/shapes/vault-list.js +0 -19
  306. package/dist/output/text/proposal-producer.js +0 -8
  307. package/dist/output/text/proposal.js +0 -12
  308. package/dist/output/text/vault.js +0 -16
  309. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  310. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  311. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  312. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  313. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  314. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  315. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  316. /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";
@@ -104,6 +102,7 @@ function createUnknownImproveMetrics() {
104
102
  ran: false,
105
103
  considered: 0,
106
104
  cacheHits: 0,
105
+ retryAttempts: 0,
107
106
  freshAttempts: 0,
108
107
  splitParents: 0,
109
108
  written: 0,
@@ -111,6 +110,7 @@ function createUnknownImproveMetrics() {
111
110
  skippedChildExists: 0,
112
111
  skippedAborted: 0,
113
112
  unaccounted: 0,
113
+ htmlErrorCount: 0,
114
114
  yieldEligibleRuns: 0,
115
115
  yieldEligibleConsidered: 0,
116
116
  yieldEligibleWritten: 0,
@@ -128,6 +128,8 @@ function createUnknownImproveMetrics() {
128
128
  cacheHitRate: 0,
129
129
  truncations: 0,
130
130
  failures: 0,
131
+ htmlErrors: 0,
132
+ retryAttempts: 0,
131
133
  durationMs: 0,
132
134
  },
133
135
  sessionExtraction: {
@@ -337,18 +339,26 @@ function projectRunMetrics(result) {
337
339
  metrics.consolidation.mergedSecondaries += toFiniteNumber(consolidation.mergedSecondaries);
338
340
  metrics.consolidation.failedChunkMemories += toFiniteNumber(consolidation.failedChunkMemories);
339
341
  // 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.
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.
343
346
  const skipReasons = consolidation.skipReasons;
344
347
  if (Array.isArray(skipReasons)) {
345
348
  for (const entry of skipReasons) {
346
349
  if (!entry || typeof entry !== "object")
347
350
  continue;
348
- const reason = entry.reason;
349
- if (typeof reason !== "string" || !reason.trim())
351
+ const skips = entry.skips;
352
+ if (!Array.isArray(skips))
350
353
  continue;
351
- 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
+ }
352
362
  }
353
363
  }
354
364
  }
@@ -358,12 +368,14 @@ function projectRunMetrics(result) {
358
368
  const writtenFacts = toFiniteNumber(memoryInference.writtenFacts);
359
369
  metrics.memoryInference.considered += considered;
360
370
  metrics.memoryInference.cacheHits += toFiniteNumber(memoryInference.cacheHits);
371
+ metrics.memoryInference.retryAttempts += toFiniteNumber(memoryInference.retryAttempts);
361
372
  metrics.memoryInference.splitParents += toFiniteNumber(memoryInference.splitParents);
362
373
  metrics.memoryInference.written += writtenFacts;
363
374
  metrics.memoryInference.skippedNoFacts += toFiniteNumber(memoryInference.skippedNoFacts);
364
375
  metrics.memoryInference.skippedChildExists += toFiniteNumber(memoryInference.skippedChildExists);
365
376
  metrics.memoryInference.skippedAborted += toFiniteNumber(memoryInference.skippedAborted);
366
377
  metrics.memoryInference.unaccounted += toFiniteNumber(memoryInference.unaccounted);
378
+ metrics.memoryInference.htmlErrorCount += toFiniteNumber(memoryInference.htmlErrorCount);
367
379
  // Yield-rate gating: pre-cache-feature envelopes lack the `cacheHits`
368
380
  // field entirely. Treating their `considered` as freshAttempts (since
369
381
  // cacheHits=0) is mathematically tempting but operationally wrong —
@@ -391,6 +403,8 @@ function projectRunMetrics(result) {
391
403
  metrics.graphExtraction.cacheMisses += toFiniteNumber(telemetry.cacheMisses);
392
404
  metrics.graphExtraction.truncations += toFiniteNumber(telemetry.truncationCount);
393
405
  metrics.graphExtraction.failures += toFiniteNumber(telemetry.failureCount);
406
+ metrics.graphExtraction.htmlErrors += toFiniteNumber(telemetry.htmlErrorCount);
407
+ metrics.graphExtraction.retryAttempts += toFiniteNumber(telemetry.retryAttempts);
394
408
  }
395
409
  }
396
410
  metrics.graphExtraction.durationMs += toFiniteNumber(result.graphExtractionDurationMs);
@@ -520,6 +534,7 @@ function mergeImproveMetrics(dst, src) {
520
534
  dst.memoryInference.skippedChildExists += src.memoryInference.skippedChildExists;
521
535
  dst.memoryInference.skippedAborted += src.memoryInference.skippedAborted;
522
536
  dst.memoryInference.unaccounted += src.memoryInference.unaccounted;
537
+ dst.memoryInference.htmlErrorCount += src.memoryInference.htmlErrorCount;
523
538
  dst.memoryInference.yieldEligibleRuns += src.memoryInference.yieldEligibleRuns;
524
539
  dst.memoryInference.yieldEligibleConsidered += src.memoryInference.yieldEligibleConsidered;
525
540
  dst.memoryInference.yieldEligibleWritten += src.memoryInference.yieldEligibleWritten;
@@ -531,6 +546,7 @@ function mergeImproveMetrics(dst, src) {
531
546
  dst.graphExtraction.cacheMisses += src.graphExtraction.cacheMisses;
532
547
  dst.graphExtraction.truncations += src.graphExtraction.truncations;
533
548
  dst.graphExtraction.failures += src.graphExtraction.failures;
549
+ dst.graphExtraction.htmlErrors += src.graphExtraction.htmlErrors;
534
550
  dst.graphExtraction.durationMs += src.graphExtraction.durationMs;
535
551
  dst.sessionExtraction.sessionsScanned += src.sessionExtraction.sessionsScanned;
536
552
  dst.sessionExtraction.sessionsExtracted += src.sessionExtraction.sessionsExtracted;
@@ -539,15 +555,9 @@ function mergeImproveMetrics(dst, src) {
539
555
  dst.sessionExtraction.warnings += src.sessionExtraction.warnings;
540
556
  dst.sessionExtraction.durationMs += src.sessionExtraction.durationMs;
541
557
  }
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
558
  function summarizeImproveRuns(db, since, until) {
549
559
  const accum = createUnknownImproveMetrics();
550
- const rows = loadImproveRunRows(db, since, until);
560
+ const rows = queryImproveRuns(db, since, until);
551
561
  // Per-phase wall-time samples. Each entry is one envelope's durationMs for
552
562
  // that phase. Phases that did not run on a given envelope are simply
553
563
  // omitted (NOT counted as 0) so the median/p95 reflect actual phase work.
@@ -666,10 +676,7 @@ function loadTaskIntervals(db, since, until) {
666
676
  const untilMs = until ? new Date(until).getTime() : Number.POSITIVE_INFINITY;
667
677
  const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
668
678
  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));
679
+ const rows = queryCompletedTaskIntervals(db, widenedSince, widenedUntil);
673
680
  const intervals = [];
674
681
  for (const row of rows) {
675
682
  const startMs = new Date(row.started_at).getTime();
@@ -702,18 +709,27 @@ function findContainingTaskInterval(timestampMs, intervals) {
702
709
  return undefined;
703
710
  }
704
711
  function buildPerRunSummaries(db, since, until) {
705
- const rows = loadImproveRunRows(db, since, until);
712
+ const rows = queryImproveRuns(db, since, until);
706
713
  const taskIntervals = loadTaskIntervals(db, since, until);
707
714
  const summaries = [];
708
715
  for (const row of rows) {
709
716
  const startMs = new Date(row.started_at).getTime();
710
717
  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;
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
+ }
717
733
  summaries.push(projectImproveRunSummary(row, wallTimeMs));
718
734
  }
719
735
  return summaries;
@@ -759,108 +775,11 @@ function probeStateDbRoundTrip(stateDbPath) {
759
775
  }
760
776
  return { ok: true, durationMs };
761
777
  }
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
778
  /**
860
779
  * Parse a `--window-compare <duration>` shorthand into two adjacent windows
861
780
  * (current, prior). Duration syntax matches {@link parseHealthSince}.
862
781
  */
863
- function resolveWindowCompare(duration) {
782
+ function resolveWindowCompare(duration, now = () => Date.now()) {
864
783
  const trimmed = duration.trim();
865
784
  const durationMatch = trimmed.match(/^(\d+)([dhm])$/i);
866
785
  if (!durationMatch) {
@@ -873,10 +792,10 @@ function resolveWindowCompare(duration) {
873
792
  }
874
793
  const multiplier = unit === "h" ? 60 * 60 * 1000 : unit === "m" ? 60 * 1000 : 24 * 60 * 60 * 1000;
875
794
  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();
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();
880
799
  const priorUntil = currentSince;
881
800
  return [
882
801
  { name: "current", since: currentSince, until: currentUntil },
@@ -924,8 +843,10 @@ const INTERESTING_DELTA_PATHS = [
924
843
  "improve.memoryInference.written",
925
844
  "improve.memoryInference.yieldRate",
926
845
  "improve.memoryInference.skippedNoFacts",
846
+ "improve.memoryInference.htmlErrorCount",
927
847
  "improve.graphExtraction.cacheHitRate",
928
848
  "improve.graphExtraction.failures",
849
+ "improve.graphExtraction.htmlErrors",
929
850
  "improve.sessionExtraction.sessionsScanned",
930
851
  "improve.sessionExtraction.proposalsCreated",
931
852
  "improve.autoAccept.promoted",
@@ -961,7 +882,7 @@ function computeDeltas(first, last) {
961
882
  }
962
883
  return out;
963
884
  }
964
- function buildWindowMetrics(db, stateDbPath, since, until) {
885
+ function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now()) {
965
886
  const taskRows = queryTaskHistory(db, { since }).filter((row) => {
966
887
  const startMs = new Date(row.started_at).getTime();
967
888
  const untilMs = new Date(until).getTime();
@@ -971,7 +892,7 @@ function buildWindowMetrics(db, stateDbPath, since, until) {
971
892
  const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
972
893
  const failedTaskRows = taskRows.filter((row) => row.status === "failed");
973
894
  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;
895
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
975
896
  const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
976
897
  const promptFailures = promptRows.filter((row) => {
977
898
  const detail = parseTaskMetadata(row).detail;
@@ -1027,8 +948,9 @@ function validateAkmHealthOptions(options) {
1027
948
  }
1028
949
  export function akmHealth(options = {}) {
1029
950
  validateAkmHealthOptions(options);
951
+ const now = options.now ?? (() => Date.now());
1030
952
  const since = parseHealthSince(options.since);
1031
- const stateDbPath = getStateDbPathInDataDir();
953
+ const stateDbPath = options.stateDbPath ?? getStateDbPathInDataDir();
1032
954
  const hardChecks = [];
1033
955
  const advisories = [];
1034
956
  const getExecutionLogCandidatesFn = options.getExecutionLogCandidatesFn ?? getExecutionLogCandidates;
@@ -1040,37 +962,17 @@ export function akmHealth(options = {}) {
1040
962
  throw new ConfigError(`Unable to open state.db: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
1041
963
  }
1042
964
  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();
965
+ const tables = listExistingTableNames(db, ["events", "task_history", "proposals", "schema_migrations"]);
1046
966
  const tableNames = tables.map((row) => row.name).sort();
1047
967
  const requiredTables = ["events", "proposals", "schema_migrations", "task_history"];
1048
968
  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
969
  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
970
  const taskRows = queryTaskHistory(db, { since });
1069
971
  const taskRowsWithLogs = taskRows.filter((row) => row.log_path !== null);
1070
972
  const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
1071
973
  const failedTaskRows = taskRows.filter((row) => row.status === "failed");
1072
974
  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;
975
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
1074
976
  const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
1075
977
  const promptFailures = promptRows.filter((row) => {
1076
978
  const detail = parseTaskMetadata(row).detail;
@@ -1079,51 +981,7 @@ export function akmHealth(options = {}) {
1079
981
  const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
1080
982
  const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
1081
983
  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
984
  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
985
  const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
1128
986
  const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
1129
987
  const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
@@ -1139,7 +997,7 @@ export function akmHealth(options = {}) {
1139
997
  improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
1140
998
  let sessionLogEntries = [];
1141
999
  try {
1142
- 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)));
1143
1001
  sessionLogEntries = getExecutionLogCandidatesFn(sinceDays).map((entry) => ({
1144
1002
  topic: entry.topic,
1145
1003
  frequency: entry.frequency,
@@ -1150,59 +1008,33 @@ export function akmHealth(options = {}) {
1150
1008
  catch {
1151
1009
  sessionLogEntries = [];
1152
1010
  }
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
- });
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
+ }
1206
1038
  const metrics = {
1207
1039
  taskFailRate: roundRate(taskFailRate),
1208
1040
  agentFailureRate: roundRate(agentFailureRate),
@@ -1216,7 +1048,7 @@ export function akmHealth(options = {}) {
1216
1048
  // ── Window-compare mode (Phase 3) ─────────────────────────────────────
1217
1049
  let windowSpecs;
1218
1050
  if (options.windowCompare) {
1219
- windowSpecs = resolveWindowCompare(options.windowCompare);
1051
+ windowSpecs = resolveWindowCompare(options.windowCompare, now);
1220
1052
  }
1221
1053
  else if (options.windows && options.windows.length > 0) {
1222
1054
  windowSpecs = options.windows;
@@ -1229,8 +1061,8 @@ export function akmHealth(options = {}) {
1229
1061
  if (windowSpecs && db) {
1230
1062
  windowResults = windowSpecs.map((spec) => {
1231
1063
  const winSince = parseHealthSince(spec.since);
1232
- const winUntil = spec.until ? parseHealthSince(spec.until) : new Date().toISOString();
1233
- 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);
1234
1066
  return {
1235
1067
  name: spec.name,
1236
1068
  since: winSince,