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
@@ -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
+ }