akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.5

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 (346) hide show
  1. package/CHANGELOG.md +760 -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/lint/base-linter.js +19 -5
  53. package/dist/commands/proposal/drain.js +251 -644
  54. package/dist/commands/proposal/proposal-cli.js +3 -18
  55. package/dist/commands/proposal/proposal-types.js +20 -41
  56. package/dist/commands/proposal/proposal.js +1 -2
  57. package/dist/commands/proposal/propose.js +134 -160
  58. package/dist/commands/proposal/repository.js +502 -1487
  59. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  60. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  61. package/dist/commands/proposal/validators/proposals.js +13 -89
  62. package/dist/commands/read/curate.js +63 -413
  63. package/dist/commands/read/search-cli.js +16 -33
  64. package/dist/commands/read/search.js +17 -23
  65. package/dist/commands/read/show.js +2 -13
  66. package/dist/commands/sources/bundle-cli.js +25 -2
  67. package/dist/commands/sources/bundle-config-ops.js +4 -0
  68. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  69. package/dist/commands/sources/info.js +2 -11
  70. package/dist/commands/sources/installed-stashes.js +197 -746
  71. package/dist/commands/sources/schema-repair.js +98 -129
  72. package/dist/commands/sources/source-add.js +62 -12
  73. package/dist/commands/sources/source-manage.js +9 -2
  74. package/dist/commands/sources/stash-cli.js +1 -1
  75. package/dist/commands/tasks/explain.js +10 -13
  76. package/dist/commands/tasks/tasks-cli.js +9 -8
  77. package/dist/commands/tasks/tasks.js +326 -930
  78. package/dist/commands/tasks/validate.js +42 -21
  79. package/dist/commands/workflow/plan.js +22 -29
  80. package/dist/commands/workflow-cli.js +4 -4
  81. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  82. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  83. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  84. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  85. package/dist/core/adapter/execution-source.js +17 -29
  86. package/dist/core/asset/asset-placement.js +4 -13
  87. package/dist/core/asset/resolve-ref.js +1 -1
  88. package/dist/core/bundle-id.js +42 -5
  89. package/dist/core/bundle-rename.js +291 -0
  90. package/dist/core/config/config-io.js +1 -2
  91. package/dist/core/config/config-schema.js +1 -33
  92. package/dist/core/config/config-walker.js +1 -1
  93. package/dist/core/config/config.js +163 -68
  94. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  95. package/dist/core/config/schema/embedding.js +20 -5
  96. package/dist/core/config/schema/engines.js +5 -0
  97. package/dist/core/config/schema/execution.js +1 -1
  98. package/dist/core/config/schema/experimental.js +1 -1
  99. package/dist/core/config/schema/improve-processes.js +21 -95
  100. package/dist/core/config/schema/improve.js +4 -42
  101. package/dist/core/config/schema/scheduler.js +12 -12
  102. package/dist/core/config/schema/search.js +6 -22
  103. package/dist/core/env-secret-ref.js +0 -1
  104. package/dist/core/errors.js +8 -9
  105. package/dist/core/file-lock.js +76 -173
  106. package/dist/core/logs-db.js +2 -2
  107. package/dist/core/paths.js +0 -27
  108. package/dist/core/redaction.js +109 -2
  109. package/dist/core/run-lock.js +2 -5
  110. package/dist/core/spawn-env.js +1 -1
  111. package/dist/core/state/migrations.js +108 -61
  112. package/dist/core/state-db-scope.js +2 -4
  113. package/dist/core/state-db.js +126 -692
  114. package/dist/core/type-presentation.js +1 -9
  115. package/dist/core/write-source.js +293 -1012
  116. package/dist/execution/input-contract.js +1 -1
  117. package/dist/execution/resolved-request.js +135 -689
  118. package/dist/execution/source.js +63 -257
  119. package/dist/execution/target-ref.js +1 -1
  120. package/dist/indexer/bundle-identity-guard.js +2 -2
  121. package/dist/indexer/db/graph-db.js +106 -46
  122. package/dist/indexer/ensure-index.js +44 -85
  123. package/dist/indexer/graph/graph-extraction.js +340 -562
  124. package/dist/indexer/graph/graph-related.js +130 -0
  125. package/dist/indexer/index-rebuild-lock.js +3 -11
  126. package/dist/indexer/index-writer-lock.js +8 -17
  127. package/dist/indexer/index-written-assets.js +139 -151
  128. package/dist/indexer/indexer.js +524 -846
  129. package/dist/indexer/materialize-embeddings.js +60 -397
  130. package/dist/indexer/passes/memory-inference.js +81 -90
  131. package/dist/indexer/passes/metadata.js +132 -200
  132. package/dist/indexer/read-preflight.js +0 -7
  133. package/dist/indexer/scan/doc-to-entry.js +1 -3
  134. package/dist/indexer/scan/drain-dir.js +1 -1
  135. package/dist/indexer/search/db-search.js +181 -590
  136. package/dist/indexer/search/fts-query.js +30 -41
  137. package/dist/indexer/search/ranking.js +28 -154
  138. package/dist/indexer/search/search-attribution.js +12 -32
  139. package/dist/indexer/search/search-fields.js +11 -15
  140. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  141. package/dist/indexer/search/search-source.js +1 -4
  142. package/dist/indexer/usage/usage-events.js +2 -7
  143. package/dist/integrations/agent/engine-fallback.js +23 -40
  144. package/dist/integrations/agent/engine-resolution.js +93 -183
  145. package/dist/integrations/agent/execution.js +507 -0
  146. package/dist/integrations/agent/model-map.js +28 -156
  147. package/dist/integrations/agent/request-lowering.js +66 -141
  148. package/dist/integrations/agent/runner-dispatch.js +143 -321
  149. package/dist/integrations/agent/runner.js +54 -14
  150. package/dist/integrations/lockfile.js +53 -101
  151. package/dist/llm/embedders/deterministic.js +2 -3
  152. package/dist/llm/embedders/profile.js +71 -0
  153. package/dist/llm/embedders/remote.js +10 -15
  154. package/dist/llm/graph-extract.js +3 -12
  155. package/dist/llm/index-passes.js +3 -5
  156. package/dist/llm/memory-infer.js +1 -2
  157. package/dist/llm/metadata-enhance.js +1 -2
  158. package/dist/llm/structured-call.js +5 -24
  159. package/dist/output/generic-render.js +23 -11
  160. package/dist/output/html-render.js +13 -10
  161. package/dist/output/render-registry.js +3 -32
  162. package/dist/output/shapes/helpers.js +2 -34
  163. package/dist/output/shapes/passthrough.js +1 -9
  164. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  165. package/dist/output/text/command-format.js +60 -23
  166. package/dist/output/text/helpers.js +1 -1
  167. package/dist/output/text/migrate.js +5 -14
  168. package/dist/output/text/proposal-format.js +1 -2
  169. package/dist/output/text/workflow-format.js +0 -32
  170. package/dist/output/text.js +2 -0
  171. package/dist/registry/factory.js +4 -19
  172. package/dist/registry/network.js +66 -220
  173. package/dist/registry/providers/index.js +0 -2
  174. package/dist/registry/providers/skills-sh.js +3 -14
  175. package/dist/registry/providers/static-index.js +24 -26
  176. package/dist/registry/resolve.js +55 -131
  177. package/dist/scripts/akm-migrate-node.js +43940 -93320
  178. package/dist/scripts/akm-migrate.js +43700 -93078
  179. package/dist/setup/registry-stash-loader.js +4 -13
  180. package/dist/setup/semantic-assets.js +3 -44
  181. package/dist/setup/setup.js +1 -1
  182. package/dist/setup/steps/tasks.js +25 -15
  183. package/dist/sources/provider-factory.js +17 -18
  184. package/dist/sources/providers/filesystem.js +2 -3
  185. package/dist/sources/providers/git-install.js +7 -1
  186. package/dist/sources/providers/git-provider.js +0 -3
  187. package/dist/sources/providers/git-stash.js +0 -17
  188. package/dist/sources/providers/npm.js +2 -4
  189. package/dist/sources/providers/provider-utils.js +5 -10
  190. package/dist/sources/providers/website.js +0 -2
  191. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  192. package/dist/sources/website-url.js +2 -2
  193. package/dist/storage/database.js +9 -35
  194. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  195. package/dist/storage/repositories/index-connection.js +34 -70
  196. package/dist/storage/repositories/index-entries-repository.js +69 -111
  197. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  198. package/dist/storage/repositories/index-entry-schema.js +83 -269
  199. package/dist/storage/repositories/index-fts-repository.js +86 -256
  200. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  201. package/dist/storage/repositories/index-meta-repository.js +6 -4
  202. package/dist/storage/repositories/index-schema.js +192 -220
  203. package/dist/storage/repositories/index-utility-repository.js +8 -29
  204. package/dist/storage/repositories/index-vec-repository.js +133 -414
  205. package/dist/storage/repositories/outcome-repository.js +2 -1
  206. package/dist/storage/repositories/proposals-repository.js +35 -0
  207. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  208. package/dist/storage/repositories/task-history-repository.js +26 -4
  209. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  210. package/dist/storage/sqlite-migrations.js +136 -0
  211. package/dist/storage/sqlite-pragmas.js +11 -9
  212. package/dist/storage/sqlite-transaction.js +170 -0
  213. package/dist/storage/state-db-integrity.js +34 -27
  214. package/dist/tasks/activation-config.js +134 -62
  215. package/dist/tasks/backends/cron.js +129 -277
  216. package/dist/tasks/backends/exec-utils.js +2 -5
  217. package/dist/tasks/backends/launchd.js +125 -745
  218. package/dist/tasks/backends/schtasks.js +101 -620
  219. package/dist/tasks/prepare/prepare-support.js +5 -15
  220. package/dist/tasks/prepare/prepare.js +0 -2
  221. package/dist/tasks/resolve-akm-bin.js +20 -79
  222. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  223. package/dist/tasks/scheduler-binding.js +18 -238
  224. package/dist/tasks/scheduler-invocation.js +52 -52
  225. package/dist/tasks/scheduler-lock.js +53 -0
  226. package/dist/tasks/scheduler-sync.js +361 -751
  227. package/dist/tasks/source/parse-task-source.js +160 -10
  228. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  229. package/dist/tasks/source/task-to-v4.js +2 -2
  230. package/dist/workflows/authoring/authoring.js +3 -12
  231. package/dist/workflows/compile.js +211 -0
  232. package/dist/workflows/concurrency-policy.js +13 -74
  233. package/dist/workflows/exec/child-invocation.js +3 -17
  234. package/dist/workflows/exec/child-workflow.js +32 -141
  235. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  236. package/dist/workflows/exec/environment.js +98 -0
  237. package/dist/workflows/exec/exec-unit.js +33 -140
  238. package/dist/workflows/exec/frozen-judge.js +7 -59
  239. package/dist/workflows/exec/native-executor.js +82 -341
  240. package/dist/workflows/exec/param-secrets.js +29 -47
  241. package/dist/workflows/exec/run-workflow.js +154 -387
  242. package/dist/workflows/exec/scheduler.js +9 -36
  243. package/dist/workflows/exec/step-work.js +127 -430
  244. package/dist/workflows/exec/unit-dispatch.js +11 -63
  245. package/dist/workflows/exec/unit-writer.js +8 -52
  246. package/dist/workflows/exec/worktree.js +39 -273
  247. package/dist/workflows/freeze/child-output-references.js +4 -15
  248. package/dist/workflows/freeze/environment.js +99 -92
  249. package/dist/workflows/freeze/freeze.js +172 -0
  250. package/dist/workflows/freeze/step-values.js +19 -21
  251. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  252. package/dist/workflows/freeze/targets/command.js +10 -33
  253. package/dist/workflows/freeze/targets/script.js +5 -12
  254. package/dist/workflows/freeze/targets/shell.js +3 -6
  255. package/dist/workflows/freeze/targets/task.js +25 -80
  256. package/dist/workflows/freeze/task-bindings.js +20 -67
  257. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  258. package/dist/workflows/ir/params.js +6 -51
  259. package/dist/workflows/ir/plan-hash.js +2 -34
  260. package/dist/workflows/parser.js +140 -43
  261. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  262. package/dist/workflows/renderer.js +36 -69
  263. package/dist/workflows/resource-limits.js +12 -120
  264. package/dist/workflows/runtime/agent-identity.js +8 -40
  265. package/dist/workflows/runtime/run-outputs.js +3 -6
  266. package/dist/workflows/runtime/run-plan.js +316 -0
  267. package/dist/workflows/runtime/runs.js +48 -200
  268. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  269. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  270. package/dist/workflows/validate-summary.js +2 -7
  271. package/docs/integration/bundling-akm.md +49 -42
  272. package/docs/migration/README.md +1 -0
  273. package/docs/migration/release-notes/0.9.17.md +41 -0
  274. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  275. package/docs/reference/cli.md +182 -125
  276. package/docs/reference/configuration.md +49 -56
  277. package/docs/reference/data-and-telemetry.md +19 -20
  278. package/docs/reference/tasks.md +86 -38
  279. package/docs/reference/workflow-schema.md +14 -18
  280. package/docs/reference/workflows.md +6 -9
  281. package/package.json +1 -1
  282. package/schemas/akm-config.json +87 -406
  283. package/dist/commands/health/advisories.js +0 -150
  284. package/dist/commands/health/metrics.js +0 -329
  285. package/dist/commands/health/surfaces.js +0 -102
  286. package/dist/commands/improve/anti-collapse.js +0 -83
  287. package/dist/commands/improve/collapse-detector.js +0 -432
  288. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  289. package/dist/commands/improve/consolidate/merge.js +0 -146
  290. package/dist/commands/improve/distill/promote-memory.js +0 -329
  291. package/dist/commands/improve/distill/quality-gate.js +0 -500
  292. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  293. package/dist/commands/improve/proposal-envelope.js +0 -31
  294. package/dist/commands/improve/run-context.js +0 -123
  295. package/dist/commands/improve/shared.js +0 -21
  296. package/dist/commands/improve/source-identity.js +0 -28
  297. package/dist/commands/improve/triage.js +0 -96
  298. package/dist/commands/proposal/drain-policies.js +0 -151
  299. package/dist/commands/sources/update-transaction.js +0 -220
  300. package/dist/core/action-contributors.js +0 -28
  301. package/dist/core/config/config-version-shim.js +0 -101
  302. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  303. package/dist/core/fs-txn.js +0 -405
  304. package/dist/core/lexical-score.js +0 -25
  305. package/dist/core/maintenance-barrier.js +0 -167
  306. package/dist/execution/executable-identity.js +0 -105
  307. package/dist/execution/guarded-source.js +0 -441
  308. package/dist/indexer/graph/graph-boost.js +0 -427
  309. package/dist/indexer/graph/graph-dedup.js +0 -95
  310. package/dist/indexer/search/name-match.js +0 -35
  311. package/dist/indexer/search/ranking-contributors.js +0 -515
  312. package/dist/indexer/walk/project-context.js +0 -192
  313. package/dist/integrations/agent/execution-cascade.js +0 -566
  314. package/dist/integrations/agent/execution-definitions.js +0 -202
  315. package/dist/integrations/agent/execution-lowering.js +0 -841
  316. package/dist/integrations/agent/execution-preparation.js +0 -98
  317. package/dist/integrations/agent/inline-execution.js +0 -74
  318. package/dist/registry/create-provider-registry.js +0 -29
  319. package/dist/registry/pinned-request-helper.js +0 -247
  320. package/dist/registry/pinned-transport.js +0 -717
  321. package/dist/sources/providers/index.js +0 -14
  322. package/dist/storage/engines/sqlite-migrations.js +0 -271
  323. package/dist/storage/repositories/canaries-repository.js +0 -107
  324. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  325. package/dist/storage/repositories/registry-cache.js +0 -113
  326. package/dist/tasks/scheduler-sync-preview.js +0 -52
  327. package/dist/workflows/freeze/resolve-steps.js +0 -86
  328. package/dist/workflows/freeze/source-freeze.js +0 -64
  329. package/dist/workflows/ir/compile.js +0 -321
  330. package/dist/workflows/ir/environment-v4.js +0 -330
  331. package/dist/workflows/ir/freeze-v4.js +0 -153
  332. package/dist/workflows/ir/schema-v4.js +0 -745
  333. package/dist/workflows/ir/schema.js +0 -354
  334. package/dist/workflows/program/schema.js +0 -78
  335. package/dist/workflows/runtime/checkin.js +0 -57
  336. package/dist/workflows/runtime/plan-classifier.js +0 -196
  337. package/dist/workflows/runtime/unit-checkin.js +0 -45
  338. package/dist/workflows/runtime/unit-phases.js +0 -20
  339. package/dist/workflows/schema.js +0 -4
  340. package/dist/workflows/source-ir/compile.js +0 -200
  341. package/dist/workflows/source-ir/program.js +0 -50
  342. package/dist/workflows/source-ir/result.js +0 -26
  343. package/dist/workflows/source-ir/schema.js +0 -786
  344. package/dist/workflows/source-ir/triggers.js +0 -79
  345. package/dist/workflows/source-ir/uses.js +0 -40
  346. package/dist/workflows/validator.js +0 -60
@@ -7,24 +7,17 @@ import { decodeTaskHistoryMetadata } from "../../storage/repositories/task-histo
7
7
  export function roundRate(value) {
8
8
  return Number(value.toFixed(4));
9
9
  }
10
- export function parseTaskMetadata(row) {
11
- const metadata = decodeTaskHistoryMetadata(row.metadata_json);
12
- return {
13
- ...(metadata.durationMs !== undefined ? { durationMs: metadata.durationMs } : {}),
14
- ...(metadata.detail ? { detail: metadata.detail } : {}),
15
- ...(metadata.engine !== undefined ? { engine: metadata.engine } : {}),
16
- };
17
- }
18
10
  /**
19
- * `parseTaskMetadata`, but per-row skip-and-warn instead of throwing (mirrors
20
- * `listStateProposals`). `decodeTaskHistoryMetadata` already tolerates
21
- * legacy/additive shapes; only genuine corruption reaches this catch, and a
22
- * corrupt row must degrade the metric (excluded, not fatal) rather than abort
23
- * the whole `akm health` computation.
11
+ * `task_history.metadata_json`'s `detail` field, decoded and skip-and-warn on
12
+ * corruption instead of throwing (mirrors `listStateProposals`).
13
+ * `decodeTaskHistoryMetadata` already tolerates legacy/additive shapes; only
14
+ * genuine corruption reaches this catch, and a corrupt row must degrade the
15
+ * metric (excluded, not fatal) rather than abort the whole `akm health`
16
+ * computation.
24
17
  */
25
18
  export function taskFailureDetail(row) {
26
19
  try {
27
- return parseTaskMetadata(row).detail;
20
+ return decodeTaskHistoryMetadata(row.metadata_json).detail ?? undefined;
28
21
  }
29
22
  catch (error) {
30
23
  const message = error instanceof Error ? error.message : String(error);
@@ -37,9 +30,8 @@ export function taskFailureDetail(row) {
37
30
  * that represents a prepared command (agent/LLM) result. `target_kind` is
38
31
  * read in the current (post-D8) vocabulary — the
39
32
  * `025-task-history-vocabulary-backfill` state migration rewrites every
40
- * legacy-vocabulary row (which stored the agent/LLM arm as `"prompt"` and
41
- * the native shell/script arm as `"command"`) before this ever runs against
42
- * it, so a `"command"` row here is unambiguously the agent/LLM arm.
33
+ * legacy-vocabulary row before this ever runs against it, so a `"command"`
34
+ * row here is unambiguously the agent/LLM arm.
43
35
  */
44
36
  export function isAgentTaskHistoryRow(row) {
45
37
  return row.target_kind === "command";
@@ -48,8 +40,7 @@ export function isAgentTaskHistoryRow(row) {
48
40
  * #943: reason-value breakdown for a set of agent (command-kind) task
49
41
  * failure rows — how much of the observed failures are `timeout` vs
50
42
  * `non_zero_exit` vs `spawn_failed` etc., so `akm health`'s `task-fail-rate`
51
- * advisory can say "timeout-dominant" from data rather than log grep. Keeps
52
- * the existing `AgentFailureReason` vocabulary verbatim (spawn.ts) — a
43
+ * advisory can say "timeout-dominant" from data rather than log grep. A
53
44
  * reason-per-row read failure (already warned by {@link taskFailureDetail})
54
45
  * still counts under `"unknown"` rather than being dropped, so the total
55
46
  * always equals `agentFailures.length`.
@@ -62,17 +53,16 @@ export function countAgentFailureReasons(agentFailures) {
62
53
  }
63
54
  return counts;
64
55
  }
65
- function createUnknownImproveMetrics() {
56
+ /** A zeroed accumulator — also what health reports when it could not read state.db at all (#791). */
57
+ export function emptyImproveMetrics() {
66
58
  return {
67
59
  invoked: 0,
68
60
  completed: 0,
69
61
  skipped: 0,
70
62
  skipReasons: {},
71
63
  resultRows: { total: 0, included: 0, skipped: { invalid: 0 } },
72
- plannedRefs: 0,
73
- strategyFilteredRefs: 0,
74
64
  actions: {
75
- reflect: { ok: 0, failed: 0, cooldown: 0, skipped: 0, guardRejected: 0, skippedByReason: {} },
65
+ reflect: { ok: 0, failed: 0, cooldown: 0, skipped: 0 },
76
66
  distill: {
77
67
  queued: 0,
78
68
  llmFailed: 0,
@@ -81,8 +71,6 @@ function createUnknownImproveMetrics() {
81
71
  configDisabled: 0,
82
72
  skipped: 0,
83
73
  skippedByReason: {},
84
- deferred: 0,
85
- deferredByReason: {},
86
74
  },
87
75
  memoryPrune: 0,
88
76
  memoryInference: 0,
@@ -90,110 +78,25 @@ function createUnknownImproveMetrics() {
90
78
  error: 0,
91
79
  },
92
80
  autoAccept: { promoted: 0, validationFailed: 0 },
93
- reflectsWithErrorContext: 0,
94
- coverageGapCount: 0,
95
- deadUrlCount: 0,
96
- deadUrlsChecked: 0,
97
- deadUrlsTotal: 0,
98
- deadUrlsSkipped: 0,
99
81
  memorySummary: { eligible: 0, derived: 0 },
100
- memoryCleanup: {
101
- pruneCandidates: 0,
102
- contradictionCandidates: 0,
103
- beliefStateTransitions: 0,
104
- consolidationCandidates: 0,
105
- archived: 0,
106
- warnings: 0,
107
- },
108
82
  consolidation: {
109
- ran: false,
110
83
  processed: 0,
111
84
  promoted: 0,
112
85
  merged: 0,
113
86
  deleted: 0,
114
87
  contradicted: 0,
115
88
  judgedNoAction: 0,
116
- mergedSecondaries: 0,
117
- failedChunkMemories: 0,
118
- skipReasons: {},
119
89
  failedChunks: 0,
120
90
  totalChunks: 0,
121
91
  durationMs: 0,
122
92
  },
123
- memoryInference: {
124
- ran: false,
125
- considered: 0,
126
- cacheHits: 0,
127
- retryAttempts: 0,
128
- freshAttempts: 0,
129
- splitParents: 0,
130
- written: 0,
131
- skippedNoFacts: 0,
132
- skippedChildExists: 0,
133
- skippedAborted: 0,
134
- unaccounted: 0,
135
- htmlErrorCount: 0,
136
- yieldRate: 0,
137
- durationMs: 0,
138
- },
139
- graphExtraction: {
140
- ran: false,
141
- extractedFiles: 0,
142
- consideredFiles: 0,
143
- entities: 0,
144
- relations: 0,
145
- extractionCoverage: 0,
146
- cacheHits: 0,
147
- cacheMisses: 0,
148
- cacheHitRate: 0,
149
- truncations: 0,
150
- failures: 0,
151
- htmlErrors: 0,
152
- retryAttempts: 0,
153
- nonArrayBatchFailures: 0,
154
- durationMs: 0,
155
- },
156
- sessionExtraction: {
157
- ran: false,
158
- sessionsScanned: 0,
159
- sessionsExtracted: 0,
160
- sessionsSkipped: 0,
161
- proposalsCreated: 0,
162
- warnings: 0,
163
- durationMs: 0,
164
- },
165
- wallTime: {
166
- count: 0,
167
- medianMs: 0,
168
- p95Ms: 0,
169
- minMs: 0,
170
- maxMs: 0,
171
- byPhase: {
172
- consolidation: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
173
- memoryInference: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
174
- graphExtraction: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
175
- },
176
- },
177
- perfTelemetry: {
178
- dedupPoolSize: 0,
179
- llmPoolSize: 0,
180
- embedMs: 0,
181
- embedCacheHits: 0,
182
- embedCacheMisses: 0,
183
- overBudgetRuns: 0,
184
- runsWithTelemetry: 0,
185
- },
186
- coverage: {
187
- rate: Number.NaN,
188
- eligibleFraction: Number.NaN,
189
- acceptedProposals: 0,
190
- distinctRefs: 0,
191
- churnRatio: Number.NaN,
192
- totalAssets: 0,
193
- },
93
+ memoryInference: { considered: 0, freshAttempts: 0, written: 0, skippedNoFacts: 0, yieldRate: 0, durationMs: 0 },
94
+ graphExtraction: { extractedFiles: 0, entities: 0, relations: 0, failures: 0, durationMs: 0 },
95
+ wallTime: { medianMs: 0, p95Ms: 0 },
96
+ coverage: { acceptedProposals: 0, distinctRefs: 0 },
194
97
  };
195
98
  }
196
- export function toFiniteNumber(value) {
99
+ function toFiniteNumber(value) {
197
100
  if (typeof value === "number" && Number.isFinite(value))
198
101
  return value;
199
102
  if (typeof value === "string" && value.trim()) {
@@ -203,47 +106,9 @@ export function toFiniteNumber(value) {
203
106
  }
204
107
  return 0;
205
108
  }
206
- /**
207
- * Event-derived metrics. Only `completed` and skipReasons/invoked are sourced
208
- * from events in v2 — the richer fields come from {@link summarizeImproveRuns}.
209
- * The function still receives `improve_completed` events so that the completed
210
- * count reflects the canonical event stream (it lines up 1:1 with improve_runs
211
- * rows in practice, but the events table remains the system-of-record for the
212
- * existence of a run).
213
- */
214
- export function summarizeImproveCompleted(events) {
215
- const metrics = createUnknownImproveMetrics();
216
- metrics.completed = events.length;
217
- return metrics;
218
- }
219
- /**
220
- * Read the machine-readable reason from a distill `outcome: "skipped"` result.
221
- */
222
- function classifyDistillSkipReason(r) {
223
- const explicitReason = typeof r?.skipReason === "string" ? r.skipReason : undefined;
224
- return explicitReason || "unknown";
225
- }
226
- // ── projectRunMetrics phase helpers (chunk-9 WI-9.5c; file-level decompose of
227
- // the single-envelope projection — each helper mutates its own disjoint
228
- // subtree of the accumulator from the raw envelope, so call order does not
229
- // matter; kept in envelope-field order for readability) ──────────────────────
230
- /** plannedRefs / strategyFilteredRefs ref-count accounting. */
231
- function applyPlannedRefs(metrics, result) {
232
- // plannedRefs (array of {ref, reason})
233
- const plannedRefs = result.plannedRefs;
234
- if (Array.isArray(plannedRefs))
235
- metrics.plannedRefs += plannedRefs.length;
236
- // strategyFilteredRefs (array of {ref, reason}) — 2026-05-27: pre-filter
237
- // bucket from `collectEligibleRefs` so the metric reflects work the
238
- // planner dropped before signal-delta / per-pass dispatch.
239
- const strategyFilteredRefs = result.strategyFilteredRefs;
240
- if (Array.isArray(strategyFilteredRefs))
241
- metrics.strategyFilteredRefs += strategyFilteredRefs.length;
242
- }
243
- /** One `actions[]` entry → the matching counter bucket (see ImproveHealthMetrics.actions jsdoc for the taxonomy). */
109
+ /** One `actions[]` entry → its counter bucket; unknown modes/outcomes are not counted. */
244
110
  function applyAction(metrics, action) {
245
- const mode = typeof action.mode === "string" ? action.mode : "";
246
- switch (mode) {
111
+ switch (typeof action.mode === "string" ? action.mode : "") {
247
112
  case "reflect":
248
113
  metrics.actions.reflect.ok += 1;
249
114
  break;
@@ -253,20 +118,12 @@ function applyAction(metrics, action) {
253
118
  case "reflect-cooldown":
254
119
  metrics.actions.reflect.cooldown += 1;
255
120
  break;
256
- case "reflect-skipped": {
121
+ case "reflect-skipped":
257
122
  metrics.actions.reflect.skipped += 1;
258
- const r = action.result;
259
- const reason = typeof r?.reason === "string" && r.reason.trim() ? r.reason : "unknown";
260
- metrics.actions.reflect.skippedByReason[reason] = (metrics.actions.reflect.skippedByReason[reason] ?? 0) + 1;
261
- break;
262
- }
263
- case "reflect-guard-rejected":
264
- metrics.actions.reflect.guardRejected += 1;
265
123
  break;
266
124
  case "distill": {
267
- const r = action.result;
268
- const outcome = typeof r?.outcome === "string" ? r.outcome : "";
269
- switch (outcome) {
125
+ const result = action.result;
126
+ switch (typeof result?.outcome === "string" ? result.outcome : "") {
270
127
  case "queued":
271
128
  metrics.actions.distill.queued += 1;
272
129
  break;
@@ -283,21 +140,6 @@ function applyAction(metrics, action) {
283
140
  case "config_disabled":
284
141
  metrics.actions.distill.configDisabled += 1;
285
142
  break;
286
- case "skipped": {
287
- // Previously dropped on the floor. The four sub-paths that emit
288
- // `outcome: "skipped"` (see distill.ts:893, 1024, 1120, 1576):
289
- // - recursive_lesson_input (type guard refused a lesson input)
290
- // - conflict_noop (LLM resolved destination conflict as NOOP)
291
- // - proposal-skipped cooldown / dedup at persistence
292
- // 465 events/7d in the user's live stack. The result message
293
- // typically encodes the reason; we also accept an explicit
294
- // `skipReason` field when downstream code sets it.
295
- metrics.actions.distill.deferred += 1;
296
- const reason = classifyDistillSkipReason(r);
297
- metrics.actions.distill.deferredByReason[reason] =
298
- (metrics.actions.distill.deferredByReason[reason] ?? 0) + 1;
299
- break;
300
- }
301
143
  default:
302
144
  break;
303
145
  }
@@ -315,261 +157,89 @@ function applyAction(metrics, action) {
315
157
  case "error":
316
158
  metrics.actions.error += 1;
317
159
  break;
160
+ default:
161
+ break;
318
162
  }
319
163
  }
320
- /** actions: split reflect / distill by outcome, count others. */
321
- function applyActionsList(metrics, result) {
322
- const actions = result.actions;
323
- if (!Array.isArray(actions))
324
- return;
325
- for (const action of actions) {
326
- applyAction(metrics, action);
164
+ /** Project one `improve_runs.result_json` envelope into a single-run accumulator. */
165
+ function projectRunMetrics(result) {
166
+ const metrics = emptyImproveMetrics();
167
+ if (Array.isArray(result.actions)) {
168
+ for (const action of result.actions)
169
+ applyAction(metrics, action);
327
170
  }
328
- }
329
- /**
330
- * Read the bounded `distillSkipped` aggregate into the corresponding total and
331
- * per-reason health metrics.
332
- */
333
- function applyDistillSkippedAggregate(metrics, result) {
334
171
  const distillSkipped = result.distillSkipped;
335
- if (!distillSkipped || typeof distillSkipped !== "object")
336
- return;
337
- metrics.actions.distill.skipped += toFiniteNumber(distillSkipped.total);
338
- const byReason = distillSkipped.byReason;
339
- if (byReason && typeof byReason === "object") {
340
- for (const [reason, count] of Object.entries(byReason)) {
172
+ if (distillSkipped && typeof distillSkipped === "object") {
173
+ metrics.actions.distill.skipped += toFiniteNumber(distillSkipped.total);
174
+ for (const [reason, count] of Object.entries(distillSkipped.byReason ?? {})) {
341
175
  metrics.actions.distill.skippedByReason[reason] =
342
176
  (metrics.actions.distill.skippedByReason[reason] ?? 0) + toFiniteNumber(count);
343
177
  }
344
178
  }
345
- }
346
- /** autoAccept + the small envelope-level counters (reflectsWithErrorContext, coverageGaps, deadUrls). */
347
- function applyMiscCounters(metrics, result) {
348
179
  metrics.autoAccept.promoted += toFiniteNumber(result.gateAutoAcceptedCount);
349
180
  metrics.autoAccept.validationFailed += toFiniteNumber(result.gateAutoAcceptFailedCount);
350
- metrics.reflectsWithErrorContext += toFiniteNumber(result.reflectsWithErrorContext);
351
- if (Array.isArray(result.coverageGaps))
352
- metrics.coverageGapCount += result.coverageGaps.length;
353
- if (Array.isArray(result.deadUrls))
354
- metrics.deadUrlCount += result.deadUrls.length;
355
- const deadUrlCoverage = result.deadUrlCoverage;
356
- if (deadUrlCoverage && typeof deadUrlCoverage === "object") {
357
- metrics.deadUrlsChecked += toFiniteNumber(deadUrlCoverage.checked);
358
- metrics.deadUrlsTotal += toFiniteNumber(deadUrlCoverage.total);
359
- metrics.deadUrlsSkipped += toFiniteNumber(deadUrlCoverage.skipped);
360
- }
361
- }
362
- function applyMemorySummary(metrics, result) {
363
181
  const memorySummary = result.memorySummary;
364
- if (!memorySummary)
365
- return;
366
- metrics.memorySummary.eligible += toFiniteNumber(memorySummary.eligible);
367
- metrics.memorySummary.derived += toFiniteNumber(memorySummary.derived);
368
- }
369
- function applyMemoryCleanup(metrics, result) {
370
- const memoryCleanup = result.memoryCleanup;
371
- if (!memoryCleanup)
372
- return;
373
- if (Array.isArray(memoryCleanup.pruneCandidates))
374
- metrics.memoryCleanup.pruneCandidates += memoryCleanup.pruneCandidates.length;
375
- if (Array.isArray(memoryCleanup.contradictionCandidates))
376
- metrics.memoryCleanup.contradictionCandidates += memoryCleanup.contradictionCandidates.length;
377
- if (Array.isArray(memoryCleanup.beliefStateTransitions))
378
- metrics.memoryCleanup.beliefStateTransitions += memoryCleanup.beliefStateTransitions.length;
379
- if (Array.isArray(memoryCleanup.consolidationCandidates))
380
- metrics.memoryCleanup.consolidationCandidates += memoryCleanup.consolidationCandidates.length;
381
- if (Array.isArray(memoryCleanup.archived))
382
- metrics.memoryCleanup.archived += memoryCleanup.archived.length;
383
- if (Array.isArray(memoryCleanup.warnings))
384
- metrics.memoryCleanup.warnings += memoryCleanup.warnings.length;
385
- }
386
- function applyConsolidation(metrics, result) {
387
- const consolidation = result.consolidation;
388
- if (!consolidation)
389
- return;
390
- metrics.consolidation.processed += toFiniteNumber(consolidation.processed);
391
- metrics.consolidation.merged += toFiniteNumber(consolidation.merged);
392
- metrics.consolidation.deleted += toFiniteNumber(consolidation.deleted);
393
- metrics.consolidation.contradicted += toFiniteNumber(consolidation.contradicted);
394
- if (Array.isArray(consolidation.promoted))
395
- metrics.consolidation.promoted += consolidation.promoted.length;
396
- metrics.consolidation.failedChunks += toFiniteNumber(consolidation.failedChunks);
397
- metrics.consolidation.totalChunks += toFiniteNumber(consolidation.totalChunks);
398
- metrics.consolidation.durationMs += toFiniteNumber(consolidation.durationMs);
399
- metrics.consolidation.judgedNoAction += toFiniteNumber(consolidation.judgedNoAction);
400
- metrics.consolidation.mergedSecondaries += toFiniteNumber(consolidation.mergedSecondaries);
401
- metrics.consolidation.failedChunkMemories += toFiniteNumber(consolidation.failedChunkMemories);
402
- // Structured emitter (new on this branch): consolidate.ts now pushes
403
- // per-ref grouped `{ref, skips: [{op, reason}]}` entries to `skipReasons`
404
- // for every deterministic post-LLM rejection. Each ref appears once but
405
- // may carry multiple skips; aggregate every reason. Pre-fix envelopes have
406
- // neither field, so be defensive.
407
- const skipReasons = consolidation.skipReasons;
408
- if (Array.isArray(skipReasons)) {
409
- for (const entry of skipReasons) {
410
- if (!entry || typeof entry !== "object")
411
- continue;
412
- const skips = entry.skips;
413
- if (!Array.isArray(skips))
414
- continue;
415
- for (const skip of skips) {
416
- if (!skip || typeof skip !== "object")
417
- continue;
418
- const reason = skip.reason;
419
- if (typeof reason !== "string" || !reason.trim())
420
- continue;
421
- metrics.consolidation.skipReasons[reason] = (metrics.consolidation.skipReasons[reason] ?? 0) + 1;
422
- }
423
- }
182
+ if (memorySummary) {
183
+ metrics.memorySummary.eligible += toFiniteNumber(memorySummary.eligible);
184
+ metrics.memorySummary.derived += toFiniteNumber(memorySummary.derived);
424
185
  }
425
- // WS-5: extract perf telemetry from the consolidation envelope.
426
- // Pre-WS-5 envelopes lack `perfTelemetry`; be defensive.
427
- const perf = consolidation.perfTelemetry;
428
- if (perf) {
429
- metrics.perfTelemetry.runsWithTelemetry += 1;
430
- metrics.perfTelemetry.dedupPoolSize += toFiniteNumber(perf.dedupPoolSize);
431
- metrics.perfTelemetry.llmPoolSize += toFiniteNumber(perf.llmPoolSize);
432
- metrics.perfTelemetry.embedMs += toFiniteNumber(perf.embedMs);
433
- metrics.perfTelemetry.embedCacheHits += toFiniteNumber(perf.embedCacheHits);
434
- metrics.perfTelemetry.embedCacheMisses += toFiniteNumber(perf.embedCacheMisses);
435
- const budgetFrac = toFiniteNumber(perf.estimatedBudgetFractionUsed);
436
- if (budgetFrac > 1.0)
437
- metrics.perfTelemetry.overBudgetRuns += 1;
186
+ const consolidation = result.consolidation;
187
+ if (consolidation) {
188
+ const cons = metrics.consolidation;
189
+ cons.processed += toFiniteNumber(consolidation.processed);
190
+ if (Array.isArray(consolidation.promoted))
191
+ cons.promoted += consolidation.promoted.length;
192
+ cons.merged += toFiniteNumber(consolidation.merged);
193
+ cons.deleted += toFiniteNumber(consolidation.deleted);
194
+ cons.contradicted += toFiniteNumber(consolidation.contradicted);
195
+ cons.judgedNoAction += toFiniteNumber(consolidation.judgedNoAction);
196
+ cons.failedChunks += toFiniteNumber(consolidation.failedChunks);
197
+ cons.totalChunks += toFiniteNumber(consolidation.totalChunks);
198
+ cons.durationMs += toFiniteNumber(consolidation.durationMs);
438
199
  }
439
- }
440
- function applyMemoryInference(metrics, result) {
441
200
  const memoryInference = result.memoryInference;
442
201
  if (memoryInference) {
443
202
  const considered = toFiniteNumber(memoryInference.considered);
444
- const writtenFacts = toFiniteNumber(memoryInference.writtenFacts);
445
- metrics.memoryInference.considered += considered;
446
- metrics.memoryInference.cacheHits += toFiniteNumber(memoryInference.cacheHits);
447
- metrics.memoryInference.retryAttempts += toFiniteNumber(memoryInference.retryAttempts);
448
- metrics.memoryInference.splitParents += toFiniteNumber(memoryInference.splitParents);
449
- metrics.memoryInference.written += writtenFacts;
450
- metrics.memoryInference.skippedNoFacts += toFiniteNumber(memoryInference.skippedNoFacts);
451
- metrics.memoryInference.skippedChildExists += toFiniteNumber(memoryInference.skippedChildExists);
452
- metrics.memoryInference.skippedAborted += toFiniteNumber(memoryInference.skippedAborted);
453
- metrics.memoryInference.unaccounted += toFiniteNumber(memoryInference.unaccounted);
454
- metrics.memoryInference.htmlErrorCount += toFiniteNumber(memoryInference.htmlErrorCount);
203
+ const mi = metrics.memoryInference;
204
+ mi.considered += considered;
205
+ // Cache hits and budget-aborted records never reached the LLM; excluding
206
+ // them keeps the yield rate a statement about real inference attempts.
207
+ mi.freshAttempts += Math.max(0, considered - toFiniteNumber(memoryInference.cacheHits) - toFiniteNumber(memoryInference.skippedAborted));
208
+ mi.written += toFiniteNumber(memoryInference.writtenFacts);
209
+ mi.skippedNoFacts += toFiniteNumber(memoryInference.skippedNoFacts);
455
210
  }
456
211
  metrics.memoryInference.durationMs += toFiniteNumber(result.memoryInferenceDurationMs);
457
- }
458
- function applyGraphExtraction(metrics, result) {
459
212
  const graphExtraction = result.graphExtraction;
460
213
  if (graphExtraction) {
461
- const quality = graphExtraction.quality;
462
- if (quality) {
463
- metrics.graphExtraction.extractedFiles += toFiniteNumber(quality.extractedFiles);
464
- metrics.graphExtraction.consideredFiles += toFiniteNumber(quality.consideredFiles);
465
- }
466
- metrics.graphExtraction.entities += toFiniteNumber(graphExtraction.totalEntities);
467
- metrics.graphExtraction.relations += toFiniteNumber(graphExtraction.totalRelations);
214
+ const ge = metrics.graphExtraction;
215
+ // This run's counts, like entities and relations. `quality` describes the
216
+ // whole stored graph after the run; summing it would count each stored file
217
+ // once per run in the window.
218
+ ge.extractedFiles += toFiniteNumber(graphExtraction.extracted);
219
+ ge.entities += toFiniteNumber(graphExtraction.totalEntities);
220
+ ge.relations += toFiniteNumber(graphExtraction.totalRelations);
468
221
  const telemetry = graphExtraction.telemetry;
469
- if (telemetry) {
470
- metrics.graphExtraction.cacheHits += toFiniteNumber(telemetry.cacheHits);
471
- metrics.graphExtraction.cacheMisses += toFiniteNumber(telemetry.cacheMisses);
472
- metrics.graphExtraction.truncations += toFiniteNumber(telemetry.truncationCount);
473
- metrics.graphExtraction.failures += toFiniteNumber(telemetry.failureCount);
474
- metrics.graphExtraction.htmlErrors += toFiniteNumber(telemetry.htmlErrorCount);
475
- metrics.graphExtraction.retryAttempts += toFiniteNumber(telemetry.retryAttempts);
476
- metrics.graphExtraction.nonArrayBatchFailures += toFiniteNumber(telemetry.nonArrayBatchFailures);
477
- }
222
+ ge.failures += toFiniteNumber(telemetry?.failureCount);
478
223
  }
479
224
  metrics.graphExtraction.durationMs += toFiniteNumber(result.graphExtractionDurationMs);
480
- }
481
- function applySessionExtraction(metrics, result) {
482
- if (!Array.isArray(result.extract))
483
- return;
484
- for (const e of result.extract) {
485
- metrics.sessionExtraction.sessionsScanned += toFiniteNumber(e.sessionsProcessed);
486
- metrics.sessionExtraction.sessionsSkipped += toFiniteNumber(e.sessionsSkipped);
487
- if (Array.isArray(e.sessions)) {
488
- metrics.sessionExtraction.sessionsExtracted += e.sessions.filter((s) => Array.isArray(s.proposalIds) && s.proposalIds.length > 0).length;
489
- }
490
- metrics.sessionExtraction.proposalsCreated += Array.isArray(e.proposals) ? e.proposals.length : 0;
491
- metrics.sessionExtraction.warnings += Array.isArray(e.warnings) ? e.warnings.length : 0;
492
- metrics.sessionExtraction.durationMs += toFiniteNumber(e.durationMs);
493
- }
494
- }
495
- /**
496
- * Project a single `improve_runs.result_json` envelope into an accumulator-shaped
497
- * ImproveHealthMetrics. The aggregator merges these per-row metrics into one
498
- * window-level metric. File-level decomposed (chunk-9 WI-9.5c) into one
499
- * `apply*` helper per envelope section above; each mutates its own disjoint
500
- * subtree of the accumulator so the call order below is not load-bearing.
501
- */
502
- function projectRunMetrics(result) {
503
- const metrics = createUnknownImproveMetrics();
504
- applyPlannedRefs(metrics, result);
505
- applyActionsList(metrics, result);
506
- applyDistillSkippedAggregate(metrics, result);
507
- applyMiscCounters(metrics, result);
508
- applyMemorySummary(metrics, result);
509
- applyMemoryCleanup(metrics, result);
510
- applyConsolidation(metrics, result);
511
- applyMemoryInference(metrics, result);
512
- applyGraphExtraction(metrics, result);
513
- applySessionExtraction(metrics, result);
514
225
  return metrics;
515
226
  }
516
- /**
517
- * Finalize derived flags and rates on an accumulator. Used both for the
518
- * window-level aggregate and for each per-run row in --detail per-run mode
519
- * so the single-row metrics still expose `ran` / `yieldRate` / `cacheHitRate`.
520
- */
227
+ /** Derived rates on an accumulator (window aggregate or single run). */
521
228
  function finalizeImproveMetrics(metrics) {
522
- metrics.consolidation.ran =
523
- metrics.consolidation.processed > 0 ||
524
- metrics.consolidation.durationMs > 0 ||
525
- metrics.consolidation.promoted > 0 ||
526
- metrics.consolidation.merged > 0 ||
527
- metrics.consolidation.deleted > 0 ||
528
- metrics.consolidation.contradicted > 0 ||
529
- metrics.consolidation.totalChunks > 0;
530
- metrics.memoryInference.ran =
531
- metrics.memoryInference.considered > 0 ||
532
- metrics.memoryInference.written > 0 ||
533
- metrics.memoryInference.durationMs > 0;
534
- // Yield denominator excludes cache hits and aborted records.
535
- metrics.memoryInference.freshAttempts = Math.max(0, metrics.memoryInference.considered - metrics.memoryInference.cacheHits - metrics.memoryInference.skippedAborted);
536
- metrics.memoryInference.yieldRate =
537
- metrics.memoryInference.freshAttempts > 0
538
- ? roundRate(metrics.memoryInference.written / metrics.memoryInference.freshAttempts)
539
- : 0;
540
- metrics.graphExtraction.ran =
541
- metrics.graphExtraction.extractedFiles > 0 ||
542
- metrics.graphExtraction.entities > 0 ||
543
- metrics.graphExtraction.durationMs > 0;
544
- const cacheTotal = metrics.graphExtraction.cacheHits + metrics.graphExtraction.cacheMisses;
545
- metrics.graphExtraction.cacheHitRate = cacheTotal > 0 ? roundRate(metrics.graphExtraction.cacheHits / cacheTotal) : 0;
546
- metrics.graphExtraction.extractionCoverage =
547
- metrics.graphExtraction.consideredFiles > 0
548
- ? roundRate(metrics.graphExtraction.extractedFiles / metrics.graphExtraction.consideredFiles)
549
- : 0;
550
- metrics.sessionExtraction.ran =
551
- metrics.sessionExtraction.sessionsScanned > 0 ||
552
- metrics.sessionExtraction.proposalsCreated > 0 ||
553
- metrics.sessionExtraction.durationMs > 0;
229
+ const mi = metrics.memoryInference;
230
+ mi.yieldRate = mi.freshAttempts > 0 ? roundRate(mi.written / mi.freshAttempts) : 0;
554
231
  }
555
232
  /**
556
- * Merge per-row metrics from `src` into accumulator `dst`. All numeric fields
557
- * are additive; cumulative rates are recomputed by finalizeImproveMetrics.
233
+ * Merge per-run metrics from `src` into accumulator `dst`. Every counter is
234
+ * additive; `memorySummary` is a whole-stash snapshot and is deliberately NOT
235
+ * merged (summing it across runs inflated it ~N× — the 1.2M-eligible bug), and
236
+ * the derived rate is recomputed by {@link finalizeImproveMetrics}.
558
237
  */
559
238
  function mergeImproveMetrics(dst, src) {
560
- dst.plannedRefs += src.plannedRefs;
561
- // strategyFilteredRefs is the count of refs the planner drops up-front for the
562
- // active strategy — recomputed against the (stable) stash every run, so it is a
563
- // snapshot, NOT a per-run increment. Summing it re-counts the same refs each
564
- // run (the ~2.4M bug). Set from the most recent run in summarizeImproveRuns.
565
239
  dst.actions.reflect.ok += src.actions.reflect.ok;
566
240
  dst.actions.reflect.failed += src.actions.reflect.failed;
567
241
  dst.actions.reflect.cooldown += src.actions.reflect.cooldown;
568
242
  dst.actions.reflect.skipped += src.actions.reflect.skipped;
569
- dst.actions.reflect.guardRejected += src.actions.reflect.guardRejected;
570
- for (const [reason, count] of Object.entries(src.actions.reflect.skippedByReason)) {
571
- dst.actions.reflect.skippedByReason[reason] = (dst.actions.reflect.skippedByReason[reason] ?? 0) + count;
572
- }
573
243
  dst.actions.distill.queued += src.actions.distill.queued;
574
244
  dst.actions.distill.llmFailed += src.actions.distill.llmFailed;
575
245
  dst.actions.distill.judgeRejected += src.actions.distill.judgeRejected;
@@ -579,86 +249,31 @@ function mergeImproveMetrics(dst, src) {
579
249
  for (const [reason, count] of Object.entries(src.actions.distill.skippedByReason)) {
580
250
  dst.actions.distill.skippedByReason[reason] = (dst.actions.distill.skippedByReason[reason] ?? 0) + count;
581
251
  }
582
- dst.actions.distill.deferred += src.actions.distill.deferred;
583
- for (const [reason, count] of Object.entries(src.actions.distill.deferredByReason)) {
584
- dst.actions.distill.deferredByReason[reason] = (dst.actions.distill.deferredByReason[reason] ?? 0) + count;
585
- }
586
252
  dst.actions.memoryPrune += src.actions.memoryPrune;
587
253
  dst.actions.memoryInference += src.actions.memoryInference;
588
254
  dst.actions.graphExtraction += src.actions.graphExtraction;
589
255
  dst.actions.error += src.actions.error;
590
256
  dst.autoAccept.promoted += src.autoAccept.promoted;
591
257
  dst.autoAccept.validationFailed += src.autoAccept.validationFailed;
592
- dst.reflectsWithErrorContext += src.reflectsWithErrorContext;
593
- dst.coverageGapCount += src.coverageGapCount;
594
- dst.deadUrlCount += src.deadUrlCount;
595
- dst.deadUrlsChecked += src.deadUrlsChecked;
596
- dst.deadUrlsTotal += src.deadUrlsTotal;
597
- dst.deadUrlsSkipped += src.deadUrlsSkipped;
598
- // NOTE: memorySummary (derived/eligible) is a WHOLE-STASH snapshot recorded on
599
- // every run, NOT a per-run increment — summing it across the window inflates
600
- // it ~N× (the 1.2M-eligible bug). It is set from the most recent run in
601
- // summarizeImproveRuns instead, so it is intentionally not merged here.
602
- dst.memoryCleanup.pruneCandidates += src.memoryCleanup.pruneCandidates;
603
- dst.memoryCleanup.contradictionCandidates += src.memoryCleanup.contradictionCandidates;
604
- dst.memoryCleanup.beliefStateTransitions += src.memoryCleanup.beliefStateTransitions;
605
- dst.memoryCleanup.consolidationCandidates += src.memoryCleanup.consolidationCandidates;
606
- dst.memoryCleanup.archived += src.memoryCleanup.archived;
607
- dst.memoryCleanup.warnings += src.memoryCleanup.warnings;
608
258
  dst.consolidation.processed += src.consolidation.processed;
609
259
  dst.consolidation.promoted += src.consolidation.promoted;
610
260
  dst.consolidation.merged += src.consolidation.merged;
611
261
  dst.consolidation.deleted += src.consolidation.deleted;
612
262
  dst.consolidation.contradicted += src.consolidation.contradicted;
263
+ dst.consolidation.judgedNoAction += src.consolidation.judgedNoAction;
613
264
  dst.consolidation.failedChunks += src.consolidation.failedChunks;
614
265
  dst.consolidation.totalChunks += src.consolidation.totalChunks;
615
266
  dst.consolidation.durationMs += src.consolidation.durationMs;
616
- dst.consolidation.judgedNoAction += src.consolidation.judgedNoAction;
617
- dst.consolidation.mergedSecondaries += src.consolidation.mergedSecondaries;
618
- dst.consolidation.failedChunkMemories += src.consolidation.failedChunkMemories;
619
- for (const [reason, count] of Object.entries(src.consolidation.skipReasons)) {
620
- dst.consolidation.skipReasons[reason] = (dst.consolidation.skipReasons[reason] ?? 0) + count;
621
- }
622
267
  dst.memoryInference.considered += src.memoryInference.considered;
623
- dst.memoryInference.cacheHits += src.memoryInference.cacheHits;
624
- dst.memoryInference.splitParents += src.memoryInference.splitParents;
268
+ dst.memoryInference.freshAttempts += src.memoryInference.freshAttempts;
625
269
  dst.memoryInference.written += src.memoryInference.written;
626
270
  dst.memoryInference.skippedNoFacts += src.memoryInference.skippedNoFacts;
627
- dst.memoryInference.skippedChildExists += src.memoryInference.skippedChildExists;
628
- dst.memoryInference.skippedAborted += src.memoryInference.skippedAborted;
629
- dst.memoryInference.unaccounted += src.memoryInference.unaccounted;
630
- dst.memoryInference.htmlErrorCount += src.memoryInference.htmlErrorCount;
631
- dst.memoryInference.retryAttempts += src.memoryInference.retryAttempts;
632
271
  dst.memoryInference.durationMs += src.memoryInference.durationMs;
633
272
  dst.graphExtraction.extractedFiles += src.graphExtraction.extractedFiles;
634
- dst.graphExtraction.consideredFiles += src.graphExtraction.consideredFiles;
635
273
  dst.graphExtraction.entities += src.graphExtraction.entities;
636
274
  dst.graphExtraction.relations += src.graphExtraction.relations;
637
- dst.graphExtraction.cacheHits += src.graphExtraction.cacheHits;
638
- dst.graphExtraction.cacheMisses += src.graphExtraction.cacheMisses;
639
- dst.graphExtraction.truncations += src.graphExtraction.truncations;
640
275
  dst.graphExtraction.failures += src.graphExtraction.failures;
641
- dst.graphExtraction.htmlErrors += src.graphExtraction.htmlErrors;
642
- dst.graphExtraction.nonArrayBatchFailures += src.graphExtraction.nonArrayBatchFailures;
643
- dst.graphExtraction.retryAttempts += src.graphExtraction.retryAttempts;
644
276
  dst.graphExtraction.durationMs += src.graphExtraction.durationMs;
645
- dst.sessionExtraction.sessionsScanned += src.sessionExtraction.sessionsScanned;
646
- dst.sessionExtraction.sessionsExtracted += src.sessionExtraction.sessionsExtracted;
647
- dst.sessionExtraction.sessionsSkipped += src.sessionExtraction.sessionsSkipped;
648
- dst.sessionExtraction.proposalsCreated += src.sessionExtraction.proposalsCreated;
649
- dst.sessionExtraction.warnings += src.sessionExtraction.warnings;
650
- dst.sessionExtraction.durationMs += src.sessionExtraction.durationMs;
651
- // WS-5: merge perf telemetry (additive sums).
652
- dst.perfTelemetry.dedupPoolSize += src.perfTelemetry.dedupPoolSize;
653
- dst.perfTelemetry.llmPoolSize += src.perfTelemetry.llmPoolSize;
654
- dst.perfTelemetry.embedMs += src.perfTelemetry.embedMs;
655
- dst.perfTelemetry.embedCacheHits += src.perfTelemetry.embedCacheHits;
656
- dst.perfTelemetry.embedCacheMisses += src.perfTelemetry.embedCacheMisses;
657
- dst.perfTelemetry.overBudgetRuns += src.perfTelemetry.overBudgetRuns;
658
- dst.perfTelemetry.runsWithTelemetry += src.perfTelemetry.runsWithTelemetry;
659
- // coverage: acceptedProposals is additive; totalAssets is a snapshot (like memorySummary).
660
- // totalAssets is intentionally NOT merged here — set from the most recent run in summarizeImproveRuns.
661
- dst.coverage.acceptedProposals += src.coverage.acceptedProposals;
662
277
  }
663
278
  function compareImproveRunRecency(a, b) {
664
279
  const started = a.started_at.localeCompare(b.started_at);
@@ -669,101 +284,48 @@ function compareImproveRunRecency(a, b) {
669
284
  return completed;
670
285
  return a.id.localeCompare(b.id);
671
286
  }
287
+ /**
288
+ * Aggregate the window's `improve_runs` rows. `runCount` is every non-dry-run
289
+ * row in the window; rows whose envelope does not decode are counted under
290
+ * `resultRows.skipped.invalid` and excluded from the result-derived metrics.
291
+ */
672
292
  export function summarizeImproveRuns(db, since, until) {
673
- const accum = createUnknownImproveMetrics();
293
+ const accum = emptyImproveMetrics();
674
294
  const rows = queryImproveRuns(db, since, until);
675
- // Per-phase wall-time samples. Each entry is one envelope's durationMs for
676
- // that phase. Phases that did not run on a given envelope are simply
677
- // omitted (NOT counted as 0) so the median/p95 reflect actual phase work.
678
- const phaseDurations = {
679
- consolidation: [],
680
- memoryInference: [],
681
- graphExtraction: [],
682
- };
295
+ const resultRows = { total: rows.length, included: 0, skipped: { invalid: 0 } };
683
296
  // memorySummary is a whole-stash snapshot per run, so the window value is the
684
- // MOST RECENT run's snapshot (current state) — not a sum across runs.
685
- let latestCompleteRow;
686
- let latestMemorySummary;
687
- let latestStrategyFilteredRefs = 0;
688
- if (!accum.resultRows)
689
- throw new Error("invariant: improve result-row accounting was not initialized");
690
- accum.resultRows.total = rows.length;
297
+ // newest complete run's snapshot (current state) — not a sum across runs.
298
+ let latest;
691
299
  for (const row of rows) {
692
- let decoded;
300
+ let result;
693
301
  try {
694
- decoded = decodeImproveResult(row.result_json);
302
+ result = decodeImproveResult(row.result_json).envelope;
695
303
  }
696
304
  catch {
697
- accum.resultRows.skipped.invalid += 1;
305
+ resultRows.skipped.invalid += 1;
698
306
  continue;
699
307
  }
700
- accum.resultRows.included += 1;
701
- const result = decoded.envelope;
308
+ resultRows.included += 1;
702
309
  const perRow = projectRunMetrics(result);
703
310
  mergeImproveMetrics(accum, perRow);
704
- const startMs = new Date(row.started_at).getTime();
705
311
  if (result.terminated === undefined &&
706
- Number.isFinite(startMs) &&
707
- (latestCompleteRow === undefined || compareImproveRunRecency(row, latestCompleteRow) > 0)) {
708
- latestCompleteRow = row;
709
- latestMemorySummary = perRow.memorySummary;
710
- latestStrategyFilteredRefs = perRow.strategyFilteredRefs;
312
+ Number.isFinite(new Date(row.started_at).getTime()) &&
313
+ (latest === undefined || compareImproveRunRecency(row, latest.row) > 0)) {
314
+ latest = { row, memorySummary: perRow.memorySummary };
711
315
  }
712
- // Collect per-phase durations directly off the envelope. consolidation's
713
- // duration lives inside the sub-object; memoryInference and graphExtraction
714
- // expose top-level *DurationMs keys (`memoryInferenceDurationMs`,
715
- // `graphExtractionDurationMs`) when they actually ran on that envelope.
716
- const consol = result.consolidation;
717
- const consolMs = toFiniteNumber(consol?.durationMs);
718
- if (consolMs > 0)
719
- phaseDurations.consolidation.push(consolMs);
720
- const memMs = toFiniteNumber(result.memoryInferenceDurationMs);
721
- if (memMs > 0)
722
- phaseDurations.memoryInference.push(memMs);
723
- const graphMs = toFiniteNumber(result.graphExtractionDurationMs);
724
- if (graphMs > 0)
725
- phaseDurations.graphExtraction.push(graphMs);
726
316
  }
727
317
  finalizeImproveMetrics(accum);
728
- if (latestMemorySummary)
729
- accum.memorySummary = latestMemorySummary;
730
- accum.strategyFilteredRefs = latestStrategyFilteredRefs;
731
- accum.wallTime.byPhase = {
732
- consolidation: summarizePhaseDurations(phaseDurations.consolidation),
733
- memoryInference: summarizePhaseDurations(phaseDurations.memoryInference),
734
- graphExtraction: summarizePhaseDurations(phaseDurations.graphExtraction),
735
- };
318
+ accum.resultRows = resultRows;
319
+ if (latest)
320
+ accum.memorySummary = latest.memorySummary;
736
321
  return { metrics: accum, runCount: rows.length };
737
322
  }
738
- /**
739
- * Aggregate a list of per-envelope phase durations into the
740
- * `wallTime.byPhase.*` shape: count, total, median, p95. Median/p95 use the
741
- * same nearest-rank picker as the top-level wallTime stats so the two are
742
- * comparable.
743
- */
744
- export function summarizePhaseDurations(samples) {
745
- if (samples.length === 0)
746
- return { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 };
747
- const sorted = [...samples].sort((a, b) => a - b);
748
- const pick = (q) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))] ?? 0;
749
- const totalMs = sorted.reduce((acc, n) => acc + n, 0);
750
- return {
751
- count: sorted.length,
752
- totalMs,
753
- medianMs: pick(0.5),
754
- p95Ms: pick(0.95),
755
- };
756
- }
757
- /**
758
- * Project an improve_runs row + wall-time lookup into a single ImproveRunSummary.
759
- * Used by `akm health --detail per-run`.
760
- */
323
+ /** Project an improve_runs row + wall time + task attribution into one {@link ImproveRunSummary}. */
761
324
  export function projectImproveRunSummary(row, wallTimeMs, taskId) {
762
325
  let result = {};
763
326
  let resultStatus = "invalid";
764
327
  try {
765
- const decoded = decodeImproveResult(row.result_json);
766
- result = decoded.envelope;
328
+ result = decodeImproveResult(row.result_json).envelope;
767
329
  resultStatus = "valid";
768
330
  }
769
331
  catch {
@@ -773,10 +335,7 @@ export function projectImproveRunSummary(row, wallTimeMs, taskId) {
773
335
  }
774
336
  const perRow = projectRunMetrics(result);
775
337
  finalizeImproveMetrics(perRow);
776
- const orphansPurged = toFiniteNumber(result.orphansPurged);
777
338
  const lintSummary = result.lintSummary;
778
- const lintFixed = lintSummary ? toFiniteNumber(lintSummary.fixed) : 0;
779
- const lintFlagged = lintSummary ? toFiniteNumber(lintSummary.flagged) : 0;
780
339
  return {
781
340
  id: row.id,
782
341
  startedAt: row.started_at,
@@ -793,37 +352,21 @@ export function projectImproveRunSummary(row, wallTimeMs, taskId) {
793
352
  taskId,
794
353
  actions: perRow.actions,
795
354
  memorySummary: perRow.memorySummary,
796
- memoryCleanup: perRow.memoryCleanup,
797
355
  consolidation: perRow.consolidation,
798
356
  memoryInference: perRow.memoryInference,
799
357
  graphExtraction: perRow.graphExtraction,
800
- reflectsWithErrorContext: perRow.reflectsWithErrorContext,
801
- orphansPurged,
802
- lintFixed,
803
- lintFlagged,
804
- };
805
- }
806
- function emptyPhaseStats() {
807
- return {
808
- consolidation: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
809
- memoryInference: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
810
- graphExtraction: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
358
+ orphansPurged: toFiniteNumber(result.orphansPurged),
359
+ lintFixed: lintSummary ? toFiniteNumber(lintSummary.fixed) : 0,
360
+ lintFlagged: lintSummary ? toFiniteNumber(lintSummary.flagged) : 0,
811
361
  };
812
362
  }
813
- export function computeWallTimeStats(durationsMs, byPhase) {
814
- const phase = byPhase ?? emptyPhaseStats();
363
+ /** Nearest-rank median and p95 of the window's run wall times. */
364
+ export function computeWallTimeStats(durationsMs) {
815
365
  if (durationsMs.length === 0)
816
- return { count: 0, medianMs: 0, p95Ms: 0, minMs: 0, maxMs: 0, byPhase: phase };
366
+ return { medianMs: 0, p95Ms: 0 };
817
367
  const sorted = [...durationsMs].sort((a, b) => a - b);
818
368
  const pick = (q) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))] ?? 0;
819
- return {
820
- count: sorted.length,
821
- medianMs: pick(0.5),
822
- p95Ms: pick(0.95),
823
- minMs: sorted[0] ?? 0,
824
- maxMs: sorted[sorted.length - 1] ?? 0,
825
- byPhase: phase,
826
- };
369
+ return { medianMs: pick(0.5), p95Ms: pick(0.95) };
827
370
  }
828
371
  export function buildImproveSkipSummary(events) {
829
372
  // Two kinds of skip events:
@@ -833,9 +376,9 @@ export function buildImproveSkipSummary(events) {
833
376
  // (`no_new_signal`, `strategy_filtered_all_passes`). Each run re-counts the
834
377
  // same stable set, so summing across the window re-counts it N times (the
835
378
  // 2.7M / 3M inflation). For these we keep the MOST RECENT run's count — the
836
- // current snapshot — matching how memorySummary/strategyFilteredRefs are
837
- // handled. Events arrive in chronological (offset) order, so the last
838
- // count-bearing event per reason is the latest run's value.
379
+ // current snapshot — matching how memorySummary is handled. Events arrive
380
+ // in chronological (offset) order, so the last count-bearing event per
381
+ // reason is the latest run's value.
839
382
  const summed = {};
840
383
  const latestSnapshot = {};
841
384
  for (const event of events) {
@@ -855,3 +398,21 @@ export function buildImproveSkipSummary(events) {
855
398
  const skipped = Object.values(skipReasons).reduce((a, b) => a + b, 0);
856
399
  return { skipped, skipReasons };
857
400
  }
401
+ /**
402
+ * Proposals accepted in `[since, until)` (by `updated_at`) and the distinct
403
+ * refs among them — N accepted rewrites of one asset touch one ref. A single
404
+ * SQL aggregate; proposal bodies are never loaded. Fails open to zeros when
405
+ * the table is absent.
406
+ */
407
+ export function computeWindowProposalCoverage(db, since, until) {
408
+ try {
409
+ const row = db
410
+ .prepare("SELECT COUNT(*) AS accepted, COUNT(DISTINCT ref) AS refs FROM proposals " +
411
+ "WHERE status = 'accepted' AND updated_at >= ? AND (? IS NULL OR updated_at < ?)")
412
+ .get(since, until ?? null, until ?? null);
413
+ return { acceptedProposals: row?.accepted ?? 0, distinctRefs: row?.refs ?? 0 };
414
+ }
415
+ catch {
416
+ return { acceptedProposals: 0, distinctRefs: 0 };
417
+ }
418
+ }