akm-cli 0.9.0-beta.9 → 0.9.0-rc.1

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 (381) hide show
  1. package/CHANGELOG.md +715 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  39. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  40. package/dist/assets/templates/html/health.html +281 -111
  41. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  42. package/dist/assets/wiki/schema-template.md +4 -4
  43. package/dist/cli/clack.js +56 -0
  44. package/dist/cli/config-migrate.js +7 -1
  45. package/dist/cli/confirm.js +1 -1
  46. package/dist/cli/parse-args.js +46 -1
  47. package/dist/cli/shared.js +28 -0
  48. package/dist/cli.js +25 -21
  49. package/dist/commands/agent/agent-dispatch.js +3 -2
  50. package/dist/commands/agent/agent-support.js +0 -7
  51. package/dist/commands/agent/contribute-cli.js +26 -7
  52. package/dist/commands/config-cli.js +26 -13
  53. package/dist/commands/env/child-env.js +47 -0
  54. package/dist/commands/env/env-binding.js +95 -0
  55. package/dist/commands/env/env-cli.js +228 -292
  56. package/dist/commands/env/env.js +14 -67
  57. package/dist/commands/env/secret-cli.js +140 -138
  58. package/dist/commands/feedback-cli.js +156 -155
  59. package/dist/commands/graph/graph-cli.js +5 -13
  60. package/dist/commands/graph/graph.js +3 -3
  61. package/dist/commands/health/advisories.js +151 -0
  62. package/dist/commands/health/checks.js +103 -16
  63. package/dist/commands/health/html-report.js +447 -81
  64. package/dist/commands/health/improve-metrics.js +771 -0
  65. package/dist/commands/health/llm-usage.js +65 -0
  66. package/dist/commands/health/md-report.js +103 -0
  67. package/dist/commands/health/metrics.js +278 -0
  68. package/dist/commands/health/stash-exposure.js +46 -0
  69. package/dist/commands/health/surfaces.js +216 -0
  70. package/dist/commands/health/task-runs.js +135 -0
  71. package/dist/commands/health/types.js +26 -0
  72. package/dist/commands/health/windows.js +195 -0
  73. package/dist/commands/health.js +91 -1091
  74. package/dist/commands/improve/anti-collapse.js +170 -0
  75. package/dist/commands/improve/calibration.js +161 -0
  76. package/dist/commands/improve/collapse-detector.js +421 -0
  77. package/dist/commands/improve/consolidate/chunking.js +141 -0
  78. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  79. package/dist/commands/improve/consolidate/merge.js +145 -0
  80. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  81. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  82. package/dist/commands/improve/consolidate.js +1295 -1277
  83. package/dist/commands/improve/dedup.js +482 -0
  84. package/dist/commands/improve/distill/content-repair.js +202 -0
  85. package/dist/commands/improve/distill/promote-memory.js +229 -0
  86. package/dist/commands/improve/distill/quality-gate.js +236 -0
  87. package/dist/commands/improve/distill-guards.js +127 -0
  88. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  89. package/dist/commands/improve/distill.js +228 -605
  90. package/dist/commands/improve/eligibility.js +434 -0
  91. package/dist/commands/improve/encoding-salience.js +205 -0
  92. package/dist/commands/improve/extract-cli.js +179 -59
  93. package/dist/commands/improve/extract-prompt.js +54 -3
  94. package/dist/commands/improve/extract-watch.js +140 -0
  95. package/dist/commands/improve/extract.js +409 -43
  96. package/dist/commands/improve/feedback-valence.js +54 -0
  97. package/dist/commands/improve/hot-probation.js +45 -0
  98. package/dist/commands/improve/improve-auto-accept.js +157 -10
  99. package/dist/commands/improve/improve-cli.js +115 -73
  100. package/dist/commands/improve/improve-profiles.js +28 -8
  101. package/dist/commands/improve/improve-result-file.js +15 -25
  102. package/dist/commands/improve/improve-session.js +58 -0
  103. package/dist/commands/improve/improve.js +485 -2764
  104. package/dist/commands/improve/locks.js +154 -0
  105. package/dist/commands/improve/loop-stages.js +1100 -0
  106. package/dist/commands/improve/memory/memory-belief.js +14 -15
  107. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  108. package/dist/commands/improve/memory/memory-improve.js +27 -27
  109. package/dist/commands/improve/outcome-loop.js +270 -0
  110. package/dist/commands/improve/preparation.js +2002 -0
  111. package/dist/commands/improve/proactive-maintenance.js +37 -35
  112. package/dist/commands/improve/procedural.js +398 -0
  113. package/dist/commands/improve/recombine.js +818 -0
  114. package/dist/commands/improve/reflect-noise.js +0 -0
  115. package/dist/commands/improve/reflect.js +206 -45
  116. package/dist/commands/improve/salience.js +455 -0
  117. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  118. package/dist/commands/improve/shared.js +51 -0
  119. package/dist/commands/improve/triage.js +93 -0
  120. package/dist/commands/lint/agent-linter.js +19 -24
  121. package/dist/commands/lint/base-linter.js +173 -60
  122. package/dist/commands/lint/command-linter.js +19 -24
  123. package/dist/commands/lint/env-key-rules.js +38 -1
  124. package/dist/commands/lint/fact-linter.js +39 -0
  125. package/dist/commands/lint/index.js +31 -13
  126. package/dist/commands/lint/memory-linter.js +1 -1
  127. package/dist/commands/lint/registry.js +7 -2
  128. package/dist/commands/lint/task-linter.js +3 -3
  129. package/dist/commands/lint/workflow-linter.js +26 -1
  130. package/dist/commands/observability-cli.js +4 -4
  131. package/dist/commands/proposal/drain-policies.js +13 -4
  132. package/dist/commands/proposal/drain.js +45 -51
  133. package/dist/commands/proposal/legacy-import.js +115 -0
  134. package/dist/commands/proposal/proposal-cli.js +24 -34
  135. package/dist/commands/proposal/proposal.js +2 -1
  136. package/dist/commands/proposal/propose.js +8 -3
  137. package/dist/commands/proposal/repository.js +829 -0
  138. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  139. package/dist/commands/proposal/validators/proposals.js +93 -895
  140. package/dist/commands/read/curate.js +410 -111
  141. package/dist/commands/read/knowledge.js +10 -3
  142. package/dist/commands/read/remember-cli.js +133 -138
  143. package/dist/commands/read/search-cli.js +15 -8
  144. package/dist/commands/read/search.js +22 -11
  145. package/dist/commands/read/show.js +106 -14
  146. package/dist/commands/registry-cli.js +76 -87
  147. package/dist/commands/remember.js +11 -12
  148. package/dist/commands/sources/add-cli.js +91 -95
  149. package/dist/commands/sources/history.js +1 -1
  150. package/dist/commands/sources/init.js +66 -18
  151. package/dist/commands/sources/installed-stashes.js +11 -3
  152. package/dist/commands/sources/migration-help.js +7 -4
  153. package/dist/commands/sources/schema-repair.js +44 -46
  154. package/dist/commands/sources/self-update.js +2 -2
  155. package/dist/commands/sources/source-add.js +7 -3
  156. package/dist/commands/sources/sources-cli.js +3 -3
  157. package/dist/commands/sources/stash-cli.js +19 -39
  158. package/dist/commands/sources/stash-skeleton.js +57 -8
  159. package/dist/commands/tasks/default-tasks.js +15 -2
  160. package/dist/commands/tasks/tasks-cli.js +20 -29
  161. package/dist/commands/tasks/tasks.js +39 -11
  162. package/dist/commands/wiki-cli.js +23 -38
  163. package/dist/commands/workflow-cli.js +291 -13
  164. package/dist/core/asset/asset-registry.js +3 -1
  165. package/dist/core/asset/asset-spec.js +79 -5
  166. package/dist/core/asset/frontmatter.js +188 -167
  167. package/dist/core/asset/markdown.js +8 -0
  168. package/dist/core/authoring-rules.js +92 -0
  169. package/dist/core/common.js +4 -23
  170. package/dist/core/concurrent.js +10 -1
  171. package/dist/core/config/config-io.js +10 -1
  172. package/dist/core/config/config-migration.js +18 -40
  173. package/dist/core/config/config-schema.js +403 -62
  174. package/dist/core/config/config-types.js +3 -3
  175. package/dist/core/config/config.js +67 -22
  176. package/dist/core/deep-merge.js +38 -0
  177. package/dist/core/errors.js +1 -0
  178. package/dist/core/eval/rank-metrics.js +113 -0
  179. package/dist/core/events.js +4 -7
  180. package/dist/core/improve-types.js +47 -8
  181. package/dist/core/json-schema.js +142 -0
  182. package/dist/core/logs-db.js +14 -75
  183. package/dist/core/parse.js +36 -16
  184. package/dist/core/paths.js +18 -18
  185. package/dist/core/standards/resolve-standards-context.js +87 -0
  186. package/dist/core/standards/resolve-stash-standards.js +99 -0
  187. package/dist/core/standards/resolve-type-conventions.js +66 -0
  188. package/dist/core/state/migrations.js +770 -0
  189. package/dist/core/state-db.js +132 -1126
  190. package/dist/core/structured.js +69 -0
  191. package/dist/core/time.js +53 -0
  192. package/dist/core/warn.js +21 -0
  193. package/dist/core/write-source.js +37 -0
  194. package/dist/indexer/db/db.js +261 -770
  195. package/dist/indexer/db/entry-mapper.js +41 -0
  196. package/dist/indexer/db/graph-db.js +129 -86
  197. package/dist/indexer/db/llm-cache.js +2 -2
  198. package/dist/indexer/db/schema.js +516 -0
  199. package/dist/indexer/ensure-index.js +36 -92
  200. package/dist/indexer/feedback/utility-policy.js +75 -0
  201. package/dist/indexer/graph/graph-boost.js +51 -41
  202. package/dist/indexer/graph/graph-extraction.js +207 -4
  203. package/dist/indexer/index-writer-lock.js +18 -11
  204. package/dist/indexer/index-written-assets.js +105 -0
  205. package/dist/indexer/indexer.js +182 -204
  206. package/dist/indexer/passes/dir-staleness.js +114 -0
  207. package/dist/indexer/passes/memory-inference.js +13 -5
  208. package/dist/indexer/passes/metadata.js +20 -0
  209. package/dist/indexer/read-preflight.js +23 -0
  210. package/dist/indexer/search/db-search.js +89 -13
  211. package/dist/indexer/search/fts-query.js +51 -0
  212. package/dist/indexer/search/ranking-contributors.js +95 -9
  213. package/dist/indexer/search/ranking.js +79 -3
  214. package/dist/indexer/search/search-fields.js +6 -0
  215. package/dist/indexer/search/search-source.js +32 -21
  216. package/dist/indexer/search/semantic-status.js +4 -0
  217. package/dist/indexer/walk/matchers.js +48 -0
  218. package/dist/indexer/walk/walker.js +21 -13
  219. package/dist/integrations/agent/builders.js +41 -13
  220. package/dist/integrations/agent/config.js +20 -59
  221. package/dist/integrations/agent/detect.js +9 -0
  222. package/dist/integrations/agent/index.js +3 -19
  223. package/dist/integrations/agent/model-aliases.js +16 -2
  224. package/dist/integrations/agent/profiles.js +79 -6
  225. package/dist/integrations/agent/prompts.js +75 -9
  226. package/dist/integrations/agent/runner-dispatch.js +83 -0
  227. package/dist/integrations/agent/runner.js +13 -9
  228. package/dist/integrations/agent/spawn.js +206 -81
  229. package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
  230. package/dist/integrations/harnesses/aider/index.js +58 -0
  231. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  232. package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
  233. package/dist/integrations/harnesses/amazonq/index.js +59 -0
  234. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  235. package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
  236. package/dist/integrations/harnesses/claude/index.js +27 -23
  237. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  238. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  239. package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
  240. package/dist/integrations/harnesses/codex/index.js +63 -0
  241. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  242. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  243. package/dist/integrations/harnesses/copilot/index.js +60 -0
  244. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  245. package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
  246. package/dist/integrations/harnesses/gemini/index.js +60 -0
  247. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  248. package/dist/integrations/harnesses/index.js +28 -7
  249. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  250. package/dist/integrations/harnesses/opencode/index.js +17 -16
  251. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  252. package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
  253. package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
  254. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
  255. package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
  256. package/dist/integrations/harnesses/openhands/index.js +58 -0
  257. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  258. package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
  259. package/dist/integrations/harnesses/pi/index.js +58 -0
  260. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  261. package/dist/integrations/harnesses/types.js +8 -0
  262. package/dist/integrations/session-logs/index.js +40 -11
  263. package/dist/llm/call-ai.js +2 -2
  264. package/dist/llm/client.js +34 -11
  265. package/dist/llm/embedder.js +67 -4
  266. package/dist/llm/embedders/cache.js +3 -1
  267. package/dist/llm/embedders/deterministic.js +66 -0
  268. package/dist/llm/embedders/local.js +73 -3
  269. package/dist/llm/feature-gate.js +16 -15
  270. package/dist/llm/graph-extract.js +67 -44
  271. package/dist/llm/memory-infer-impl.js +138 -0
  272. package/dist/llm/memory-infer.js +1 -127
  273. package/dist/llm/metadata-enhance.js +44 -31
  274. package/dist/llm/structured-call.js +49 -0
  275. package/dist/migrate-storage-node.mjs +8 -0
  276. package/dist/output/context.js +5 -5
  277. package/dist/output/renderers.js +87 -15
  278. package/dist/output/shapes/curate.js +14 -2
  279. package/dist/output/shapes/helpers.js +0 -3
  280. package/dist/output/shapes/passthrough.js +6 -1
  281. package/dist/output/text/helpers.js +241 -2
  282. package/dist/output/text/workflow.js +4 -1
  283. package/dist/registry/providers/skills-sh.js +21 -147
  284. package/dist/registry/providers/static-index.js +15 -157
  285. package/dist/registry/resolve.js +27 -9
  286. package/dist/runtime.js +25 -1
  287. package/dist/schemas/akm-config.json +14225 -0
  288. package/dist/schemas/akm-workflow.json +328 -0
  289. package/dist/scripts/migrate-storage.js +2743 -8390
  290. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
  291. package/dist/setup/detect.js +9 -0
  292. package/dist/setup/legacy-config.js +106 -0
  293. package/dist/setup/prompt.js +57 -0
  294. package/dist/setup/providers.js +14 -0
  295. package/dist/setup/registry-stash-loader.js +12 -0
  296. package/dist/setup/semantic-assets.js +124 -0
  297. package/dist/setup/setup.js +52 -1614
  298. package/dist/setup/steps/connection.js +734 -0
  299. package/dist/setup/steps/output.js +31 -0
  300. package/dist/setup/steps/platforms.js +124 -0
  301. package/dist/setup/steps/semantic.js +27 -0
  302. package/dist/setup/steps/sources.js +222 -0
  303. package/dist/setup/steps/stashdir.js +42 -0
  304. package/dist/setup/steps/tasks.js +152 -0
  305. package/dist/sources/include.js +6 -2
  306. package/dist/sources/providers/filesystem.js +0 -1
  307. package/dist/sources/providers/git-install.js +210 -0
  308. package/dist/sources/providers/git-provider.js +234 -0
  309. package/dist/sources/providers/git-stash.js +248 -0
  310. package/dist/sources/providers/git.js +10 -661
  311. package/dist/sources/providers/npm.js +2 -6
  312. package/dist/sources/providers/provider-utils.js +13 -7
  313. package/dist/sources/providers/sync-from-ref.js +9 -1
  314. package/dist/sources/providers/website.js +9 -5
  315. package/dist/sources/website-ingest.js +187 -29
  316. package/dist/sources/wiki-fetchers/registry.js +53 -0
  317. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  318. package/dist/storage/database.js +45 -10
  319. package/dist/storage/managed-db.js +82 -0
  320. package/dist/storage/repositories/canaries-repository.js +107 -0
  321. package/dist/storage/repositories/consolidation-repository.js +38 -0
  322. package/dist/storage/repositories/embeddings-repository.js +72 -0
  323. package/dist/storage/repositories/events-repository.js +187 -0
  324. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  325. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  326. package/dist/storage/repositories/index-db.js +14 -8
  327. package/dist/storage/repositories/proposals-repository.js +220 -0
  328. package/dist/storage/repositories/recombine-repository.js +213 -0
  329. package/dist/storage/repositories/registry-cache.js +93 -0
  330. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  331. package/dist/storage/repositories/task-history-repository.js +93 -0
  332. package/dist/storage/repositories/workflow-runs-repository.js +189 -1
  333. package/dist/storage/sqlite-pragmas.js +146 -0
  334. package/dist/tasks/backends/cron.js +1 -1
  335. package/dist/tasks/backends/index.js +9 -0
  336. package/dist/tasks/backends/launchd.js +1 -1
  337. package/dist/tasks/backends/schtasks.js +1 -1
  338. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  339. package/dist/tasks/runner.js +15 -13
  340. package/dist/text-import-hook.mjs +1 -1
  341. package/dist/wiki/wiki.js +52 -11
  342. package/dist/workflows/authoring/authoring.js +123 -10
  343. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  344. package/dist/workflows/cli.js +5 -0
  345. package/dist/workflows/db.js +138 -4
  346. package/dist/workflows/exec/brief.js +484 -0
  347. package/dist/workflows/exec/native-executor.js +975 -0
  348. package/dist/workflows/exec/param-secrets.js +115 -0
  349. package/dist/workflows/exec/report.js +1295 -0
  350. package/dist/workflows/exec/run-workflow.js +596 -0
  351. package/dist/workflows/exec/scheduler.js +100 -0
  352. package/dist/workflows/exec/step-work.js +1156 -0
  353. package/dist/workflows/exec/unit-writer.js +23 -0
  354. package/dist/workflows/exec/watch.js +116 -0
  355. package/dist/workflows/exec/worktree.js +171 -0
  356. package/dist/workflows/ir/compile.js +388 -0
  357. package/dist/workflows/ir/params.js +54 -0
  358. package/dist/workflows/ir/plan-hash.js +33 -0
  359. package/dist/workflows/ir/schema.js +4 -0
  360. package/dist/workflows/parser.js +3 -1
  361. package/dist/workflows/program/expressions.js +369 -0
  362. package/dist/workflows/program/parser.js +760 -0
  363. package/dist/workflows/program/project.js +105 -0
  364. package/dist/workflows/program/schema.js +54 -0
  365. package/dist/workflows/renderer.js +82 -5
  366. package/dist/workflows/runtime/agent-identity.js +59 -14
  367. package/dist/workflows/runtime/runs.js +248 -153
  368. package/dist/workflows/runtime/unit-checkin.js +45 -0
  369. package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
  370. package/dist/workflows/validate-summary.js +26 -10
  371. package/dist/workflows/validator.js +1 -1
  372. package/docs/README.md +69 -18
  373. package/docs/data-and-telemetry.md +7 -5
  374. package/docs/migration/release-notes/0.7.0.md +1 -1
  375. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  376. package/docs/migration/release-notes/0.9.0.md +39 -0
  377. package/package.json +10 -10
  378. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  379. package/dist/commands/db-cli.js +0 -23
  380. package/dist/indexer/db/db-backup.js +0 -376
  381. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -0,0 +1,216 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * The remaining `surfaces` advisory group for `akm health` (meta-review 08).
6
+ * `stash-git-exposure` (08-F1) shipped first in ./stash-exposure.ts; this
7
+ * module adds the other four read-only checks the adjudication approved:
8
+ *
9
+ * - `secret-file-perms` — env/secret/backup files not 0600, dirs not 0700 (F4)
10
+ * - `binary-config-skew` — config.json written by a NEWER akm than this binary (F3)
11
+ * - `orphan-stores` — legacy config-backups dirs + 0-byte stash state.db decoy (F4/F7)
12
+ * - `egress-endpoints` — the remote-destination list, for eyeball diff (surfaces 3/9)
13
+ *
14
+ * Every collector is a pure projection over injected paths/config (no
15
+ * process.env reads) and is silent when there is nothing to report, matching
16
+ * the stash-exposure pattern. `egress-endpoints` is the one informational
17
+ * (pass-status) entry: it emits whenever any remote endpoint is configured.
18
+ */
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+ import { CURRENT_CONFIG_VERSION, compareConfigVersion } from "../../core/config/config-migration.js";
22
+ const GROUP_OTHER_BITS = 0o077;
23
+ const OFFENDER_EVIDENCE_CAP = 50;
24
+ function modeOctal(mode) {
25
+ return (mode & 0o777).toString(8).padStart(3, "0");
26
+ }
27
+ /**
28
+ * `secret-file-perms` (08-F4): flag env/secret/backup files that are not 0600
29
+ * and their directories when not 0700. Scans `<stash>/env`, `<stash>/secrets`
30
+ * and `<cache>/config-backups`; anything readable by group/other is an
31
+ * offender. Silent when every path is tight (or none of the dirs exist).
32
+ */
33
+ export function collectSecretPermsAdvisory(input, platform = process.platform) {
34
+ if (platform === "win32")
35
+ return undefined;
36
+ const roots = [
37
+ path.join(input.stashDir, "env"),
38
+ path.join(input.stashDir, "secrets"),
39
+ path.join(input.cacheDir, "config-backups"),
40
+ ];
41
+ const offenders = [];
42
+ for (const root of roots) {
43
+ let stat;
44
+ try {
45
+ stat = fs.statSync(root);
46
+ }
47
+ catch {
48
+ continue; // absent → nothing to protect
49
+ }
50
+ if ((stat.mode & GROUP_OTHER_BITS) !== 0)
51
+ offenders.push(`${root}/ (${modeOctal(stat.mode)}, want 700)`);
52
+ let entries;
53
+ try {
54
+ entries = fs.readdirSync(root, { recursive: true });
55
+ }
56
+ catch {
57
+ continue;
58
+ }
59
+ for (const entry of entries) {
60
+ const abs = path.join(root, entry);
61
+ let entryStat;
62
+ try {
63
+ entryStat = fs.statSync(abs);
64
+ }
65
+ catch {
66
+ continue;
67
+ }
68
+ if ((entryStat.mode & GROUP_OTHER_BITS) === 0)
69
+ continue;
70
+ offenders.push(entryStat.isDirectory()
71
+ ? `${abs}/ (${modeOctal(entryStat.mode)}, want 700)`
72
+ : `${abs} (${modeOctal(entryStat.mode)}, want 600)`);
73
+ }
74
+ }
75
+ if (offenders.length === 0)
76
+ return undefined;
77
+ const preview = offenders.slice(0, 5).join("; ") + (offenders.length > 5 ? `; +${offenders.length - 5} more` : "");
78
+ return {
79
+ name: "secret-file-perms",
80
+ kind: "deterministic",
81
+ status: "warn",
82
+ confidence: "high",
83
+ message: `${offenders.length} env/secret/backup path(s) are readable by group/other: ${preview}. ` +
84
+ "Tighten with chmod 600 (files) / chmod 700 (dirs) — these hold tokens, keys, and config snapshots.",
85
+ evidence: { offenders: offenders.slice(0, OFFENDER_EVIDENCE_CAP) },
86
+ };
87
+ }
88
+ /**
89
+ * `binary-config-skew` (08-F3): warn when config.json carries a configVersion
90
+ * NEWER than (or unorderable against) this binary's CURRENT_CONFIG_VERSION —
91
+ * i.e. a newer/foreign akm wrote the shared config and this install is stale.
92
+ * That is exactly the state where auto-migration is skipped (downgrade
93
+ * protection) and the proven multi-install incident class begins. Silent for
94
+ * same/older versions (auto-migration handles those) and unreadable configs
95
+ * (config loading surfaces its own errors).
96
+ */
97
+ export function collectConfigSkewAdvisory(configPath) {
98
+ let raw;
99
+ try {
100
+ raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ const onDisk = raw.configVersion;
106
+ const order = compareConfigVersion(onDisk, CURRENT_CONFIG_VERSION);
107
+ const skewed = order === 1 || (onDisk !== undefined && order === undefined);
108
+ if (!skewed)
109
+ return undefined;
110
+ return {
111
+ name: "binary-config-skew",
112
+ kind: "deterministic",
113
+ status: "warn",
114
+ confidence: "high",
115
+ message: `config.json has configVersion ${JSON.stringify(onDisk)} but this binary knows ${CURRENT_CONFIG_VERSION} — ` +
116
+ "a newer akm wrote the shared config, so this install is stale and auto-migration is skipped " +
117
+ "(downgrade protection). Upgrade this install; do not keep a stale binary against the shared config/DBs.",
118
+ evidence: { onDiskConfigVersion: onDisk, binaryConfigVersion: CURRENT_CONFIG_VERSION },
119
+ };
120
+ }
121
+ /**
122
+ * `orphan-stores` (08-F4/F7): stores that look load-bearing but are dead —
123
+ * legacy `config-backups/` dirs in $DATA/$CONFIG (only `$CACHE/config-backups`
124
+ * is written and pruned today) and a 0-byte `<stash>/.akm/state.db` decoy that
125
+ * misleads debugging (the live state.db lives in the data dir). Read-only:
126
+ * this advisory NAMES them; removal stays an owner-approved per-path action.
127
+ */
128
+ export function collectOrphanStoresAdvisory(input) {
129
+ const orphans = [];
130
+ for (const [label, dir] of [
131
+ ["legacy data-dir backup location", path.join(input.dataDir, "config-backups")],
132
+ ["legacy config-dir backup location", path.join(input.configDir, "config-backups")],
133
+ ]) {
134
+ try {
135
+ if (fs.statSync(dir).isDirectory())
136
+ orphans.push(`${dir}/ (${label}; live backups are in $CACHE/config-backups)`);
137
+ }
138
+ catch {
139
+ // absent — fine
140
+ }
141
+ }
142
+ const decoy = path.join(input.stashDir, ".akm", "state.db");
143
+ try {
144
+ const stat = fs.statSync(decoy);
145
+ if (stat.isFile() && stat.size === 0) {
146
+ orphans.push(`${decoy} (0-byte decoy; the live state.db is in the data dir)`);
147
+ }
148
+ }
149
+ catch {
150
+ // absent — fine
151
+ }
152
+ if (orphans.length === 0)
153
+ return undefined;
154
+ return {
155
+ name: "orphan-stores",
156
+ kind: "deterministic",
157
+ status: "warn",
158
+ confidence: "high",
159
+ message: `${orphans.length} orphan store(s) found: ${orphans.join("; ")}. ` +
160
+ "These are dead locations that mislead debugging/recovery — review and remove them (owner-approved, per-path).",
161
+ evidence: { orphans },
162
+ };
163
+ }
164
+ /**
165
+ * `egress-endpoints` (08 surfaces 3/9): the full list of remote destinations
166
+ * akm can talk to under the effective config — registries, remote sources,
167
+ * LLM endpoints, embedding endpoint — as one pass-status informational entry
168
+ * for eyeball diff against expectations. Silent only when nothing remote is
169
+ * configured at all.
170
+ */
171
+ export function collectEgressAdvisory(config) {
172
+ if (!config)
173
+ return undefined;
174
+ const endpoints = [];
175
+ for (const reg of config.registries ?? []) {
176
+ if (reg.enabled === false || !reg.url)
177
+ continue;
178
+ endpoints.push(`registry ${reg.name ?? "(unnamed)"}: ${reg.url}`);
179
+ }
180
+ for (const source of config.sources ?? []) {
181
+ if (source.enabled === false || !source.url)
182
+ continue;
183
+ endpoints.push(`source ${source.name ?? "(unnamed)"} (${source.type ?? "?"}): ${source.url}`);
184
+ }
185
+ for (const [name, profile] of Object.entries(config.profiles?.llm ?? {})) {
186
+ if (!profile?.endpoint)
187
+ continue;
188
+ endpoints.push(`llm ${name}: ${profile.endpoint}`);
189
+ }
190
+ if (config.embedding?.endpoint)
191
+ endpoints.push(`embedding: ${config.embedding.endpoint}`);
192
+ if (endpoints.length === 0)
193
+ return undefined;
194
+ return {
195
+ name: "egress-endpoints",
196
+ kind: "deterministic",
197
+ status: "pass",
198
+ confidence: "high",
199
+ message: `${endpoints.length} remote endpoint(s) in the effective config (registries/sources/LLM/embedding) — ` +
200
+ "review the evidence list for unexpected destinations.",
201
+ evidence: { endpoints },
202
+ };
203
+ }
204
+ /**
205
+ * Aggregate the four collectors into the advisories array shape `akmHealth`
206
+ * consumes. Order is fixed: perms → skew → orphans → egress.
207
+ */
208
+ export function collectSurfacesAdvisories(input) {
209
+ const results = [
210
+ collectSecretPermsAdvisory({ stashDir: input.stashDir, cacheDir: input.cacheDir }, input.platform ?? process.platform),
211
+ collectConfigSkewAdvisory(input.configPath),
212
+ collectOrphanStoresAdvisory({ dataDir: input.dataDir, configDir: input.configDir, stashDir: input.stashDir }),
213
+ collectEgressAdvisory(input.config),
214
+ ];
215
+ return results.filter((r) => r !== undefined);
216
+ }
@@ -0,0 +1,135 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { queryImproveRuns } from "../../storage/repositories/improve-runs-repository.js";
5
+ import { queryCompletedTaskIntervals, queryTaskHistory } from "../../storage/repositories/task-history-repository.js";
6
+ import { projectImproveRunSummary } from "./improve-metrics.js";
7
+ /**
8
+ * Load task_history intervals for `task_id='akm-improve'` in the window.
9
+ * Returned sorted by startMs ascending so containment lookups can use a
10
+ * linear scan (typical N is ~24/day; not worth a tree).
11
+ *
12
+ * The window filter is widened by 5 minutes on each side because the cron
13
+ * task wraps `akm improve` — the task `started_at` fires at e.g. :07:01
14
+ * while `recordImproveRun` writes the matching `improve_runs.started_at`
15
+ * later (after config load, planning, etc.), so the improve_runs row can
16
+ * be inside the window even when its enclosing task_history row started
17
+ * just before the window opened.
18
+ */
19
+ function loadTaskIntervals(db, since, until) {
20
+ const sinceMs = new Date(since).getTime();
21
+ const untilMs = until ? new Date(until).getTime() : Number.POSITIVE_INFINITY;
22
+ const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
23
+ const widenedUntil = Number.isFinite(untilMs) ? new Date(untilMs + 5 * 60 * 1000).toISOString() : undefined;
24
+ const rows = queryCompletedTaskIntervals(db, widenedSince, widenedUntil);
25
+ const intervals = [];
26
+ for (const row of rows) {
27
+ const startMs = new Date(row.started_at).getTime();
28
+ const endMs = new Date(row.completed_at).getTime();
29
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs)
30
+ continue;
31
+ intervals.push({ startMs, endMs, durationMs: endMs - startMs });
32
+ }
33
+ return intervals;
34
+ }
35
+ /**
36
+ * Find the task_history interval that contains the given timestamp. The
37
+ * task wraps `akm improve`, so `improve_runs.started_at` (when
38
+ * `recordImproveRun` writes) always falls inside the enclosing task's
39
+ * [started_at, completed_at]. Returns undefined when no interval
40
+ * contains the timestamp (which happens for manually-invoked improve
41
+ * runs not driven by the `akm-improve` task).
42
+ *
43
+ * Linear scan because N is small. We tolerate a 1s slop on the upper
44
+ * bound to handle clock skew between the wrapper's `completed_at` write
45
+ * and recordImproveRun's `started_at` write.
46
+ */
47
+ function findContainingTaskInterval(timestampMs, intervals) {
48
+ const SLOP_MS = 1000;
49
+ for (const interval of intervals) {
50
+ if (timestampMs >= interval.startMs && timestampMs <= interval.endMs + SLOP_MS) {
51
+ return interval;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ /**
57
+ * Load `task_history` rows whose `task_id` begins `akm-improve` (the scheduled
58
+ * improve tasks: `akm-improve-frequent`, `akm-improve-proactive-weekly`, …) in
59
+ * the window, widened ±5 min so a task that fired just before the window opened
60
+ * still matches a run inside it. Used to attribute each improve run to the task
61
+ * that launched it.
62
+ */
63
+ function loadImproveTaskRuns(db, since, until) {
64
+ const sinceMs = new Date(since).getTime();
65
+ const untilMs = until ? new Date(until).getTime() : undefined;
66
+ const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
67
+ const widenedUntil = untilMs !== undefined ? new Date(untilMs + 5 * 60 * 1000).toISOString() : undefined;
68
+ const runs = [];
69
+ for (const row of queryTaskHistory(db, { since: widenedSince, until: widenedUntil })) {
70
+ if (!row.task_id.startsWith("akm-improve"))
71
+ continue;
72
+ const startMs = new Date(row.started_at).getTime();
73
+ if (!Number.isFinite(startMs))
74
+ continue;
75
+ const endIso = row.completed_at ?? row.failed_at;
76
+ const endMs = endIso ? new Date(endIso).getTime() : Number.NaN;
77
+ runs.push({ taskId: row.task_id, startMs, endMs });
78
+ }
79
+ return runs;
80
+ }
81
+ /**
82
+ * Attribute an improve run to the scheduled task that launched it by matching
83
+ * start times within ±5 min, scored by start delta (plus end delta when both
84
+ * ends are known). Port of the health-report skill's `match_task_id`. Returns
85
+ * `"manual"` when no scheduled improve task matches.
86
+ */
87
+ export function matchImproveTaskId(startedAt, completedAt, taskRuns) {
88
+ const startMs = new Date(startedAt).getTime();
89
+ if (!Number.isFinite(startMs))
90
+ return "manual";
91
+ const endMs = completedAt ? new Date(completedAt).getTime() : Number.NaN;
92
+ let best;
93
+ let bestScore = Number.POSITIVE_INFINITY;
94
+ for (const task of taskRuns) {
95
+ const startDelta = Math.abs(task.startMs - startMs);
96
+ if (startDelta > 5 * 60 * 1000)
97
+ continue;
98
+ let score = startDelta;
99
+ if (Number.isFinite(endMs) && Number.isFinite(task.endMs))
100
+ score += Math.abs(task.endMs - endMs);
101
+ if (score < bestScore) {
102
+ bestScore = score;
103
+ best = task.taskId;
104
+ }
105
+ }
106
+ return best ?? "manual";
107
+ }
108
+ export function buildPerRunSummaries(db, since, until) {
109
+ const rows = queryImproveRuns(db, since, until);
110
+ const taskIntervals = loadTaskIntervals(db, since, until);
111
+ const improveTaskRuns = loadImproveTaskRuns(db, since, until);
112
+ const summaries = [];
113
+ for (const row of rows) {
114
+ const startMs = new Date(row.started_at).getTime();
115
+ const endMs = new Date(row.completed_at).getTime();
116
+ // Prefer the improve_runs row's own (completed_at - started_at) delta:
117
+ // recordImproveRun now persists distinct start/end timestamps, so the
118
+ // row's own delta is the authoritative per-run wall time even for
119
+ // manually-invoked `akm improve` runs with no enclosing task_history.
120
+ // Only fall back to the task_history containing-interval join for legacy/
121
+ // backfill rows where started_at == completed_at (row delta is 0).
122
+ const hasRowDelta = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs;
123
+ let wallTimeMs;
124
+ if (hasRowDelta) {
125
+ wallTimeMs = endMs - startMs;
126
+ }
127
+ else {
128
+ const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
129
+ wallTimeMs = interval?.durationMs ?? 0;
130
+ }
131
+ const taskId = matchImproveTaskId(row.started_at, row.completed_at, improveTaskRuns);
132
+ summaries.push(projectImproveRunSummary(row, wallTimeMs, taskId));
133
+ }
134
+ return summaries;
135
+ }
@@ -0,0 +1,26 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Lanes ratified as ENRICHMENT-ONLY: they may propose edits to existing
6
+ * assets (metadata, relations, content refresh) but must not mint new ones.
7
+ * New-asset generation belongs to the signal-gated minting lanes
8
+ * (extract/distill/memory-inference/recombine).
9
+ */
10
+ export const ENRICHMENT_LANES = ["proactive", "high-salience", "high-retrieval", "signal-delta"];
11
+ /** Minted share of enrichment-lane accepts that triggers a WARN advisory. */
12
+ export const ENRICHMENT_MINTED_WARN_SHARE = 0.05;
13
+ /** Minted share of enrichment-lane accepts that triggers a FAIL advisory. */
14
+ export const ENRICHMENT_MINTED_FAIL_SHARE = 0.15;
15
+ /**
16
+ * Cron task failure rate at or above which the `task-fail-rate` health advisory
17
+ * warns. 0.05 (5%) is the SAME threshold the HTML report already applies as its
18
+ * fail-rate pass/warn cutoff (`failOk = taskFailRate < 0.05` in
19
+ * src/commands/health/html-report.ts) — this constant makes it the single
20
+ * source so the advisory and the rendered badge cannot drift.
21
+ */
22
+ export const TASK_FAIL_RATE_WARN = 0.05;
23
+ /** Event type recorded on each completed improve run. */
24
+ export const IMPROVE_COMPLETED_EVENT = "improve_completed";
25
+ /** An active task older than this (ms) is treated as stuck. */
26
+ export const ACTIVE_RUN_WARN_MS = 15 * 60 * 1000;
@@ -0,0 +1,195 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Window handling for `akm health`: `--window-compare` / `--windows` parsing,
6
+ * per-window metric assembly, log-backing partition, and delta computation.
7
+ */
8
+ import fs from "node:fs";
9
+ import { UsageError } from "../../core/errors.js";
10
+ import { readEvents } from "../../core/events.js";
11
+ import { buildTaskRunId, getLoggedRunIds } from "../../core/logs-db.js";
12
+ import { DURATION_UNITS, parseDuration } from "../../core/time.js";
13
+ import { queryTaskHistory } from "../../storage/repositories/task-history-repository.js";
14
+ import { buildImproveSkipSummary, computeWallTimeStats, parseTaskMetadata, roundRate, summarizeImproveCompleted, summarizeImproveRuns, } from "./improve-metrics.js";
15
+ import { readLlmUsageAggregate } from "./llm-usage.js";
16
+ import { computeDegradationMetrics, computeDenominatorFixedCoverage, readCalibration } from "./metrics.js";
17
+ import { buildPerRunSummaries } from "./task-runs.js";
18
+ import { ACTIVE_RUN_WARN_MS, IMPROVE_COMPLETED_EVENT, } from "./types.js";
19
+ /**
20
+ * Parse a `--window-compare <duration>` shorthand into two adjacent windows
21
+ * (current, prior). Duration syntax matches {@link parseHealthSince}.
22
+ */
23
+ export function resolveWindowCompare(duration, now = () => Date.now()) {
24
+ const trimmed = duration.trim();
25
+ // Canonical CLI unit grammar: `m` = minutes, `M` = months. Not lower-cased,
26
+ // so case distinguishes the two. See core/time.ts DURATION_UNITS.
27
+ const ms = parseDuration(trimmed, DURATION_UNITS);
28
+ if (ms === null) {
29
+ throw new UsageError("--window-compare must be a duration like '24h', '7d', or '30m'.", "INVALID_FLAG_VALUE");
30
+ }
31
+ if (ms <= 0) {
32
+ throw new UsageError("--window-compare must be a positive duration.", "INVALID_FLAG_VALUE");
33
+ }
34
+ const nowMs = now();
35
+ const currentSince = new Date(nowMs - ms).toISOString();
36
+ const currentUntil = new Date(nowMs).toISOString();
37
+ const priorSince = new Date(nowMs - 2 * ms).toISOString();
38
+ const priorUntil = currentSince;
39
+ return [
40
+ { name: "current", since: currentSince, until: currentUntil },
41
+ { name: "prior", since: priorSince, until: priorUntil },
42
+ ];
43
+ }
44
+ /**
45
+ * Parse a single repeatable `--windows` value of the form
46
+ * `name=...,since=...,until=...`. All keys are optional EXCEPT name and since.
47
+ */
48
+ export function parseWindowSpec(raw) {
49
+ const fields = {};
50
+ for (const part of raw.split(",")) {
51
+ const trimmed = part.trim();
52
+ if (!trimmed)
53
+ continue;
54
+ const eq = trimmed.indexOf("=");
55
+ if (eq < 0) {
56
+ throw new UsageError(`--windows entry must be a comma-separated list of key=value pairs: ${raw}`, "INVALID_FLAG_VALUE");
57
+ }
58
+ const key = trimmed.slice(0, eq).trim();
59
+ const value = trimmed.slice(eq + 1).trim();
60
+ fields[key] = value;
61
+ }
62
+ if (!fields.name) {
63
+ throw new UsageError(`--windows entry is missing required 'name': ${raw}`, "INVALID_FLAG_VALUE");
64
+ }
65
+ if (!fields.since) {
66
+ throw new UsageError(`--windows entry is missing required 'since': ${raw}`, "INVALID_FLAG_VALUE");
67
+ }
68
+ return {
69
+ name: fields.name,
70
+ since: fields.since,
71
+ ...(fields.until ? { until: fields.until } : {}),
72
+ };
73
+ }
74
+ /** Hard-coded list of "interesting" metric paths for window-compare deltas. */
75
+ export const INTERESTING_DELTA_PATHS = [
76
+ "improve.actions.reflect.failed",
77
+ "improve.actions.reflect.guardRejected",
78
+ "improve.actions.distill.llmFailed",
79
+ "improve.actions.distill.queued",
80
+ "improve.actions.distill.deferred",
81
+ "improve.consolidation.promoted",
82
+ "improve.memoryInference.written",
83
+ "improve.memoryInference.yieldRate",
84
+ "improve.memoryInference.skippedNoFacts",
85
+ "improve.memoryInference.htmlErrorCount",
86
+ "improve.graphExtraction.cacheHitRate",
87
+ "improve.graphExtraction.failures",
88
+ "improve.graphExtraction.htmlErrors",
89
+ "improve.graphExtraction.nonArrayBatchFailures",
90
+ "improve.sessionExtraction.sessionsScanned",
91
+ "improve.sessionExtraction.proposalsCreated",
92
+ "improve.autoAccept.promoted",
93
+ "improve.autoAccept.validationFailed",
94
+ "improve.wallTime.medianMs",
95
+ "improve.wallTime.p95Ms",
96
+ ];
97
+ export function readNumericPath(obj, path) {
98
+ const parts = path.split(".");
99
+ let cursor = obj;
100
+ for (const part of parts) {
101
+ if (typeof cursor !== "object" || cursor === null)
102
+ return 0;
103
+ cursor = cursor[part];
104
+ }
105
+ return typeof cursor === "number" && Number.isFinite(cursor) ? cursor : 0;
106
+ }
107
+ export function computeDeltas(first, last) {
108
+ const out = {};
109
+ for (const path of INTERESTING_DELTA_PATHS) {
110
+ const from = readNumericPath(first, path);
111
+ const to = readNumericPath(last, path);
112
+ if (from === 0 && to === 0)
113
+ continue;
114
+ let pctChange;
115
+ if (from === 0) {
116
+ pctChange = to === 0 ? 0 : "+inf";
117
+ }
118
+ else {
119
+ pctChange = Number((((to - from) / from) * 100).toFixed(2));
120
+ }
121
+ out[path] = { from, to, pctChange };
122
+ }
123
+ return out;
124
+ }
125
+ /**
126
+ * Partition task_history rows into "should have a log" (non-null log_path) and
127
+ * "log is actually backed". A run counts as backed when logs.db holds rows for
128
+ * its run_id (#579 — the DB is the primary record); rows written before logs.db
129
+ * existed fall back to the transitional on-disk file check. `logsDb` may be
130
+ * undefined when logs.db could not be opened — then only the file check runs.
131
+ */
132
+ export function partitionLogBackedRows(taskRows, logsDb) {
133
+ const withLogs = taskRows.filter((row) => row.log_path !== null);
134
+ const loggedRunIds = logsDb
135
+ ? getLoggedRunIds(logsDb, withLogs.map((row) => buildTaskRunId(row.task_id, row.started_at)))
136
+ : new Set();
137
+ const backed = withLogs.filter((row) => loggedRunIds.has(buildTaskRunId(row.task_id, row.started_at)) ||
138
+ (row.log_path !== null && fs.existsSync(row.log_path)));
139
+ return { withLogs, backed };
140
+ }
141
+ export function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now(), logsDb) {
142
+ const taskRows = queryTaskHistory(db, { since }).filter((row) => {
143
+ const startMs = new Date(row.started_at).getTime();
144
+ const untilMs = new Date(until).getTime();
145
+ return !Number.isFinite(untilMs) || startMs < untilMs;
146
+ });
147
+ const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
148
+ const failedTaskRows = taskRows.filter((row) => row.status === "failed");
149
+ const activeRows = taskRows.filter((row) => row.status === "active");
150
+ const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
151
+ const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
152
+ const promptFailures = promptRows.filter((row) => {
153
+ const detail = parseTaskMetadata(row).detail;
154
+ return typeof detail?.reason === "string" && detail.reason.length > 0;
155
+ });
156
+ const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
157
+ const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
158
+ const agentFailureRate = promptRows.length === 0 ? 0 : promptFailures.length / promptRows.length;
159
+ const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.filter((event) => new Date(event.ts ?? since).getTime() < new Date(until).getTime()).length;
160
+ const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events.filter((event) => new Date(event.ts ?? since).getTime() < new Date(until).getTime());
161
+ const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events.filter((event) => new Date(event.ts ?? since).getTime() < new Date(until).getTime());
162
+ const eventsMetrics = summarizeImproveCompleted(improveCompletedEvents);
163
+ const { metrics: improveSummary, runCount } = summarizeImproveRuns(db, since, until);
164
+ improveSummary.invoked = improveInvoked;
165
+ improveSummary.completed = eventsMetrics.completed;
166
+ const skipSummary = buildImproveSkipSummary(improveSkippedEvents);
167
+ improveSummary.skipped = skipSummary.skipped;
168
+ improveSummary.skipReasons = skipSummary.skipReasons;
169
+ // Preserve the per-phase aggregation computed by summarizeImproveRuns and
170
+ // derive top-level wall times from the same improve-runs window so counts
171
+ // and percentiles stay aligned with per-run reporting.
172
+ const perRunSummaries = buildPerRunSummaries(db, since, until);
173
+ const wallTimes = perRunSummaries.map((run) => run.wallTimeMs).filter((ms) => Number.isFinite(ms) && ms > 0);
174
+ improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
175
+ improveSummary.calibration = readCalibration(db, since, until);
176
+ // WS-5: Compute denominator-fixed coverage from the most recent run's
177
+ // memorySummary (totalAssets = eligible + derived — the fixed denominator).
178
+ const totalAssets = improveSummary.memorySummary.eligible + improveSummary.memorySummary.derived;
179
+ improveSummary.coverage = computeDenominatorFixedCoverage(db, totalAssets, improveSummary.memorySummary.eligible, since, until);
180
+ // WS-5: Compute per-run degradation metrics (corpus diversity, merge fidelity,
181
+ // generation distribution, oracle spot-check). Health VIEWS only.
182
+ const degradation = computeDegradationMetrics(db, since, until);
183
+ if (degradation) {
184
+ improveSummary.degradation = degradation;
185
+ }
186
+ const metrics = {
187
+ taskFailRate: roundRate(taskFailRate),
188
+ agentFailureRate: roundRate(agentFailureRate),
189
+ stuckActiveRuns,
190
+ logBackingRate: roundRate(logBackingRate),
191
+ probeRoundTripMs: null,
192
+ llmUsage: readLlmUsageAggregate(stateDbPath, since, until),
193
+ };
194
+ return { improve: improveSummary, metrics, runs: runCount };
195
+ }