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
@@ -2,8 +2,7 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
- import { defineCommand } from "citty";
6
- import { output, parseAllFlagValues, runWithJsonErrors } from "../cli/shared.js";
5
+ import { defineJsonCommand, output, parseAllFlagValues } from "../cli/shared.js";
7
6
  import { parseAssetRef } from "../core/asset/asset-ref.js";
8
7
  import { assembleAsset } from "../core/asset/asset-serialize.js";
9
8
  import { parseFrontmatter, parseFrontmatterBlock } from "../core/asset/frontmatter.js";
@@ -11,11 +10,9 @@ import { writeFileAtomic } from "../core/common.js";
11
10
  import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config/config.js";
12
11
  import { UsageError } from "../core/errors.js";
13
12
  import { appendEvent } from "../core/events.js";
13
+ import { getDbPath } from "../core/paths.js";
14
14
  import { warn } from "../core/warn.js";
15
15
  import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, getEntryFilePathById, openExistingDatabase, } from "../indexer/db/db.js";
16
- import { ensureIndex } from "../indexer/ensure-index.js";
17
- import { withIndexWriterLease } from "../indexer/index-writer-lock.js";
18
- import { resolveSourceEntries } from "../indexer/search/search-source.js";
19
16
  import { countFeedbackSignals, insertUsageEvent } from "../indexer/usage/usage-events.js";
20
17
  // ── Tag validation ────────────────────────────────────────────────────────────
21
18
  const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
@@ -110,7 +107,7 @@ function appendLessonStrength(type, name, feedbackRef) {
110
107
  return { strength: strengthList.length };
111
108
  }
112
109
  // ── Command definition ────────────────────────────────────────────────────────
113
- export const feedbackCommand = defineCommand({
110
+ export const feedbackCommand = defineJsonCommand({
114
111
  meta: {
115
112
  name: "feedback",
116
113
  description: "Record positive or negative feedback for any indexed stash asset.\n\n" +
@@ -154,167 +151,171 @@ export const feedbackCommand = defineCommand({
154
151
  "`lessonStrength[]` frontmatter array (dedup, idempotent). Ignored on non-lesson targets.",
155
152
  },
156
153
  },
157
- run({ args }) {
158
- return runWithJsonErrors(async () => {
159
- const ref = (args.ref ?? "").trim();
160
- if (!ref) {
161
- throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skill:deploy` and either --positive or --negative.");
154
+ async run({ args }) {
155
+ const ref = (args.ref ?? "").trim();
156
+ if (!ref) {
157
+ throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skill:deploy` and either --positive or --negative.");
158
+ }
159
+ parseAssetRef(ref);
160
+ if (args.positive && args.negative) {
161
+ throw new UsageError("Specify either --positive or --negative, not both.");
162
+ }
163
+ if (!args.positive && !args.negative) {
164
+ throw new UsageError("Specify --positive or --negative.");
165
+ }
166
+ const signal = args.positive ? "positive" : "negative";
167
+ const reason = args.reason;
168
+ // F-3 / #384: Validate --failure-mode against the curated enum.
169
+ const failureMode = args["failure-mode"]?.trim() || undefined;
170
+ if (failureMode) {
171
+ if (args.positive) {
172
+ throw new UsageError("--failure-mode is only valid for negative feedback.", "INVALID_FLAG_VALUE", "Remove --failure-mode or switch to --negative.");
162
173
  }
163
- parseAssetRef(ref);
164
- if (args.positive && args.negative) {
165
- throw new UsageError("Specify either --positive or --negative, not both.");
174
+ const cfg = loadConfig();
175
+ const allowedModes = cfg.feedback?.allowedFailureModes ?? FEEDBACK_FAILURE_MODES;
176
+ if (allowedModes.length > 0 && !allowedModes.includes(failureMode)) {
177
+ throw new UsageError(`Invalid --failure-mode "${failureMode}". Accepted values: ${allowedModes.join(", ")}.`, "INVALID_FLAG_VALUE", `Use one of: ${allowedModes.join(", ")}`);
166
178
  }
167
- if (!args.positive && !args.negative) {
168
- throw new UsageError("Specify --positive or --negative.");
179
+ }
180
+ if (args.negative === true && !reason?.trim()) {
181
+ // F-3 / #384: Default requireReason is now true. Load config to allow
182
+ // operators to opt out via feedback.requireReason: false in akm.json.
183
+ const cfg = loadConfig();
184
+ const requireReason = cfg.feedback?.requireReason ?? true; // Default: true (F-3 / #384)
185
+ if (requireReason) {
186
+ throw new UsageError("Negative feedback requires --reason (structured failure signals are needed for distillation). " +
187
+ "Use --failure-mode for a curated taxonomy or --reason for free text. " +
188
+ "Set feedback.requireReason: false in akm.json to downgrade to a warning.", "MISSING_REQUIRED_ARGUMENT", `Hint: akm feedback ${ref} --negative --reason "..." [--failure-mode incorrect|outdated|dangerous|incomplete|redundant]`);
169
189
  }
170
- const signal = args.positive ? "positive" : "negative";
171
- const reason = args.reason;
172
- // F-3 / #384: Validate --failure-mode against the curated enum.
173
- const failureMode = args["failure-mode"]?.trim() || undefined;
174
- if (failureMode) {
175
- if (args.positive) {
176
- throw new UsageError("--failure-mode is only valid for negative feedback.", "INVALID_FLAG_VALUE", "Remove --failure-mode or switch to --negative.");
177
- }
178
- const cfg = loadConfig();
179
- const allowedModes = cfg.feedback?.allowedFailureModes ?? FEEDBACK_FAILURE_MODES;
180
- if (allowedModes.length > 0 && !allowedModes.includes(failureMode)) {
181
- throw new UsageError(`Invalid --failure-mode "${failureMode}". Accepted values: ${allowedModes.join(", ")}.`, "INVALID_FLAG_VALUE", `Use one of: ${allowedModes.join(", ")}`);
182
- }
190
+ else {
191
+ warn("Warning: negative feedback without --reason provides less distillation signal.");
183
192
  }
184
- if (args.negative === true && !reason?.trim()) {
185
- // F-3 / #384: Default requireReason is now true. Load config to allow
186
- // operators to opt out via feedback.requireReason: false in akm.json.
187
- const cfg = loadConfig();
188
- const requireReason = cfg.feedback?.requireReason ?? true; // Default: true (F-3 / #384)
189
- if (requireReason) {
190
- throw new UsageError("Negative feedback requires --reason (structured failure signals are needed for distillation). " +
191
- "Use --failure-mode for a curated taxonomy or --reason for free text. " +
192
- "Set feedback.requireReason: false in akm.json to downgrade to a warning.", "MISSING_REQUIRED_ARGUMENT", `Hint: akm feedback ${ref} --negative --reason "..." [--failure-mode incorrect|outdated|dangerous|incomplete|redundant]`);
193
- }
194
- else {
195
- warn("Warning: negative feedback without --reason provides less distillation signal.");
196
- }
193
+ }
194
+ const rawTags = parseAllFlagValues("--tag");
195
+ const validatedTags = validateFeedbackTags(rawTags);
196
+ const metadataObj = {
197
+ signal,
198
+ ...(reason?.trim() ? { reason: reason.trim() } : {}),
199
+ ...(failureMode ? { failureMode } : {}),
200
+ ...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
201
+ };
202
+ const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
203
+ // Feedback only needs the index to exist, not to be current. A stale index
204
+ // is fine the ref lookup works against any populated DB. We do NOT call
205
+ // ensureIndex here: it either blocks (3+ min inline reindex) or spawns a
206
+ // background process that holds the writer lock, causing the feedback write
207
+ // to spin-wait for the full reindex duration. If the DB is absent we give a
208
+ // clear error below rather than silently triggering a rebuild.
209
+ if (!fs.existsSync(getDbPath())) {
210
+ throw new UsageError("Index not found. Run 'akm index' first to build the index before recording feedback.", "MISSING_REQUIRED_ARGUMENT", "akm index");
211
+ }
212
+ // Feedback writes exactly 2 rows (usage_events + utility_score). SQLite
213
+ // WAL mode + busy_timeout=30s handles concurrent access with an ongoing
214
+ // `akm improve` run without needing the application-level writer lock.
215
+ // The lock was originally needed to prevent feedback from racing a
216
+ // background reindex it spawned — now that ensureIndex is removed, holding
217
+ // the lock only causes feedback to block for the full improve run duration.
218
+ let utilityResult;
219
+ const db = openExistingDatabase();
220
+ try {
221
+ const entryId = findEntryIdByRef(db, ref);
222
+ if (entryId === undefined) {
223
+ throw new UsageError(`Ref "${ref}" is not in the index. ` +
224
+ "Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
197
225
  }
198
- const rawTags = parseAllFlagValues("--tag");
199
- const validatedTags = validateFeedbackTags(rawTags);
200
- const metadataObj = {
226
+ // Persist the feedback signal into usage_events. For positive signals,
227
+ // the EMA utility score is updated immediately on the next read path.
228
+ // For negative signals, the score is adjusted the next time `akm index`
229
+ // runs — the signal is durable in the DB but does NOT suppress ranking
230
+ // in search results until after reindexing.
231
+ insertUsageEvent(db, {
232
+ event_type: "feedback",
233
+ entry_ref: ref,
234
+ entry_id: entryId,
201
235
  signal,
202
- ...(reason?.trim() ? { reason: reason.trim() } : {}),
203
- ...(failureMode ? { failureMode } : {}),
204
- ...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
205
- };
206
- const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
207
- const utilityResult = await withIndexWriterLease({ purpose: "feedback-write" }, async () => {
208
- // Feedback is itself an index.db writer, so it must not spawn a detached
209
- // reindex and then compete with it for the same database file.
210
- const sources = resolveSourceEntries();
211
- if (sources.length > 0) {
212
- await ensureIndex(sources[0].path, { mode: "blocking" });
213
- }
214
- let scopedUtilityResult;
215
- const db = openExistingDatabase();
216
- try {
217
- const entryId = findEntryIdByRef(db, ref);
218
- if (entryId === undefined) {
219
- throw new UsageError(`Ref "${ref}" is not in the index. ` +
220
- "Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
221
- }
222
- // Persist the feedback signal into usage_events. For positive signals,
223
- // the EMA utility score is updated immediately on the next read path.
224
- // For negative signals, the score is adjusted the next time `akm index`
225
- // runs — the signal is durable in the DB but does NOT suppress ranking
226
- // in search results until after reindexing.
227
- insertUsageEvent(db, {
228
- event_type: "feedback",
229
- entry_ref: ref,
230
- entry_id: entryId,
231
- signal,
232
- metadata: metadataStr,
233
- });
234
- // Apply feedback-derived utility score adjustment immediately so that
235
- // positive/negative signals influence search ranking without requiring
236
- // a full reindex. We query the total accumulated feedback counts from
237
- // usage_events so the delta reflects the entire signal history.
238
- // Uses MemRL bounded-step EMA (F-5 / #386, arXiv:2601.03192).
239
- try {
240
- const { pos, neg } = countFeedbackSignals(db, entryId);
241
- scopedUtilityResult = applyFeedbackToUtilityScore(db, entryId, pos, neg);
242
- }
243
- catch {
244
- // best-effort — feedback recording succeeds even if utility update fails
245
- }
246
- }
247
- finally {
248
- closeDatabase(db);
249
- }
250
- return scopedUtilityResult;
236
+ metadata: metadataStr,
251
237
  });
252
- appendEvent({
253
- eventType: "feedback",
254
- ref,
255
- metadata: metadataObj,
256
- });
257
- // F-5 / #386: When a high-utility asset crosses below the review threshold,
258
- // auto-create a review-needed escalation proposal so a human can confirm
259
- // whether the negative feedback is valid before the asset falls out of
260
- // the improve loop. Best-effort — failure is logged but does not fail the
261
- // feedback command.
262
- // Emit a structured event rather than a proposal so the review-needed
263
- // signal is queryable via `akm events list --type improve_review_needed`
264
- // without risking accidental asset overwrite if the proposal is accepted.
265
- if (utilityResult?.crossedReviewThreshold) {
266
- try {
267
- appendEvent({
268
- eventType: "improve_review_needed",
269
- ref,
270
- metadata: {
271
- previousUtility: utilityResult.previousUtility,
272
- nextUtility: utilityResult.nextUtility,
273
- reason: reason?.trim() ?? null,
274
- failureMode: failureMode ?? null,
275
- },
276
- });
277
- }
278
- catch (escalationErr) {
279
- warn(`[feedback] Could not emit review-needed event for ${ref}: ${escalationErr instanceof Error ? escalationErr.message : String(escalationErr)}`);
280
- }
238
+ // Apply feedback-derived utility score adjustment immediately so that
239
+ // positive/negative signals influence search ranking without requiring
240
+ // a full reindex. We query the total accumulated feedback counts from
241
+ // usage_events so the delta reflects the entire signal history.
242
+ // Uses MemRL bounded-step EMA (F-5 / #386, arXiv:2601.03192).
243
+ try {
244
+ const { pos, neg } = countFeedbackSignals(db, entryId);
245
+ utilityResult = applyFeedbackToUtilityScore(db, entryId, pos, neg);
246
+ }
247
+ catch {
248
+ // best-effort feedback recording succeeds even if utility update fails
249
+ }
250
+ }
251
+ finally {
252
+ closeDatabase(db);
253
+ }
254
+ appendEvent({
255
+ eventType: "feedback",
256
+ ref,
257
+ metadata: metadataObj,
258
+ });
259
+ // F-5 / #386: When a high-utility asset crosses below the review threshold,
260
+ // auto-create a review-needed escalation proposal so a human can confirm
261
+ // whether the negative feedback is valid before the asset falls out of
262
+ // the improve loop. Best-effort — failure is logged but does not fail the
263
+ // feedback command.
264
+ // Emit a structured event rather than a proposal so the review-needed
265
+ // signal is queryable via `akm events list --type improve_review_needed`
266
+ // without risking accidental asset overwrite if the proposal is accepted.
267
+ if (utilityResult?.crossedReviewThreshold) {
268
+ try {
269
+ appendEvent({
270
+ eventType: "improve_review_needed",
271
+ ref,
272
+ metadata: {
273
+ previousUtility: utilityResult.previousUtility,
274
+ nextUtility: utilityResult.nextUtility,
275
+ reason: reason?.trim() ?? null,
276
+ failureMode: failureMode ?? null,
277
+ },
278
+ });
281
279
  }
282
- // Phase 7A / Advantage D4b: --applied-to credits a lesson. When the
283
- // target is a `lesson:<name>` ref and the signal is positive, append
284
- // the feedback ref to the target lesson's `lessonStrength[]`
285
- // frontmatter array (dedup, idempotent). Non-lesson targets are
286
- // ignored. Failures here are warnings feedback recording is the
287
- // primary contract and must not regress on lesson-write errors.
288
- const appliedToRaw = args["applied-to"]?.trim();
289
- let appliedToResult = null;
290
- if (appliedToRaw && signal === "positive") {
291
- try {
292
- const parsedApplied = parseAssetRef(appliedToRaw);
293
- if (parsedApplied.type === "lesson") {
294
- const updated = appendLessonStrength(parsedApplied.type, parsedApplied.name, ref);
295
- if (updated) {
296
- appliedToResult = { lessonRef: appliedToRaw, strength: updated.strength };
297
- }
280
+ catch (escalationErr) {
281
+ warn(`[feedback] Could not emit review-needed event for ${ref}: ${escalationErr instanceof Error ? escalationErr.message : String(escalationErr)}`);
282
+ }
283
+ }
284
+ // Phase 7A / Advantage D4b: --applied-to credits a lesson. When the
285
+ // target is a `lesson:<name>` ref and the signal is positive, append
286
+ // the feedback ref to the target lesson's `lessonStrength[]`
287
+ // frontmatter array (dedup, idempotent). Non-lesson targets are
288
+ // ignored. Failures here are warnings — feedback recording is the
289
+ // primary contract and must not regress on lesson-write errors.
290
+ const appliedToRaw = args["applied-to"]?.trim();
291
+ let appliedToResult = null;
292
+ if (appliedToRaw && signal === "positive") {
293
+ try {
294
+ const parsedApplied = parseAssetRef(appliedToRaw);
295
+ if (parsedApplied.type === "lesson") {
296
+ const updated = appendLessonStrength(parsedApplied.type, parsedApplied.name, ref);
297
+ if (updated) {
298
+ appliedToResult = { lessonRef: appliedToRaw, strength: updated.strength };
298
299
  }
299
300
  }
300
- catch (err) {
301
- warn(`[feedback] --applied-to failed for ${appliedToRaw}: ${err instanceof Error ? err.message : String(err)}`);
302
- }
303
301
  }
304
- else if (appliedToRaw && signal !== "positive") {
305
- warn("[feedback] --applied-to is ignored without --positive; lesson credit is only recorded on positive signals.");
302
+ catch (err) {
303
+ warn(`[feedback] --applied-to failed for ${appliedToRaw}: ${err instanceof Error ? err.message : String(err)}`);
306
304
  }
307
- output("feedback", {
308
- ok: true,
309
- ref,
310
- signal,
311
- reason: reason?.trim() ?? null,
312
- failureMode: failureMode ?? null,
313
- tags: validatedTags,
314
- ...(appliedToResult
315
- ? { appliedTo: { ref: appliedToResult.lessonRef, lessonStrength: appliedToResult.strength } }
316
- : {}),
317
- });
305
+ }
306
+ else if (appliedToRaw && signal !== "positive") {
307
+ warn("[feedback] --applied-to is ignored without --positive; lesson credit is only recorded on positive signals.");
308
+ }
309
+ output("feedback", {
310
+ ok: true,
311
+ ref,
312
+ signal,
313
+ reason: reason?.trim() ?? null,
314
+ failureMode: failureMode ?? null,
315
+ tags: validatedTags,
316
+ ...(appliedToResult
317
+ ? { appliedTo: { ref: appliedToResult.lessonRef, lessonStrength: appliedToResult.strength } }
318
+ : {}),
318
319
  });
319
320
  },
320
321
  });
@@ -9,12 +9,9 @@
9
9
  * same JSON envelope (stdout/stderr/exit-code) as the inline `runWithJsonErrors`
10
10
  * form it replaces.
11
11
  */
12
- import { defineCommand } from "citty";
13
- import { hasSubcommand, parsePositiveIntFlag } from "../../cli/parse-args.js";
14
- import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
12
+ import { parsePositiveIntFlag } from "../../cli/parse-args.js";
13
+ import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
15
14
  import { akmGraphEntities, akmGraphEntity, akmGraphExport, akmGraphOrphans, akmGraphRelated, akmGraphRelations, akmGraphSummary, akmGraphUpdate, } from "./graph.js";
16
- // Single source of truth: the routing set is derived from the subCommands keys
17
- // (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
18
15
  const graphSubCommands = {
19
16
  summary: defineJsonCommand({
20
17
  meta: { name: "summary", description: "Show entity-graph counts and quality telemetry" },
@@ -118,15 +115,10 @@ const graphSubCommands = {
118
115
  },
119
116
  }),
120
117
  };
121
- const GRAPH_SUBCOMMAND_SET = new Set(Object.keys(graphSubCommands));
122
- export const graphCommand = defineCommand({
118
+ export const graphCommand = defineGroupCommand({
123
119
  meta: { name: "graph", description: "Inspect the indexed entity graph stored in SQLite" },
124
120
  subCommands: graphSubCommands,
125
- run({ args }) {
126
- return runWithJsonErrors(() => {
127
- if (hasSubcommand(args, GRAPH_SUBCOMMAND_SET))
128
- return;
129
- output("graph-summary", akmGraphSummary());
130
- });
121
+ defaultRun() {
122
+ output("graph-summary", akmGraphSummary());
131
123
  },
132
124
  });
@@ -8,7 +8,7 @@ import { loadConfig } from "../../core/config/config.js";
8
8
  import { NotFoundError, UsageError } from "../../core/errors.js";
9
9
  import { getDbPath } from "../../core/paths.js";
10
10
  import { warn } from "../../core/warn.js";
11
- import { closeDatabase, findEntryIdByRef, getEntryById, getEntryRefRowsForStashRoot, openDatabase, openExistingDatabase, } from "../../indexer/db/db.js";
11
+ import { closeDatabase, findEntryIdByRef, getEntryById, getEntryRefRowsForStashRoot, openExistingDatabase, openIndexDatabase, } from "../../indexer/db/db.js";
12
12
  import { loadStoredGraphSnapshot } from "../../indexer/db/graph-db.js";
13
13
  import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
14
14
  import { runGraphExtractionPass } from "../../indexer/graph/graph-extraction.js";
@@ -385,7 +385,7 @@ export async function akmGraphUpdate(options) {
385
385
  let db;
386
386
  const resolvedPaths = new Set();
387
387
  try {
388
- db = openDatabase(dbPath);
388
+ db = openIndexDatabase(dbPath);
389
389
  for (const ref of options.refs) {
390
390
  const trimmed = ref.trim();
391
391
  if (!trimmed)
@@ -426,7 +426,7 @@ export async function akmGraphUpdate(options) {
426
426
  let db;
427
427
  const startMs = Date.now();
428
428
  try {
429
- db = openDatabase(getDbPath());
429
+ db = openIndexDatabase(getDbPath());
430
430
  const onProgress = (event) => {
431
431
  if (!event.currentPath)
432
432
  return;
@@ -0,0 +1,151 @@
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
+ * Improve-pipeline advisories for `akm health`: projects the computed
6
+ * {@link ImproveHealthMetrics} plus a few direct event reads into the
7
+ * ordered advisory list.
8
+ */
9
+ import { readEvents } from "../../core/events.js";
10
+ import { getLatestCycleMetrics } from "../../storage/repositories/canaries-repository.js";
11
+ import { ENRICHMENT_MINTED_FAIL_SHARE, ENRICHMENT_MINTED_WARN_SHARE, } from "./types.js";
12
+ /**
13
+ * Build the improve-pipeline advisories for the health window from the already
14
+ * computed {@link ImproveHealthMetrics} plus a few direct event reads. Pure
15
+ * projection of state → advisories; emission order is preserved so the health
16
+ * report is byte-identical to the previous inline construction.
17
+ */
18
+ export function collectImproveAdvisories(db, stateDbPath, since, improveSummary) {
19
+ const advisories = [];
20
+ // WS-2 proxy-adequacy tripwire: surface any outcome_proxy_inverted events
21
+ // in the health window as an advisory so operators know when the 0.10+
22
+ // rich in-session signal is no longer deferrable.
23
+ const proxyInvertedEvents = readEvents({ since, type: "outcome_proxy_inverted" }, { dbPath: stateDbPath }).events;
24
+ if (proxyInvertedEvents.length > 0) {
25
+ const lastEvent = proxyInvertedEvents[proxyInvertedEvents.length - 1];
26
+ const correlation = typeof lastEvent.metadata?.correlation === "number" ? lastEvent.metadata.correlation.toFixed(3) : "unknown";
27
+ advisories.push({
28
+ name: "outcome-proxy-adequacy",
29
+ status: "warn",
30
+ kind: "deterministic",
31
+ confidence: "high",
32
+ message: `WS-2 outcome proxy inverted (${proxyInvertedEvents.length} event(s) in window). ` +
33
+ `corr(outcome_score, accepted_change_rate) = ${correlation} < −0.3. ` +
34
+ "Popular assets are also the most-needing-improvement assets — " +
35
+ "the retrieval-based proxy is inverted. " +
36
+ "The 0.10+ rich in-session outcome signal is no longer deferrable. See plan §WS-2.",
37
+ });
38
+ }
39
+ // Two-tailed companion: a proxy that decays to noise (|corr| < 0.1 at scale)
40
+ // is as much a failure as an inverted one — it just fails silently.
41
+ const proxyDeadEvents = readEvents({ since, type: "outcome_proxy_dead" }, { dbPath: stateDbPath, db }).events;
42
+ if (proxyDeadEvents.length > 0) {
43
+ const lastEvent = proxyDeadEvents[proxyDeadEvents.length - 1];
44
+ const correlation = typeof lastEvent.metadata?.correlation === "number" ? lastEvent.metadata.correlation.toFixed(3) : "unknown";
45
+ advisories.push({
46
+ name: "outcome-proxy-dead",
47
+ status: "warn",
48
+ kind: "deterministic",
49
+ confidence: "high",
50
+ message: `WS-2 outcome proxy is DEAD (${proxyDeadEvents.length} event(s) in window). ` +
51
+ `|corr(outcome_score, accepted_change_rate)| = ${correlation} < 0.1 at n ≥ 500. ` +
52
+ "outcome_score is statistically unrelated to improvement outcomes — " +
53
+ "treat outcome-derived rank contributions as noise until a real usage/outcome signal lands.",
54
+ });
55
+ }
56
+ // Salience-distribution collapse: Gini below the uniform baseline means
57
+ // ranking no longer discriminates between assets.
58
+ if (improveSummary.degradation?.salienceUniformityFlagged) {
59
+ advisories.push({
60
+ name: "salience-uniformity-collapse",
61
+ status: "warn",
62
+ kind: "deterministic",
63
+ confidence: "high",
64
+ message: `Salience distribution collapsed toward uniform: top-100 retrieval_salience Gini = ` +
65
+ `${improveSummary.degradation.corpusCentroidDistance} < 0.08 (uniform baseline ≈ 0.1). ` +
66
+ "Ranking currently carries little to no discrimination between assets.",
67
+ });
68
+ }
69
+ // Enrichment-vs-minting policy: enrichment lanes edit existing assets;
70
+ // a rising minted share means a lane is generating new content instead.
71
+ const minting = improveSummary.enrichmentMinting;
72
+ if (minting && Number.isFinite(minting.share) && minting.share > ENRICHMENT_MINTED_WARN_SHARE) {
73
+ advisories.push({
74
+ name: "enrichment-lane-minting",
75
+ status: minting.share > ENRICHMENT_MINTED_FAIL_SHARE ? "fail" : "warn",
76
+ kind: "deterministic",
77
+ confidence: "high",
78
+ message: `Enrichment lanes minted ${minting.minted} NEW asset(s) vs ${minting.updated} update(s) ` +
79
+ `(${Math.round(minting.share * 100)}% minted, threshold ${Math.round(ENRICHMENT_MINTED_WARN_SHARE * 100)}%). ` +
80
+ "Enrichment-classed lanes (proactive/high-salience/high-retrieval/signal-delta) are ratified to edit " +
81
+ "existing assets only — new-asset generation belongs to the signal-gated minting lanes.",
82
+ });
83
+ }
84
+ // Churn: accepted proposals far exceeding distinct touched refs means the
85
+ // loop is repeatedly rewriting the same assets, not covering the corpus.
86
+ if (Number.isFinite(improveSummary.coverage.churnRatio) && improveSummary.coverage.churnRatio > 1.5) {
87
+ advisories.push({
88
+ name: "improve-churn-ratio",
89
+ status: "warn",
90
+ kind: "deterministic",
91
+ confidence: "high",
92
+ message: `Improve churn ratio ${improveSummary.coverage.churnRatio} > 1.5: ` +
93
+ `${improveSummary.coverage.acceptedProposals} accepted proposals touched only ` +
94
+ `${improveSummary.coverage.distinctRefs} distinct assets in the window — ` +
95
+ "repeated rewrites of the same refs count as churn, not coverage.",
96
+ });
97
+ }
98
+ // R5 collapse/churn detector: surface any collapse_detector_alert events
99
+ // in the health window, plus the latest cycle row's headline numbers so
100
+ // the operator can act without opening the DB. `unknown` when the detector
101
+ // has never produced a cycle row (no consolidate/recombine work yet).
102
+ try {
103
+ // Reuse the already-open state.db handle (readEvents supports a
104
+ // borrowed connection) — no extra open/migrate/close per health call.
105
+ const collapseAlertEvents = readEvents({ since, type: "collapse_detector_alert" }, { dbPath: stateDbPath, db }).events;
106
+ const latestCycle = getLatestCycleMetrics(db);
107
+ const cycleSummary = latestCycle
108
+ ? `Latest cycle (${latestCycle.ts}, ${latestCycle.pass}): mean canary recall ${latestCycle.mean_recall.toFixed(3)}, ` +
109
+ `distinct-content ratio ${latestCycle.distinct_content_ratio.toFixed(3)}, ` +
110
+ `${latestCycle.accepted_actions} accepted action(s).`
111
+ : "";
112
+ if (collapseAlertEvents.length > 0) {
113
+ const kinds = [...new Set(collapseAlertEvents.map((e) => String(e.metadata?.kind ?? "unknown")))];
114
+ const collapseKinds = kinds.filter((k) => k.startsWith("collapse"));
115
+ advisories.push({
116
+ name: "collapse-churn-detector",
117
+ status: "warn",
118
+ kind: "deterministic",
119
+ // Collapse kinds are measured, not inferred; churn/merge-floor
120
+ // volume thresholds are still being tuned (design doc §7).
121
+ confidence: collapseKinds.length > 0 ? "high" : "medium",
122
+ message: `R5 detector fired ${collapseAlertEvents.length} alert(s) in window (kinds: ${kinds.join(", ")}). ` +
123
+ `${cycleSummary} See docs/design/improve-collapse-churn-detector-design.md §6.3 runbook queries.`,
124
+ });
125
+ }
126
+ else if (latestCycle) {
127
+ advisories.push({
128
+ name: "collapse-churn-detector",
129
+ status: "pass",
130
+ kind: "deterministic",
131
+ confidence: "high",
132
+ message: `No collapse/churn alerts in window. ${cycleSummary}`,
133
+ });
134
+ }
135
+ else {
136
+ advisories.push({
137
+ name: "collapse-churn-detector",
138
+ status: "unknown",
139
+ kind: "deterministic",
140
+ confidence: "high",
141
+ message: "No detector cycle rows yet — the collapse/churn detector runs only on improve cycles " +
142
+ "where consolidate/recombine did work (synthesis lanes may be idle).",
143
+ });
144
+ }
145
+ }
146
+ catch {
147
+ // Table may predate migration 016 in odd mixed-version setups — advisory
148
+ // is best-effort and must never fail the health command.
149
+ }
150
+ return advisories;
151
+ }