akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -5,14 +5,13 @@ import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { daysToMs, resolveStashDir } from "../core/common.js";
7
7
  import { loadConfig } from "../core/config/config.js";
8
- import { ConfigError, UsageError } from "../core/errors.js";
9
- import { readEvents } from "../core/events.js";
10
- import { openLogsDatabase } from "../core/logs-db.js";
8
+ import { ConfigError, rethrowIfTestIsolationError, UsageError } from "../core/errors.js";
11
9
  import { classifyPathAccess, describeInaccessiblePath } from "../core/path-access.js";
12
10
  import { getConfigPath, getDataDir, getDbPath, getStateDbPathInDataDir } from "../core/paths.js";
13
- import { listExistingTableNames, listPendingStateMigrations, openStateDatabase } from "../core/state-db.js";
11
+ import { listExistingTableNames, listPendingStateMigrations, openStateDatabaseWithReport, withStateDb, } from "../core/state-db.js";
14
12
  import { DURATION_UNITS, parseDuration, parseSinceToIso } from "../core/time.js";
15
13
  import { probeLlmEndpoint } from "../llm/client.js";
14
+ import { insertEvent } from "../storage/repositories/events-repository.js";
16
15
  import { getExtractOutcomeCountsSince } from "../storage/repositories/extract-sessions-repository.js";
17
16
  import { countImproveRunsSince } from "../storage/repositories/improve-runs-repository.js";
18
17
  import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
@@ -20,22 +19,20 @@ import { getAllEntries } from "../storage/repositories/index-entries-repository.
20
19
  import { queryTaskHistory } from "../storage/repositories/task-history-repository.js";
21
20
  import { getStateDbFreelistInfo, runStateDbQuickCheck } from "../storage/state-db-integrity.js";
22
21
  import { pkgVersion } from "../version.js";
23
- import { collectImproveAdvisories } from "./health/advisories.js";
24
22
  import { HEALTH_CHECKS, probeActiveImproveStrategy, runHealthEngineProbes, runPendingStateMigrationsCheck, SESSION_EXTRACTION_LEDGER_WINDOW_DAYS, } from "./health/checks.js";
23
+ import { collectConfigSkewAdvisory } from "./health/config-skew.js";
25
24
  import { collectDataDirUsageAdvisory } from "./health/data-dir-usage.js";
25
+ import { collectEgressAdvisory } from "./health/egress.js";
26
26
  import { engineLastUsedSince, readLastEngineUsage } from "./health/engine-usage.js";
27
- import { buildImproveSkipSummary, computeWallTimeStats, countAgentFailureReasons, isAgentTaskHistoryRow, roundRate, summarizeImproveCompleted, summarizeImproveRuns, taskFailureDetail, } from "./health/improve-metrics.js";
27
+ import { emptyImproveMetrics, roundRate } from "./health/improve-metrics.js";
28
28
  import { emptyLlmUsageAggregate, readLlmUsageAggregate } from "./health/llm-usage.js";
29
- import { computeDegradationMetrics, computeDenominatorFixedCoverage, computeEnrichmentMintingRollup, probeStateDbRoundTrip, } from "./health/metrics.js";
30
29
  import { collectPluginStalenessAdvisories } from "./health/plugin-staleness.js";
31
30
  import { collectSchedulerBinaryAdvisory } from "./health/scheduler-binary.js";
32
31
  import { collectStashExposureAdvisory } from "./health/stash-exposure.js";
33
- import { collectSurfacesAdvisories } from "./health/surfaces.js";
34
- import { buildPerRunSummaries } from "./health/task-runs.js";
35
32
  import { buildTypeDirectoryAdvisory } from "./health/type-directory-check.js";
36
- import { ACTIVE_RUN_WARN_MS, IMPROVE_COMPLETED_EVENT, MIN_ROWS_FOR_WORST_TASK_FAIL_RATE, } from "./health/types.js";
33
+ import { MIN_ROWS_FOR_WORST_TASK_FAIL_RATE, } from "./health/types.js";
37
34
  import { collectVersionDriftAdvisory } from "./health/version-drift.js";
38
- import { buildWindowMetrics, computeDeltas, partitionLogBackedRows, resolveWindowCompare } from "./health/windows.js";
35
+ import { buildImproveWindowSummary, buildWindowMetrics, computeDeltas, computeTaskWindowRates, resolveWindowCompare, } from "./health/windows.js";
39
36
  const DEFAULT_SINCE_MS = 24 * 60 * 60 * 1000;
40
37
  export function parseHealthSince(since) {
41
38
  if (since === undefined || since.trim() === "") {
@@ -72,6 +69,65 @@ function validateAkmHealthOptions(options) {
72
69
  }
73
70
  }
74
71
  }
72
+ // ── akmHealth phase helpers (chunk-9 WI-9.5b; file-level decompose following
73
+ // the function's natural gather/advise/check/assemble phases) ───────────────
74
+ /** Event type appended + read back by the state.db round-trip probe. */
75
+ const HEALTH_PROBE_EVENT = "health_probe";
76
+ /** Synthetic sentinel ref (ref-grammar decision D-R3): a colon-free
77
+ * `<subsystem>/_<marker>` label. `health` has no asset stash-subdir, so
78
+ * `health/_probe` names the subsystem. */
79
+ const HEALTH_PROBE_REF = "health/_probe";
80
+ /**
81
+ * Verify state.db can accept a write and read it back — WITHOUT leaving any
82
+ * permanent trace (R-030). Earlier versions appended a `health_probe` event
83
+ * on every `akm health` invocation and never removed it: a read-only health
84
+ * check ran on a cron would grow state.db without bound (the only purge is
85
+ * `improve`'s retention pass, which a health-only user never runs). The probe
86
+ * row inserted here is deleted again inside the SAME connection once the
87
+ * round trip is confirmed, so the net effect on the `events` table is always
88
+ * zero rows — the round trip still genuinely exercises append + read against
89
+ * the real table, it just doesn't accumulate.
90
+ */
91
+ export function probeStateDbRoundTrip(stateDbPath) {
92
+ const started = Date.now();
93
+ try {
94
+ return withStateDb((db) => {
95
+ const ts = new Date().toISOString();
96
+ const insertedId = insertEvent(db, {
97
+ eventType: HEALTH_PROBE_EVENT,
98
+ ts,
99
+ ref: HEALTH_PROBE_REF,
100
+ metadata: { source: "akm health" },
101
+ });
102
+ const durationMs = Date.now() - started;
103
+ if (insertedId === undefined) {
104
+ return { ok: false, durationMs, error: "probe event insert did not return a row id" };
105
+ }
106
+ // The round-trip matches on the exact (id, eventType, ref) triple
107
+ // written above, then removes the row regardless of outcome — a
108
+ // failed round trip must not leak a row any more than a successful
109
+ // one should.
110
+ let roundTripOk = false;
111
+ try {
112
+ const row = db
113
+ .prepare("SELECT 1 AS present FROM events WHERE id = ? AND event_type = ? AND ref = ?")
114
+ .get(insertedId, HEALTH_PROBE_EVENT, HEALTH_PROBE_REF);
115
+ roundTripOk = row !== undefined;
116
+ }
117
+ finally {
118
+ db.prepare("DELETE FROM events WHERE id = ?").run(insertedId);
119
+ }
120
+ if (!roundTripOk) {
121
+ return { ok: false, durationMs, error: "probe event was not readable after append" };
122
+ }
123
+ return { ok: true, durationMs };
124
+ }, { path: stateDbPath });
125
+ }
126
+ catch (err) {
127
+ rethrowIfTestIsolationError(err);
128
+ return { ok: false, durationMs: Date.now() - started, error: err instanceof Error ? err.message : String(err) };
129
+ }
130
+ }
75
131
  /**
76
132
  * Item 7: dedupe stuck-active rows by task_id (keeping the oldest/largest age
77
133
  * per id) so the `active-runs` check can name WHICH tasks are stuck instead of
@@ -119,7 +175,7 @@ function computeWorstTaskFailRate(rows) {
119
175
  return worst;
120
176
  }
121
177
  /** Table presence, the state.db round-trip probe, and task_history-derived rates. */
122
- function gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now) {
178
+ function gatherTaskHistoryPhase(db, since, stateDbPath, now) {
123
179
  const tables = listExistingTableNames(db, ["events", "task_history", "proposals", "schema_migrations"]);
124
180
  const tableNames = tables.map((row) => row.name).sort();
125
181
  const requiredTables = ["events", "proposals", "schema_migrations", "task_history"];
@@ -130,23 +186,13 @@ function gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now) {
130
186
  // bad index entry counts), and the freelist reading is purely informational.
131
187
  const stateDbIntegrity = runStateDbQuickCheck(stateDbPath);
132
188
  const stateDbFreelist = getStateDbFreelistInfo(stateDbPath);
133
- const taskRows = queryTaskHistory(db, { since });
134
- const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
135
- const failedTaskRows = taskRows.filter((row) => row.status === "failed");
136
- const activeRows = taskRows.filter((row) => row.status === "active" && row.completed_at === null);
137
- const stuckActiveRows = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS);
138
189
  // D8 (spec §5.3): a marked "command" row or a legacy (unmarked) "prompt"
139
190
  // row is the agent/LLM arm; an unmarked "command" row is the legacy
140
191
  // native shell/script arm and must not be counted here (see
141
- // isAgentTaskHistoryRow's header comment for the full mapping).
142
- const agentRows = taskRows.filter((row) => isAgentTaskHistoryRow(row));
143
- const agentFailures = agentRows.filter((row) => {
144
- const detail = taskFailureDetail(row);
145
- return typeof detail?.reason === "string" && detail.reason.length > 0;
146
- });
147
- const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
148
- const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
149
- const agentFailureRate = agentRows.length === 0 ? 0 : agentFailures.length / agentRows.length;
192
+ // isAgentTaskHistoryRow's header comment, referenced from
193
+ // computeTaskWindowRates, for the full mapping).
194
+ const taskRows = queryTaskHistory(db, { since });
195
+ const rates = computeTaskWindowRates(taskRows, now);
150
196
  return {
151
197
  tableNames,
152
198
  missingTables,
@@ -154,15 +200,12 @@ function gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now) {
154
200
  stateDbIntegrity,
155
201
  stateDbFreelist,
156
202
  taskRowCount: taskRows.length,
157
- taskRowsWithLogsCount: taskRowsWithLogs.length,
158
- existingLogRowsCount: existingLogRows.length,
159
- stuckActiveRuns: stuckActiveRows.length,
160
- stuckActiveTasks: dedupeStuckActiveTasks(stuckActiveRows, now),
161
- logBackingRate,
162
- taskFailRate,
203
+ stuckActiveRuns: rates.stuckActiveRuns,
204
+ stuckActiveTasks: dedupeStuckActiveTasks(rates.stuckActiveRows, now),
205
+ taskFailRate: rates.taskFailRate,
163
206
  worstTaskFailRate: computeWorstTaskFailRate(taskRows),
164
- agentFailureRate,
165
- agentFailureReasonCounts: countAgentFailureReasons(agentFailures),
207
+ agentFailureRate: rates.agentFailureRate,
208
+ agentFailureReasonCounts: rates.agentFailureReasonCounts,
166
209
  };
167
210
  }
168
211
  /**
@@ -191,47 +234,16 @@ function gatherSessionExtractionLedgerPhase(db, now) {
191
234
  return { since, rows: getExtractOutcomeCountsSince(db, since) };
192
235
  }
193
236
  /**
194
- * Assemble the window's improve-pipeline summary: invoked/completed/skipped
195
- * counts from events, the per-run result_json aggregate, wall-time stats, and
196
- * the WS-5 coverage/degradation/enrichment-minting rollups.
237
+ * The best-effort advisory groups beyond the health-check registry: the
238
+ * `stash-git-exposure` probe, the 08 surfaces group (binary-config-skew,
239
+ * egress-endpoints), `type-directory-disagreement` (#831), `data-dir-usage`
240
+ * (#896), and `plugin-version` (itlackey/akm#832). Order matches emission
241
+ * order in the returned array. A probe/filesystem failure in any try/catch
242
+ * must not abort the health report — each group degrades to "no advisory"
243
+ * independently.
197
244
  */
198
- function gatherImproveSummaryPhase(db, stateDbPath, since, now) {
199
- const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
200
- const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
201
- const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
202
- const eventsMetrics = summarizeImproveCompleted(improveCompletedEvents);
203
- const { metrics: improveSummary } = summarizeImproveRuns(db, since);
204
- improveSummary.invoked = improveInvoked;
205
- improveSummary.completed = eventsMetrics.completed;
206
- const skipSummary = buildImproveSkipSummary(improveSkippedEvents);
207
- improveSummary.skipped = skipSummary.skipped;
208
- improveSummary.skipReasons = skipSummary.skipReasons;
209
- const perRunSummaries = buildPerRunSummaries(db, since);
210
- const wallTimes = perRunSummaries.map((run) => run.wallTimeMs).filter((ms) => Number.isFinite(ms) && ms > 0);
211
- improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
212
- // WS-5: Compute denominator-fixed coverage and per-run degradation metrics
213
- // for the main health path (not just window-compare mode).
214
- const until = new Date(now()).toISOString();
215
- const totalAssetsMain = improveSummary.memorySummary.eligible + improveSummary.memorySummary.derived;
216
- improveSummary.coverage = computeDenominatorFixedCoverage(db, totalAssetsMain, improveSummary.memorySummary.eligible, since, until);
217
- const degradationMain = computeDegradationMetrics(db, since, until);
218
- if (degradationMain) {
219
- improveSummary.degradation = degradationMain;
220
- }
221
- improveSummary.enrichmentMinting = computeEnrichmentMintingRollup(db, since, until);
222
- return { improveSummary, perRunSummaries };
223
- }
224
- /**
225
- * The best-effort advisory groups beyond the health-check registry: improve
226
- * advisories, the `stash-git-exposure` probe, the 08 surfaces group
227
- * (binary-config-skew, egress-endpoints), `type-directory-disagreement`
228
- * (#831), `data-dir-usage` (#896), and `plugin-version` (itlackey/akm#832).
229
- * Order matches emission order in the returned array. A probe/filesystem
230
- * failure in any try/catch must not abort the health report — each group
231
- * degrades to "no advisory" independently.
232
- */
233
- function gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView) {
234
- const advisories = [...collectImproveAdvisories(db, stateDbPath, since, improveSummary)];
245
+ function gatherAncillaryAdvisories(db, options, egressConfigView) {
246
+ const advisories = [];
235
247
  const indexStateMismatch = detectIndexStateGenerationMismatch(db);
236
248
  if (indexStateMismatch)
237
249
  advisories.push(indexStateMismatch);
@@ -258,10 +270,12 @@ function gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, optio
258
270
  // egress-endpoints). Best-effort — a filesystem probe failure must not abort
259
271
  // the health report.
260
272
  try {
261
- advisories.push(...collectSurfacesAdvisories({
262
- configPath: getConfigPath(),
263
- config: egressConfigView,
264
- }));
273
+ const configSkew = collectConfigSkewAdvisory(getConfigPath());
274
+ if (configSkew)
275
+ advisories.push(configSkew);
276
+ const egress = collectEgressAdvisory(egressConfigView);
277
+ if (egress)
278
+ advisories.push(egress);
265
279
  }
266
280
  catch {
267
281
  // Non-fatal.
@@ -399,7 +413,7 @@ function detectIndexStateGenerationMismatch(stateDb) {
399
413
  * per-window bundles and computes deltas between the earliest and latest
400
414
  * window. Top-level metrics retain the primary `--since` query.
401
415
  */
402
- function resolveWindowComparePhase(options, db, stateDbPath, now, logsDb) {
416
+ function resolveWindowComparePhase(options, db, stateDbPath, now) {
403
417
  let windowSpecs;
404
418
  if (options.windowCompare) {
405
419
  windowSpecs = resolveWindowCompare(options.windowCompare, now);
@@ -413,7 +427,7 @@ function resolveWindowComparePhase(options, db, stateDbPath, now, logsDb) {
413
427
  windowResults = windowSpecs.map((spec) => {
414
428
  const winSince = parseHealthSince(spec.since);
415
429
  const winUntil = spec.until ? parseHealthSince(spec.until) : new Date(now()).toISOString();
416
- const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil, now, logsDb);
430
+ const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil, now);
417
431
  return {
418
432
  name: spec.name,
419
433
  since: winSince,
@@ -438,19 +452,13 @@ function resolveWindowComparePhase(options, db, stateDbPath, now, logsDb) {
438
452
  return { windowResults, deltas };
439
453
  }
440
454
  /**
441
- * The health report for a state.db a managed open cannot reach at all: either
442
- * the file is not readable (#791), or — the same shape, a different cause —
443
- * it holds a pending migration the managed open refuses to apply without
444
- * deliberate consent (`akm upgrade` / `akm migrate apply` are the
445
- * only two callers allowed to admit a historical-destructive migration; see
446
- * `beforeMigrationLocked` in `src/core/state/migrations.ts`).
455
+ * The health report for a state.db the open cannot reach at all: either the
456
+ * file is not readable (#791), or — the same shape, a different cause — the
457
+ * open failed while applying a pending migration, which is left pending.
447
458
  *
448
459
  * `akm health` is what an operator (or a bundler's boot check) runs when
449
460
  * something else is misbehaving, so it must survive either problem long
450
- * enough to NAME it. Previously an unreadable file was already handled this
451
- * way, but a pending migration was not: the managed open's refusal escaped as
452
- * a thrown `ConfigError` (exit 78) and crashed the whole command before any
453
- * check — including this one — could report anything.
461
+ * enough to NAME it rather than exit 78 before any check could report.
454
462
  *
455
463
  * Reported as a single hard-channel `fail` check — the run genuinely could
456
464
  * not open state.db, so every check that depends on it is skipped rather than
@@ -470,13 +478,9 @@ function degradedStateDbReport(hardCheck, options) {
470
478
  agentFailureRate: 0,
471
479
  agentFailureReasonCounts: {},
472
480
  stuckActiveRuns: 0,
473
- logBackingRate: 0,
474
- // `null`, not 0: the round-trip probe did not run, which is not the same
475
- // as it running instantly.
476
- probeRoundTripMs: null,
477
481
  llmUsage: emptyLlmUsageAggregate(),
478
482
  },
479
- improve: summarizeImproveCompleted([]),
483
+ improve: emptyImproveMetrics(),
480
484
  };
481
485
  }
482
486
  function unreadableStateDbCheck(detail) {
@@ -498,29 +502,25 @@ export async function akmHealth(options = {}) {
498
502
  const stateDbPath = options.stateDbPath ?? getStateDbPathInDataDir();
499
503
  const hardChecks = [];
500
504
  const advisories = [];
501
- // #791: an UNREADABLE state.db, or one with a pending migration the
502
- // managed open refuses to apply, are the two failures `akm health` most
503
- // needs to be able to report, because this is the command an operator (or a
504
- // bundler's boot check) runs to find out why everything else is behaving
505
- // oddly. Dying here meant health could not diagnose that state at all — not
506
- // even the checks that never touch state.db got to run. Report it as a
507
- // finding instead.
508
- let db;
505
+ // #791: an UNREADABLE state.db, or one whose pending migration failed to
506
+ // apply, are the two failures `akm health` most needs to be able to report,
507
+ // because this is the command an operator (or a bundler's boot check) runs
508
+ // to find out why everything else is behaving oddly. Report it as a finding
509
+ // instead of dying before any check runs. The open itself applies every
510
+ // pending migration; `state-db-migrations` reports what it applied.
511
+ let opened;
509
512
  try {
510
- db = openStateDatabase(stateDbPath);
513
+ opened = openStateDatabaseWithReport(stateDbPath);
511
514
  }
512
515
  catch (error) {
513
516
  const { access, code } = classifyPathAccess(stateDbPath);
514
517
  if (access === "inaccessible") {
515
518
  return degradedStateDbReport(unreadableStateDbCheck(describeInaccessiblePath(stateDbPath, code)), options);
516
519
  }
517
- // The managed open's refusal of a pending historical-destructive
518
- // migration is a plain `Error`, not a distinguishable error class — so
519
- // confirm the cause via the read-only preflight (`listPendingStateMigrations`,
520
- // which never applies anything) rather than pattern-matching the message.
521
- // A ledger too broken to enumerate at all throws here too; in that case
522
- // fall through to the generic config-error report below, since it is a
523
- // genuinely different, rarer failure this check cannot explain.
520
+ // A migration that failed to apply rolled back and is still pending:
521
+ // name it from a read-only listing (which never applies anything) rather
522
+ // than pattern-matching the error text. A ledger too broken to enumerate
523
+ // throws here too and falls through to the generic config error below.
524
524
  let pendingMigrationsCheck;
525
525
  try {
526
526
  pendingMigrationsCheck = runPendingStateMigrationsCheck(stateDbPath, { listPendingStateMigrations });
@@ -533,16 +533,7 @@ export async function akmHealth(options = {}) {
533
533
  }
534
534
  throw new ConfigError(`Unable to open state.db: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
535
535
  }
536
- // logs.db backs the log-backing metric (#579). Best-effort: when it cannot
537
- // be opened, partitionLogBackedRows falls back to the on-disk file check, so
538
- // health never hard-fails on a missing/locked logs database.
539
- let logsDb;
540
- try {
541
- logsDb = openLogsDatabase(options.logsDbPath);
542
- }
543
- catch {
544
- logsDb = undefined;
545
- }
536
+ const { db } = opened;
546
537
  try {
547
538
  // Network probes overlap the local database phases below; awaited where consumed.
548
539
  const engineProbesPromise = runHealthEngineProbes({ probeReachable: options.probe ? probeLlmEndpoint : undefined });
@@ -557,11 +548,15 @@ export async function akmHealth(options = {}) {
557
548
  cliVersion: pkgVersion,
558
549
  });
559
550
  schedulerBinaryDriftPromise.catch(() => undefined);
560
- const taskHistory = gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now);
551
+ const taskHistory = gatherTaskHistoryPhase(db, since, stateDbPath, now);
561
552
  const { tableNames, missingTables, probe } = taskHistory;
562
553
  const { egressConfigView, thinkingOffEngines } = gatherEgressConfigPhase();
563
- const { improveSummary } = gatherImproveSummaryPhase(db, stateDbPath, since, now);
564
- advisories.push(...gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView));
554
+ // Same window bundle `--window-compare` builds per-window (windows.ts) —
555
+ // reused here for the main `--since` window's improve summary and for
556
+ // `--group-by run`, so neither re-reads `improve_runs`.
557
+ const until = new Date(now()).toISOString();
558
+ const { improve: improveSummary, perRunSummaries } = buildImproveWindowSummary(db, stateDbPath, since, until);
559
+ advisories.push(...gatherAncillaryAdvisories(db, options, egressConfigView));
565
560
  const sessionExtractionLedger = gatherSessionExtractionLedgerPhase(db, now);
566
561
  // #950: computed once (no IO beyond config/env, same as gatherEgressConfigPhase)
567
562
  // so `active-improve-strategy` and `engine-last-used` project the same
@@ -590,16 +585,13 @@ export async function akmHealth(options = {}) {
590
585
  probe,
591
586
  stateDbIntegrity: taskHistory.stateDbIntegrity,
592
587
  stateDbFreelist: taskHistory.stateDbFreelist,
588
+ stateDbMigrations: { applied: opened.applied, ...(opened.backupPath ? { backupPath: opened.backupPath } : {}) },
593
589
  taskRowCount: taskHistory.taskRowCount,
594
590
  taskFailRate: taskHistory.taskFailRate,
595
- taskRowsWithLogsCount: taskHistory.taskRowsWithLogsCount,
596
- existingLogRowsCount: taskHistory.existingLogRowsCount,
597
- logBackingRate: taskHistory.logBackingRate,
598
591
  stuckActiveRuns: taskHistory.stuckActiveRuns,
599
592
  stuckActiveTasks: taskHistory.stuckActiveTasks,
600
593
  worstTaskFailRate: taskHistory.worstTaskFailRate,
601
594
  agentFailureReasonCounts: taskHistory.agentFailureReasonCounts,
602
- sessionExtraction: improveSummary.sessionExtraction,
603
595
  sessionExtractionLedger,
604
596
  autoAccept: improveSummary.autoAccept,
605
597
  engineProbes,
@@ -624,20 +616,15 @@ export async function akmHealth(options = {}) {
624
616
  agentFailureRate: roundRate(taskHistory.agentFailureRate),
625
617
  agentFailureReasonCounts: taskHistory.agentFailureReasonCounts,
626
618
  stuckActiveRuns: taskHistory.stuckActiveRuns,
627
- logBackingRate: roundRate(taskHistory.logBackingRate),
628
- probeRoundTripMs: probe.durationMs,
629
619
  llmUsage,
630
620
  };
631
621
  const hardFailure = hardChecks.some((check) => check.status === "fail");
632
622
  const deterministicWarnings = [...hardChecks, ...advisories].some((check) => check.status === "warn" && check.kind === "deterministic");
633
623
  const status = hardFailure ? "fail" : deterministicWarnings ? "warn" : "pass";
634
624
  // ── Window-compare mode (Phase 3) ─────────────────────────────────────
635
- const { windowResults, deltas } = resolveWindowComparePhase(options, db, stateDbPath, now, logsDb);
625
+ const { windowResults, deltas } = resolveWindowComparePhase(options, db, stateDbPath, now);
636
626
  // ── Per-run mode (Phase 2) ────────────────────────────────────────────
637
- let runs;
638
- if (options.groupBy === "run") {
639
- runs = buildPerRunSummaries(db, since);
640
- }
627
+ const runs = options.groupBy === "run" ? perRunSummaries : undefined;
641
628
  return {
642
629
  schemaVersion: 3,
643
630
  ok: !hardFailure,
@@ -654,14 +641,6 @@ export async function akmHealth(options = {}) {
654
641
  }
655
642
  finally {
656
643
  db.close();
657
- if (logsDb) {
658
- try {
659
- logsDb.close();
660
- }
661
- catch {
662
- // best-effort
663
- }
664
- }
665
644
  }
666
645
  }
667
646
  // Markdown renderers (renderRunsDetailMd / renderWindowCompareMd) live in
@@ -1,129 +1,54 @@
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
- // Chunk sizing + per-chunk prompt assembly for consolidate. Pure token math and
5
- // prompt-string construction over MemoryEntry inputs — no LLM call, no embedder,
6
- // no orchestrator coupling.
4
+ /** Consolidate's chunk sizing and per-chunk prompt (pure token math and text). */
7
5
  import fs from "node:fs";
8
6
  import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
9
- import { cacheHash, stripFrontmatterBody } from "../content-hash.js";
10
- /**
11
- * Conservative chars-per-token estimate used when computing prompt budgets.
12
- * English text averages roughly 4 chars/token for most LLM tokenizers. We use
13
- * 3 to stay conservative (shorter tokens = more tokens per char). Exported so
14
- * other context-length-driven budgets (e.g. reflect's direct-LLM content cap,
15
- * #952) share this single estimate instead of redeclaring it.
16
- */
7
+ import { contentHash, stripFrontmatterBody } from "../content-hash.js";
8
+ /** A conservative chars-per-token estimate, shared by every context-length budget. */
17
9
  export const CHARS_PER_TOKEN = 3;
18
- /**
19
- * Overhead budget reserved for the system prompt, chunk header lines, and per-
20
- * memory metadata lines (name, description, tags, separator). Measured at
21
- * roughly 600 chars for the system prompt + ~100 chars of header + ~50 chars
22
- * per memory × chunk size. We round up to 2 000 tokens to leave room for the
23
- * model's own output.
24
- */
10
+ /** System prompt, headers and per-memory metadata, with room for the output. */
25
11
  const PROMPT_OVERHEAD_TOKENS = 2_000;
26
12
  /**
27
- * Default effective token budget used when the default LLM profile's
28
- * `contextLength` is not set. This is intentionally conservative (4 096)
29
- * rather than being set to the model's actual context window, because:
30
- *
31
- * - When the agent path is used, the agent CLI (e.g. opencode)
32
- * prepends its own large system prompt + conversation history before
33
- * forwarding to the model. That overhead easily consumes 30K+ tokens on
34
- * a model with a 16K context window, leaving very little room for
35
- * chunk content.
36
- * - When the HTTP path is used (an LLM profile is selected), only the akm
37
- * system prompt and user prompt are sent, so the budget can be set to the
38
- * model's actual context length via the selected LLM engine's contextLength.
39
- *
40
- * Set the selected LLM engine's contextLength in your config file to the
41
- * model's actual context window to allow larger chunks on the HTTP path.
13
+ * The budget when the engine sets no `contextLength`. Conservative on purpose:
14
+ * an agent CLI prepends its own large prompt; set `contextLength` on a direct
15
+ * LLM engine to allow bigger chunks.
42
16
  */
43
17
  export const DEFAULT_CONTEXT_LENGTH_TOKENS = 4_096;
44
- /**
45
- * Given the model's context window and the per-memory body truncation limit,
46
- * return the maximum number of memories that can safely fit in one chunk
47
- * without the prompt overflowing the context window.
48
- *
49
- * The formula is:
50
- * usableTokens = contextLength - PROMPT_OVERHEAD_TOKENS
51
- * tokensPerMemory = ceil(bodyTruncation / CHARS_PER_TOKEN)
52
- * chunkSize = floor(usableTokens / tokensPerMemory)
53
- *
54
- * Result is clamped between 1 and 50 to avoid degenerate values.
55
- *
56
- * @param contextLength - Model context window in tokens.
57
- * @param bodyTruncation - Max chars per memory body included in the prompt.
58
- * @param maxChunkSize - Optional override for the hardcoded cap of 50 (1–50).
59
- */
18
+ /** Memories per chunk: `(contextLength − overhead) / ceil(bodyTruncation / CHARS_PER_TOKEN)`, within 1…cap (50). */
60
19
  export function computeSafeChunkSize(contextLength, bodyTruncation, maxChunkSize) {
61
20
  const usableTokens = Math.max(contextLength - PROMPT_OVERHEAD_TOKENS, 0);
62
21
  const tokensPerMemory = Math.max(Math.ceil(bodyTruncation / CHARS_PER_TOKEN), 1);
63
- const raw = Math.floor(usableTokens / tokensPerMemory);
64
- return Math.max(1, Math.min(maxChunkSize ?? 50, raw));
22
+ return Math.max(1, Math.min(maxChunkSize ?? 50, Math.floor(usableTokens / tokensPerMemory)));
65
23
  }
66
24
  /**
67
- * Build the per-chunk user prompt fed to the consolidate LLM.
68
- *
69
- * Each memory is annotated with two flags:
70
- * - `(captureMode: hot)` — user-explicit memory, surfaced to the model as
71
- * provenance context.
72
- * - `(already queued)` — the memory's body hash matches a pending
73
- * consolidate proposal; the system prompt's PROMOTE rule forbids
74
- * proposing promote for these. ~107/4h before this annotation.
75
- *
76
- * Both annotations are visible to the LLM. `pendingProposalBodyHashes`
77
- * is precomputed once per run by `loadPendingConsolidateProposalHashes`
78
- * so the cost stays O(memories) inside the chunk loop.
25
+ * One chunk's user prompt. Each memory shows its body only (frontmatter can
26
+ * outrun the truncation and leave the model judging metadata), marked
27
+ * `captureMode: hot` when the user captured it explicitly and `already queued`
28
+ * when a pending consolidate proposal carries its body hash — the system
29
+ * prompt forbids promoting those.
79
30
  */
80
31
  export function buildChunkPrompt(sourceName, memories, chunkIndex, totalChunks, bodyTruncation, pendingProposalBodyHashes = new Set()) {
81
- const annotationsByIndex = [];
82
- for (const m of memories) {
83
- let body = "";
32
+ const lines = [
33
+ `Source: ${sourceName}`,
34
+ `Chunk ${chunkIndex + 1} of ${totalChunks} (${memories.length} memories):`,
35
+ "",
36
+ ];
37
+ memories.forEach((m, i) => {
38
+ let body;
84
39
  try {
85
40
  body = fs.readFileSync(m.filePath, "utf8");
86
41
  }
87
42
  catch {
88
43
  body = "(unreadable)";
89
44
  }
90
- // Hot/queued detection stays on the raw body — frontmatter is exactly
91
- // what parseFrontmatter and the pending-proposal hash domain need.
92
- const parsed = parseFrontmatter(body);
93
- const isHot = parsed.data.captureMode === "hot";
94
- // Use cacheHash (case-preserving stripped body) to match the domain used
95
- // by loadPendingConsolidateProposalHashes and the body-embedding cache.
96
- const bodyHash = cacheHash(body);
97
- const isAlreadyQueued = pendingProposalBodyHashes.has(bodyHash);
98
- // The excerpt shown to the LLM is the body ONLY — frontmatter can run
99
- // longer than bodyTruncation (~21% of memories), which used to leave the
100
- // model judging metadata instead of content. stripFrontmatterBody falls
101
- // back to raw.trim() on unparseable/absent frontmatter, so this is a
102
- // no-op for the "(unreadable)" placeholder.
103
- const excerpt = stripFrontmatterBody(body);
104
- annotationsByIndex.push({ isHot, isAlreadyQueued, excerpt });
105
- }
106
- const lines = [
107
- `Source: ${sourceName}`,
108
- `Chunk ${chunkIndex + 1} of ${totalChunks} (${memories.length} memories):`,
109
- "",
110
- ];
111
- for (let i = 0; i < memories.length; i++) {
112
- const m = memories[i];
113
- // `annotationsByIndex` has exactly one entry per memory (built in the loop above).
114
- const { isHot, isAlreadyQueued, excerpt } = annotationsByIndex[i];
115
45
  const annotations = [];
116
- if (isHot)
46
+ if (parseFrontmatter(body).data.captureMode === "hot")
117
47
  annotations.push("captureMode: hot");
118
- if (isAlreadyQueued)
48
+ if (pendingProposalBodyHashes.has(contentHash(body, "body")))
119
49
  annotations.push("already queued");
120
50
  const annotationSuffix = annotations.length > 0 ? ` (${annotations.join("; ")})` : "";
121
- lines.push(`[${i + 1}] memories/${m.name}${annotationSuffix}`);
122
- lines.push(`Description: ${m.description || "(none)"}`);
123
- lines.push(`Tags: ${m.tags.length > 0 ? m.tags.join(", ") : "(none)"}`);
124
- lines.push("---");
125
- lines.push(excerpt.slice(0, bodyTruncation));
126
- lines.push("");
127
- }
51
+ lines.push(`[${i + 1}] memories/${m.name}${annotationSuffix}`, `Description: ${m.description || "(none)"}`, `Tags: ${m.tags.length > 0 ? m.tags.join(", ") : "(none)"}`, "---", stripFrontmatterBody(body).slice(0, bodyTruncation), "");
52
+ });
128
53
  return lines.join("\n");
129
54
  }