akm-cli 0.9.0-rc.8 → 0.9.0

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 (450) hide show
  1. package/CHANGELOG.md +1063 -44
  2. package/README.md +51 -25
  3. package/SECURITY.md +14 -1
  4. package/STABILITY.md +497 -0
  5. package/dist/akm +148 -35
  6. package/dist/{akm-migrate-storage → akm-migrate} +6 -9
  7. package/dist/assets/hints/cli-hints-full.md +223 -95
  8. package/dist/assets/hints/cli-hints-short.md +85 -22
  9. package/dist/assets/improve-strategies/default.json +1 -1
  10. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  11. package/dist/assets/prompts/memory-infer-user.md +2 -3
  12. package/dist/assets/stash-skeleton/README.md +6 -5
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
  24. package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
  25. package/dist/assets/tasks/core/extract.yml +1 -1
  26. package/dist/assets/tasks/core/version-check.yml +1 -1
  27. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  28. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  29. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  30. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  31. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  32. package/dist/assets/templates/html/health.html +1 -3
  33. package/dist/assets/workflows/workflow-template.md +32 -15
  34. package/dist/cli/invocation.js +40 -15
  35. package/dist/cli/parse-args.js +0 -22
  36. package/dist/cli/retired-commands.js +121 -0
  37. package/dist/cli/shared.js +154 -22
  38. package/dist/cli/unknown-flags.js +236 -0
  39. package/dist/cli-node.mjs +2 -1
  40. package/dist/cli.js +696 -258
  41. package/dist/commands/agent/agent-dispatch.js +14 -3
  42. package/dist/commands/agent/contribute-cli.js +73 -88
  43. package/dist/commands/completions.js +79 -22
  44. package/dist/commands/config-cli.js +17 -150
  45. package/dist/commands/env/env-cli.js +59 -143
  46. package/dist/commands/env/env.js +12 -163
  47. package/dist/commands/env/marker-path.js +6 -0
  48. package/dist/commands/env/secret-cli.js +36 -66
  49. package/dist/commands/env/secret.js +24 -57
  50. package/dist/commands/feedback-cli.js +141 -87
  51. package/dist/commands/health/accept-rate.js +58 -0
  52. package/dist/commands/health/advisories.js +3 -4
  53. package/dist/commands/health/checks.js +85 -23
  54. package/dist/commands/health/html-report.js +7 -10
  55. package/dist/commands/health/improve-metrics.js +25 -83
  56. package/dist/commands/health/md-report.js +5 -9
  57. package/dist/commands/health/metrics.js +62 -20
  58. package/dist/commands/health/renderers.js +47 -0
  59. package/dist/commands/health/report-view-model.js +4 -5
  60. package/dist/commands/health/stash-exposure.js +1 -1
  61. package/dist/commands/health/surfaces.js +3 -48
  62. package/dist/commands/health/task-runs.js +3 -67
  63. package/dist/commands/health/types-improve.js +7 -0
  64. package/dist/commands/health.js +99 -28
  65. package/dist/commands/improve/anti-collapse.js +2 -2
  66. package/dist/commands/improve/autonomy-gate.js +68 -0
  67. package/dist/commands/improve/collapse-detector.js +41 -40
  68. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  69. package/dist/commands/improve/consolidate/merge.js +4 -0
  70. package/dist/commands/improve/consolidate.js +140 -1000
  71. package/dist/commands/improve/distill/promote-memory.js +12 -12
  72. package/dist/commands/improve/distill/quality-gate.js +6 -6
  73. package/dist/commands/improve/distill.js +58 -69
  74. package/dist/commands/improve/eligibility.js +105 -57
  75. package/dist/commands/improve/extract-cli.js +14 -133
  76. package/dist/commands/improve/improve-cli.js +98 -114
  77. package/dist/commands/improve/improve-result-file.js +1 -28
  78. package/dist/commands/improve/improve-strategies.js +8 -5
  79. package/dist/commands/improve/improve.js +128 -91
  80. package/dist/commands/improve/loop-stages.js +182 -20
  81. package/dist/commands/improve/memory/derived-ref.js +45 -43
  82. package/dist/commands/improve/memory/memory-belief.js +1 -1
  83. package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
  84. package/dist/commands/improve/memory/memory-improve.js +6 -5
  85. package/dist/commands/improve/outcome-loop.js +22 -65
  86. package/dist/commands/improve/preparation.js +114 -123
  87. package/dist/commands/improve/proactive-maintenance.js +2 -5
  88. package/dist/commands/improve/reflect.js +56 -160
  89. package/dist/commands/improve/salience.js +11 -122
  90. package/dist/commands/improve/source-identity.js +10 -38
  91. package/dist/commands/lint/base-linter.js +20 -124
  92. package/dist/commands/lint/env-key-rules.js +31 -47
  93. package/dist/commands/lint/index.js +249 -43
  94. package/dist/commands/{events.js → log.js} +33 -38
  95. package/dist/commands/migrate-cli.js +92 -12
  96. package/dist/commands/migration-tool.js +46 -0
  97. package/dist/commands/observability-cli.js +70 -209
  98. package/dist/commands/proposal/drain.js +101 -29
  99. package/dist/commands/proposal/proposal-cli.js +76 -48
  100. package/dist/commands/proposal/proposal.js +54 -18
  101. package/dist/commands/proposal/propose-cli.js +88 -0
  102. package/dist/commands/proposal/propose.js +23 -15
  103. package/dist/commands/proposal/repository.js +701 -278
  104. package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
  105. package/dist/commands/proposal/validators/proposal-validators.js +55 -7
  106. package/dist/commands/proposal/validators/proposals.js +4 -7
  107. package/dist/commands/read/curate.js +34 -53
  108. package/dist/commands/read/knowledge.js +150 -95
  109. package/dist/commands/read/registry-search.js +2 -2
  110. package/dist/commands/read/remember-cli.js +42 -15
  111. package/dist/commands/read/search-cli.js +180 -78
  112. package/dist/commands/read/search.js +58 -43
  113. package/dist/commands/read/show.js +197 -141
  114. package/dist/commands/registry-cli.js +12 -51
  115. package/dist/commands/remember.js +14 -57
  116. package/dist/commands/sources/add-cli.js +100 -31
  117. package/dist/commands/sources/bundle-cli.js +166 -0
  118. package/dist/commands/sources/bundle-config-ops.js +7 -2
  119. package/dist/commands/sources/info.js +18 -5
  120. package/dist/commands/sources/init.js +12 -12
  121. package/dist/commands/sources/installed-stashes.js +382 -98
  122. package/dist/commands/sources/schema-repair.js +3 -2
  123. package/dist/commands/sources/self-update.js +131 -38
  124. package/dist/commands/sources/source-add.js +72 -17
  125. package/dist/commands/sources/source-clone.js +129 -45
  126. package/dist/commands/sources/source-manage.js +43 -23
  127. package/dist/commands/sources/sources-cli.js +57 -208
  128. package/dist/commands/sources/stash-cli.js +46 -53
  129. package/dist/commands/tasks/tasks-cli.js +91 -97
  130. package/dist/commands/tasks/tasks.js +276 -421
  131. package/dist/commands/workflow-cli.js +175 -450
  132. package/dist/core/adapter/adapters/akm-adapter.js +47 -28
  133. package/dist/core/adapter/adapters/akm-lint.js +42 -27
  134. package/dist/core/adapter/adapters/akm-metadata.js +15 -44
  135. package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
  136. package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
  137. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  138. package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
  139. package/dist/core/adapter/adapters/index.js +6 -6
  140. package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
  141. package/dist/core/adapter/adapters/okf-adapter.js +187 -19
  142. package/dist/core/adapter/adapters/shared.js +3 -19
  143. package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
  144. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
  145. package/dist/core/adapter/detect-adapter.js +17 -0
  146. package/dist/core/adapter/recognize-match.js +6 -4
  147. package/dist/core/adapter/validate-context.js +214 -0
  148. package/dist/core/asset/akm-markdown.js +63 -0
  149. package/dist/core/asset/asset-placement.js +20 -6
  150. package/dist/core/asset/asset-ref.js +11 -9
  151. package/dist/core/asset/frontmatter-lint.js +30 -0
  152. package/dist/core/asset/frontmatter.js +37 -9
  153. package/dist/core/asset/markdown.js +40 -51
  154. package/dist/core/asset/resolve-ref.js +89 -18
  155. package/dist/core/asset/stash-meta.js +1 -1
  156. package/dist/core/bundle-id.js +51 -0
  157. package/dist/core/common.js +152 -38
  158. package/dist/core/config/config-io.js +12 -1
  159. package/dist/core/config/config-schema.js +35 -8
  160. package/dist/core/config/config-sources.js +55 -11
  161. package/dist/core/config/config-walker.js +25 -9
  162. package/dist/core/config/config.js +9 -48
  163. package/dist/core/config/experimental.js +21 -0
  164. package/dist/core/config/schema/embedding.js +5 -1
  165. package/dist/core/config/schema/experimental.js +30 -0
  166. package/dist/core/config/schema/improve-processes.js +0 -6
  167. package/dist/core/config/schema/improve.js +21 -3
  168. package/dist/core/config/schema/index-config.js +8 -15
  169. package/dist/core/config/schema/output.js +4 -1
  170. package/dist/core/config/schema/setup.js +9 -18
  171. package/dist/core/config/schema/sources-bundles.js +49 -33
  172. package/dist/core/config/schema/workflow.js +3 -3
  173. package/dist/core/env-secret-ref.js +76 -46
  174. package/dist/core/errors.js +18 -12
  175. package/dist/core/events.js +46 -128
  176. package/dist/core/file-change.js +6 -5
  177. package/dist/core/fs-txn.js +83 -7
  178. package/dist/core/git-message.js +2 -2
  179. package/dist/core/improve-result.js +1 -100
  180. package/dist/core/lesson-lint.js +1 -17
  181. package/dist/core/logs-db.js +2 -1
  182. package/dist/core/migration-operation.js +16 -0
  183. package/dist/core/mutation-target.js +78 -0
  184. package/dist/core/parse.js +4 -1
  185. package/dist/core/paths.js +17 -20
  186. package/dist/core/recognition-util.js +12 -14
  187. package/dist/core/redaction.js +34 -0
  188. package/dist/core/standards/resolve-standards-context.js +2 -14
  189. package/dist/core/standards/resolve-stash-standards.js +2 -2
  190. package/dist/core/standards/resolve-type-conventions.js +2 -2
  191. package/dist/core/state/migrations.js +41 -18
  192. package/dist/core/state-db.js +5 -14
  193. package/dist/core/structured.js +1 -1
  194. package/dist/core/subprocess.js +6 -4
  195. package/dist/core/text-truncation.js +9 -5
  196. package/dist/core/type-presentation.js +3 -3
  197. package/dist/core/warn.js +0 -3
  198. package/dist/core/write-source.js +771 -95
  199. package/dist/indexer/bundle-identity-guard.js +3 -2
  200. package/dist/indexer/db/graph-db.js +0 -24
  201. package/dist/indexer/ensure-index.js +1 -0
  202. package/dist/indexer/graph/graph-boost.js +9 -34
  203. package/dist/indexer/graph/graph-extraction.js +8 -5
  204. package/dist/indexer/index-writer-lock.js +53 -17
  205. package/dist/indexer/index-written-assets.js +16 -22
  206. package/dist/indexer/indexer.js +497 -239
  207. package/dist/indexer/installations.js +14 -96
  208. package/dist/indexer/passes/dir-staleness.js +16 -9
  209. package/dist/indexer/passes/memory-inference.js +11 -9
  210. package/dist/indexer/passes/metadata.js +113 -47
  211. package/dist/indexer/scan/doc-to-entry.js +38 -1
  212. package/dist/indexer/scan/drain-dir.js +13 -23
  213. package/dist/indexer/search/db-search.js +99 -54
  214. package/dist/indexer/search/fts-query.js +47 -24
  215. package/dist/indexer/search/ranking-contributors.js +42 -20
  216. package/dist/indexer/search/ranking.js +18 -99
  217. package/dist/indexer/search/search-fields.js +7 -2
  218. package/dist/indexer/search/search-source.js +82 -93
  219. package/dist/indexer/usage/usage-events.js +0 -89
  220. package/dist/indexer/walk/file-context.js +2 -1
  221. package/dist/indexer/walk/matchers.js +30 -43
  222. package/dist/indexer/walk/path-resolver.js +7 -2
  223. package/dist/indexer/walk/walker.js +38 -12
  224. package/dist/integrations/agent/builders.js +0 -6
  225. package/dist/integrations/agent/config.js +2 -2
  226. package/dist/integrations/agent/detect.js +49 -19
  227. package/dist/integrations/agent/engine-fallback.js +76 -0
  228. package/dist/integrations/agent/profiles.js +14 -0
  229. package/dist/integrations/agent/prompts.js +12 -8
  230. package/dist/integrations/agent/runner-dispatch.js +4 -2
  231. package/dist/integrations/agent/runner.js +0 -1
  232. package/dist/integrations/agent/spawn.js +5 -6
  233. package/dist/integrations/github.js +1 -1
  234. package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
  235. package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
  236. package/dist/integrations/harnesses/claude/session-log.js +0 -10
  237. package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
  238. package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
  239. package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
  240. package/dist/integrations/harnesses/index.js +3 -7
  241. package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
  242. package/dist/integrations/harnesses/opencode/session-log.js +0 -15
  243. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
  244. package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
  245. package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
  246. package/dist/integrations/lockfile.js +101 -6
  247. package/dist/integrations/session-logs/index.js +3 -28
  248. package/dist/llm/client.js +136 -100
  249. package/dist/llm/embedders/remote.js +13 -5
  250. package/dist/llm/feature-gate.js +4 -12
  251. package/dist/llm/graph-extract.js +5 -11
  252. package/dist/llm/memory-infer.js +144 -1
  253. package/dist/llm/metadata-enhance.js +5 -7
  254. package/dist/llm/structured-call.js +1 -1
  255. package/dist/llm/usage-persist.js +26 -5
  256. package/dist/llm/usage-telemetry.js +25 -2
  257. package/dist/output/cli-hints.js +1 -2
  258. package/dist/output/context.js +22 -7
  259. package/dist/output/format-exempt.js +80 -0
  260. package/dist/output/generic-render.js +259 -0
  261. package/dist/output/render-registry.js +57 -0
  262. package/dist/output/renderers.js +14 -36
  263. package/dist/output/shapes/curate.js +10 -1
  264. package/dist/output/shapes/events.js +12 -7
  265. package/dist/output/shapes/helpers.js +56 -83
  266. package/dist/output/shapes/migrate.js +8 -0
  267. package/dist/output/shapes/passthrough.js +7 -41
  268. package/dist/output/shapes/proposal/producer.js +15 -7
  269. package/dist/output/shapes.js +2 -9
  270. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  271. package/dist/output/text/bundle-show.js +7 -0
  272. package/dist/output/text/command-format.js +164 -96
  273. package/dist/output/text/env.js +1 -3
  274. package/dist/output/text/events.js +8 -7
  275. package/dist/output/text/health-format.js +103 -0
  276. package/dist/output/text/health.js +7 -0
  277. package/dist/output/text/helpers.js +10 -8
  278. package/dist/output/text/lint-format.js +43 -0
  279. package/dist/output/text/{save.js → lint.js} +2 -2
  280. package/dist/output/text/migrate.js +88 -0
  281. package/dist/output/text/proposal/producer.js +4 -2
  282. package/dist/output/text/proposal-format.js +44 -72
  283. package/dist/output/text/registry-commands.js +1 -2
  284. package/dist/output/text/show-directives.js +15 -7
  285. package/dist/output/text/status-list.js +32 -0
  286. package/dist/output/text/sync.js +5 -0
  287. package/dist/output/text/workflow-format.js +24 -203
  288. package/dist/output/text/workflow.js +1 -7
  289. package/dist/output/text.js +16 -17
  290. package/dist/registry/factory.js +4 -6
  291. package/dist/registry/origin-resolve.js +16 -27
  292. package/dist/registry/providers/skills-sh.js +3 -3
  293. package/dist/registry/providers/static-index.js +13 -23
  294. package/dist/registry/resolve.js +42 -7
  295. package/dist/registry/semver.js +34 -84
  296. package/dist/runtime.js +2 -23
  297. package/dist/scripts/akm-migrate-node.js +60290 -0
  298. package/dist/scripts/akm-migrate.js +59628 -0
  299. package/dist/setup/detect.js +42 -15
  300. package/dist/setup/registry-stash-loader.js +2 -2
  301. package/dist/setup/setup.js +236 -136
  302. package/dist/setup/steps/connection.js +7 -9
  303. package/dist/setup/steps/platforms.js +9 -9
  304. package/dist/setup/steps/semantic.js +15 -3
  305. package/dist/setup/steps/sources.js +12 -13
  306. package/dist/setup/steps/stashdir.js +2 -3
  307. package/dist/setup/steps/tasks.js +237 -120
  308. package/dist/sources/freshness.js +1 -1
  309. package/dist/sources/provider-factory.js +11 -17
  310. package/dist/sources/providers/filesystem.js +2 -3
  311. package/dist/sources/providers/git-install.js +278 -34
  312. package/dist/sources/providers/git-provider.js +25 -23
  313. package/dist/sources/providers/git-stash.js +395 -106
  314. package/dist/sources/providers/git.js +2 -2
  315. package/dist/sources/providers/npm.js +16 -19
  316. package/dist/sources/providers/provider-utils.js +7 -4
  317. package/dist/sources/providers/sync-from-ref.js +3 -9
  318. package/dist/sources/providers/website.js +6 -1
  319. package/dist/sources/resolve.js +6 -5
  320. package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
  321. package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
  322. package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
  323. package/dist/sources/snapshot-fetchers/github.js +100 -0
  324. package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
  325. package/dist/sources/snapshot-fetchers/registry.js +17 -1
  326. package/dist/sources/snapshot-fetchers/robots.js +348 -0
  327. package/dist/sources/snapshot-fetchers/rss.js +282 -0
  328. package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
  329. package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
  330. package/dist/sources/snapshot-fetchers/x.js +910 -0
  331. package/dist/storage/database.js +7 -0
  332. package/dist/storage/engines/sqlite-migrations.js +23 -111
  333. package/dist/storage/managed-db.js +2 -2
  334. package/dist/storage/repositories/canaries-repository.js +1 -1
  335. package/dist/storage/repositories/events-repository.js +27 -11
  336. package/dist/storage/repositories/improve-runs-repository.js +6 -12
  337. package/dist/storage/repositories/index-connection.js +17 -6
  338. package/dist/storage/repositories/index-entries-repository.js +151 -240
  339. package/dist/storage/repositories/index-entry-mapper.js +15 -11
  340. package/dist/storage/repositories/index-fts-repository.js +5 -2
  341. package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
  342. package/dist/storage/repositories/index-meta-repository.js +2 -3
  343. package/dist/storage/repositories/index-schema.js +10 -25
  344. package/dist/storage/repositories/index-utility-repository.js +15 -28
  345. package/dist/storage/repositories/index-vec-repository.js +6 -1
  346. package/dist/storage/repositories/outcome-repository.js +119 -0
  347. package/dist/storage/repositories/proposals-repository.js +296 -59
  348. package/dist/storage/repositories/registry-cache.js +19 -0
  349. package/dist/storage/repositories/salience-repository.js +172 -0
  350. package/dist/storage/repositories/task-history-repository.js +15 -13
  351. package/dist/storage/repositories/workflow-runs-repository.js +52 -40
  352. package/dist/tasks/backends/cron.js +105 -15
  353. package/dist/tasks/backends/index.js +1 -1
  354. package/dist/tasks/backends/launchd.js +85 -38
  355. package/dist/tasks/backends/schtasks.js +135 -15
  356. package/dist/tasks/embedded.js +56 -40
  357. package/dist/tasks/parser.js +7 -157
  358. package/dist/tasks/resolve-akm-bin.js +137 -59
  359. package/dist/tasks/runner.js +79 -42
  360. package/dist/tasks/scheduler-invocation.js +220 -10
  361. package/dist/tasks/schema.js +24 -1
  362. package/dist/tasks/task-id.js +1 -3
  363. package/dist/tasks/validator.js +20 -6
  364. package/dist/workflows/authoring/authoring.js +94 -143
  365. package/dist/workflows/authoring/scope-key.js +1 -1
  366. package/dist/workflows/exec/frozen-judge.js +28 -2
  367. package/dist/workflows/exec/native-executor.js +77 -57
  368. package/dist/workflows/exec/param-secrets.js +9 -9
  369. package/dist/workflows/exec/run-workflow.js +133 -79
  370. package/dist/workflows/exec/step-work.js +219 -346
  371. package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
  372. package/dist/workflows/ir/compile.js +141 -270
  373. package/dist/workflows/ir/freeze.js +40 -30
  374. package/dist/workflows/ir/params.js +135 -11
  375. package/dist/workflows/ir/plan-hash.js +1 -1
  376. package/dist/workflows/ir/schema.js +25 -26
  377. package/dist/workflows/parser.js +872 -307
  378. package/dist/workflows/program/expressions.js +20 -208
  379. package/dist/workflows/program/schema.js +7 -10
  380. package/dist/workflows/renderer.js +95 -68
  381. package/dist/workflows/resource-limits.js +2 -0
  382. package/dist/workflows/runtime/checkin.js +3 -3
  383. package/dist/workflows/runtime/plan-classifier.js +16 -75
  384. package/dist/workflows/runtime/runs.js +186 -127
  385. package/dist/workflows/runtime/unit-checkin.js +1 -1
  386. package/dist/workflows/runtime/unit-phases.js +2 -2
  387. package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
  388. package/dist/workflows/schema.js +1 -11
  389. package/dist/workflows/validate-summary.js +30 -36
  390. package/dist/workflows/validator.js +21 -62
  391. package/docs/README.md +68 -0
  392. package/docs/migration/README.md +8 -0
  393. package/docs/migration/release-notes/0.7.0.md +11 -11
  394. package/docs/migration/release-notes/0.9.0.md +208 -27
  395. package/docs/migration/v0.7-to-v0.8.md +46 -47
  396. package/docs/migration/v0.8-to-v0.9.md +564 -208
  397. package/docs/migration/v0.9.0-troubleshooting.md +561 -0
  398. package/docs/reference/README.md +12 -0
  399. package/docs/reference/cli.md +2253 -0
  400. package/docs/reference/configuration.md +358 -0
  401. package/docs/reference/data-and-telemetry.md +105 -42
  402. package/docs/reference/workflows.md +647 -0
  403. package/package.json +22 -11
  404. package/schemas/akm-asset-envelope.json +93 -0
  405. package/schemas/akm-config.json +81 -128
  406. package/schemas/akm-workflow.json +74 -73
  407. package/dist/assets/tasks/core/backup.yml +0 -5
  408. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  409. package/dist/cli/config-migrate.js +0 -1806
  410. package/dist/cli/config-validate.js +0 -41
  411. package/dist/commands/backup-cli.js +0 -56
  412. package/dist/commands/bundle/bundle-cli.js +0 -68
  413. package/dist/commands/bundle/bundle.js +0 -219
  414. package/dist/commands/graph/graph-cli.js +0 -124
  415. package/dist/commands/graph/graph.js +0 -489
  416. package/dist/commands/improve/extract-watch.js +0 -140
  417. package/dist/commands/mv-cli.js +0 -1221
  418. package/dist/commands/sources/history.js +0 -201
  419. package/dist/commands/tasks/default-tasks.js +0 -186
  420. package/dist/core/migration-backup.js +0 -1234
  421. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
  422. package/dist/llm/memory-infer-impl.js +0 -138
  423. package/dist/migrate/legacy/config-source-migration.js +0 -223
  424. package/dist/migrate/legacy/content-migration.js +0 -305
  425. package/dist/migrate/legacy/legacy-layout.js +0 -779
  426. package/dist/migrate/legacy/legacy-paths.js +0 -25
  427. package/dist/migrate/legacy/legacy-stash-json.js +0 -72
  428. package/dist/migrate/legacy/proposal-fs-import.js +0 -168
  429. package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
  430. package/dist/migrate/legacy/three-db-cutover.js +0 -841
  431. package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
  432. package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
  433. package/dist/migrate/legacy-ref-grammar.js +0 -214
  434. package/dist/output/shapes/distill.js +0 -14
  435. package/dist/output/shapes/history.js +0 -11
  436. package/dist/output/text/distill.js +0 -6
  437. package/dist/output/text/enable-disable.js +0 -8
  438. package/dist/output/text/history.js +0 -6
  439. package/dist/registry/build-index.js +0 -382
  440. package/dist/schemas/akm-config.json +0 -4704
  441. package/dist/schemas/akm-task.json +0 -87
  442. package/dist/schemas/akm-workflow.json +0 -372
  443. package/dist/scripts/migrate-storage.js +0 -3816
  444. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  445. package/dist/workflows/cli.js +0 -53
  446. package/dist/workflows/exec/brief.js +0 -481
  447. package/dist/workflows/exec/report.js +0 -1460
  448. package/dist/workflows/exec/watch.js +0 -116
  449. package/dist/workflows/program/parser.js +0 -813
  450. package/dist/workflows/program/project.js +0 -104
@@ -3,20 +3,24 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import { defineJsonCommand, output, parseAllFlagValues } from "../cli/shared.js";
6
+ import { makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
6
7
  import { assembleAsset } from "../core/asset/asset-serialize.js";
7
8
  import { parseFrontmatter, parseFrontmatterBlock } from "../core/asset/frontmatter.js";
8
- import { conceptIdFromTypeName, displayRef, parseRefInput } from "../core/asset/resolve-ref.js";
9
- import { writeFileAtomic } from "../core/common.js";
9
+ import { conceptIdFromTypeName, parseRefInput } from "../core/asset/resolve-ref.js";
10
+ import { isWithin, writeFileAtomic } from "../core/common.js";
10
11
  import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config/config.js";
11
- import { UsageError } from "../core/errors.js";
12
+ import { NotFoundError, UsageError } from "../core/errors.js";
12
13
  import { appendEvent } from "../core/events.js";
14
+ import { resolveMutationTarget } from "../core/mutation-target.js";
13
15
  import { getDbPath } from "../core/paths.js";
14
16
  import { withStateDb } from "../core/state-db.js";
15
17
  import { warn } from "../core/warn.js";
18
+ import { withWriteTargetMutation } from "../core/write-source.js";
16
19
  import { resolveSourceEntries } from "../indexer/search/search-source.js";
17
20
  import { countFeedbackSignals, insertUsageEvent, resolveUsageEventSource } from "../indexer/usage/usage-events.js";
21
+ import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
18
22
  import { closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
19
- import { findEntryIdByRef, getEntryById, getEntryFilePathById, getItemRefById, } from "../storage/repositories/index-entries-repository.js";
23
+ import { findEntryIdByRef, getEntryFilePathById, getItemRefById, } from "../storage/repositories/index-entries-repository.js";
20
24
  import { applyFeedbackToUtilityScore } from "../storage/repositories/index-utility-repository.js";
21
25
  // ── Tag validation ────────────────────────────────────────────────────────────
22
26
  const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
@@ -56,34 +60,68 @@ function validateFeedbackTags(raw) {
56
60
  * an existing asset — the same pattern memory-inference uses for
57
61
  * `inferenceProcessed`.
58
62
  */
59
- function appendLessonStrength(type, name, feedbackRef) {
60
- // Canonical 0.9.0 conceptId (`lessons/<name>`, D-R3) — `findEntryIdByRef`
61
- // keys on the stored `item_ref` and parses its argument as the new grammar,
62
- // so a `type:name` lookup here would never match.
63
- const ref = conceptIdFromTypeName(type, name);
63
+ function appendLessonStrength(refInput, feedbackRef) {
64
+ // Canonical conceptId (`lessons/<name>`, D-R3): `findEntryIdByRef` keys on
65
+ // the stored `item_ref`.
66
+ const conceptId = conceptIdFromTypeName(refInput.type, refInput.name);
67
+ const config = loadConfig();
64
68
  let filePath;
69
+ let bundleId;
65
70
  const db = openExistingDatabase();
66
71
  try {
67
- const entryId = findEntryIdByRef(db, ref);
68
- if (entryId === undefined) {
69
- warn(`[feedback] --applied-to: lesson ${ref} is not in the index.`);
70
- return null;
72
+ const entryId = findEntryIdByRef(db, makeBundleRef(refInput.origin, conceptId));
73
+ if (entryId !== undefined) {
74
+ const itemRef = getItemRefById(db, entryId);
75
+ const parsedItemRef = itemRef ? parseBundleRef(itemRef) : undefined;
76
+ filePath = getEntryFilePathById(db, entryId) ?? undefined;
77
+ bundleId = parsedItemRef?.bundle;
71
78
  }
72
- const resolvedPath = getEntryFilePathById(db, entryId);
73
- if (!resolvedPath) {
74
- warn(`[feedback] --applied-to: cannot resolve file path for ${ref}.`);
75
- return null;
76
- }
77
- filePath = resolvedPath;
78
79
  }
79
80
  finally {
80
81
  closeDatabase(db);
81
82
  }
82
- if (!filePath || !fs.existsSync(filePath)) {
83
- warn(`[feedback] --applied-to: lesson file missing on disk for ${ref}.`);
83
+ const requestedRef = makeBundleRef(refInput.origin, conceptId);
84
+ if (!filePath || !bundleId || !fs.existsSync(filePath)) {
85
+ warn(`[feedback] --applied-to: lesson ${requestedRef} is not in the index or is missing on disk.`);
86
+ return null;
87
+ }
88
+ const resolved = resolveMutationTarget(config, { ...refInput, origin: bundleId });
89
+ if (!isWithin(filePath, resolved.target.source.path)) {
90
+ throw new UsageError(`Resolved lesson ${requestedRef} is outside bundle "${bundleId}".`);
91
+ }
92
+ fs.lstatSync(filePath);
93
+ const initialUpdate = buildLessonStrengthUpdate(fs.readFileSync(filePath), feedbackRef);
94
+ if (!initialUpdate.nextBytes) {
95
+ return { ref: makeBundleRef(bundleId, conceptId), strength: initialUpdate.strength };
96
+ }
97
+ let strength = initialUpdate.strength;
98
+ let mutationStarted = false;
99
+ try {
100
+ withWriteTargetMutation(resolved.target, [filePath], {
101
+ ignored: "reject",
102
+ purpose: "feedback-lesson-credit",
103
+ message: `Update ${makeBundleRef(bundleId, conceptId)}`,
104
+ }, () => {
105
+ const stat = fs.lstatSync(filePath);
106
+ const update = buildLessonStrengthUpdate(fs.readFileSync(filePath), feedbackRef);
107
+ strength = update.strength;
108
+ if (!update.nextBytes)
109
+ return;
110
+ mutationStarted = true;
111
+ // Preserve the existing file's permission bits (markdown assets are typically 0o644).
112
+ writeFileAtomic(filePath, update.nextBytes, stat.mode & 0o777);
113
+ });
114
+ }
115
+ catch (err) {
116
+ if (mutationStarted)
117
+ throw err;
118
+ warn(`[feedback] --applied-to: failed to write ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
84
119
  return null;
85
120
  }
86
- const raw = fs.readFileSync(filePath, "utf8");
121
+ return { ref: makeBundleRef(bundleId, conceptId), strength };
122
+ }
123
+ function buildLessonStrengthUpdate(rawBytes, feedbackRef) {
124
+ const raw = rawBytes.toString("utf8");
87
125
  const parsed = parseFrontmatter(raw);
88
126
  const data = { ...parsed.data };
89
127
  const existing = data.lessonStrength;
@@ -93,7 +131,6 @@ function appendLessonStrength(type, name, feedbackRef) {
93
131
  ? [existing.trim()]
94
132
  : [];
95
133
  if (strengthList.includes(feedbackRef)) {
96
- // Already credited — idempotent no-op.
97
134
  return { strength: strengthList.length };
98
135
  }
99
136
  strengthList.push(feedbackRef);
@@ -101,29 +138,29 @@ function appendLessonStrength(type, name, feedbackRef) {
101
138
  const block = parseFrontmatterBlock(raw);
102
139
  const body = block?.content ?? raw;
103
140
  const next = assembleAsset(data, body);
104
- try {
105
- // Preserve the existing file's permission bits (markdown assets are
106
- // typically 0o644); writeFileAtomic defaults to 0o600 otherwise.
107
- const mode = fs.statSync(filePath).mode & 0o777;
108
- writeFileAtomic(filePath, next, mode);
109
- }
110
- catch (err) {
111
- warn(`[feedback] --applied-to: failed to write ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
112
- return null;
113
- }
114
- return { strength: strengthList.length };
141
+ const nextBytes = Buffer.from(next);
142
+ return rawBytes.equals(nextBytes) ? { strength: strengthList.length } : { strength: strengthList.length, nextBytes };
115
143
  }
116
144
  /**
117
145
  * Persist the feedback usage-event (state.db) and immediately fold it into the
118
146
  * entry's MemRL utility score (index.db). Chunk-8 WI-8.3: usage_events lives in
119
- * state.db; entries + utility_scores stay in `indexDb`. Positive signals raise
120
- * search ranking on the next read without a full reindex; negatives are durable
121
- * but take effect at the next `akm index`. Uses the bounded-step EMA policy
122
- * (F-5 / #386, arXiv:2601.03192). Best-effort: a utility-update failure never
123
- * fails the feedback record.
147
+ * state.db; entries + utility_scores stay in `indexDb`. BOTH positive and
148
+ * negative signals apply the EMA utility update unconditionally and
149
+ * immediately when the source is user-attributed — no `akm index` run is
150
+ * required for either signal to affect search ranking. Uses the bounded-step
151
+ * EMA policy (F-5 / #386, arXiv:2601.03192).
152
+ *
153
+ * The update is intentionally SKIPPED for non-`user` event sources (`improve`,
154
+ * `task`, `audit`, `unknown`) — an anti-self-reinforcement guard that stops an
155
+ * agent or automated pipeline from boosting its own picks. This is by design
156
+ * and must not be removed; R-034 only asks that the skip be surfaced to the
157
+ * caller rather than passing silently. Best-effort: a utility-update failure
158
+ * never fails the feedback record.
124
159
  */
125
160
  function recordFeedbackUsage(indexDb, entryId, durableEntryRef, signal, metadataStr) {
126
161
  let utilityResult;
162
+ let rankingUpdateApplied = false;
163
+ let rankingUpdateSkippedReason;
127
164
  const eventSource = resolveUsageEventSource();
128
165
  withStateDb((stateDb) => {
129
166
  insertUsageEvent(stateDb, {
@@ -134,29 +171,33 @@ function recordFeedbackUsage(indexDb, entryId, durableEntryRef, signal, metadata
134
171
  metadata: metadataStr,
135
172
  source: eventSource,
136
173
  });
137
- if (eventSource !== "user")
174
+ if (eventSource !== "user") {
175
+ rankingUpdateSkippedReason =
176
+ `feedback source is "${eventSource}", not "user" — ranking updates only apply to user-attributed ` +
177
+ "feedback (anti-self-reinforcement guard; set AKM_EVENT_SOURCE=user to record as user demand).";
138
178
  return;
179
+ }
139
180
  try {
140
181
  const { pos, neg } = countFeedbackSignals(stateDb, entryId);
141
182
  utilityResult = applyFeedbackToUtilityScore(indexDb, entryId, pos, neg);
183
+ rankingUpdateApplied = true;
142
184
  }
143
- catch {
185
+ catch (err) {
144
186
  // best-effort — feedback recording succeeds even if utility update fails
187
+ rankingUpdateSkippedReason = `utility update failed: ${err instanceof Error ? err.message : String(err)}`;
145
188
  }
146
189
  });
147
- return utilityResult;
190
+ return { utilityResult, rankingUpdateApplied, rankingUpdateSkippedReason };
148
191
  }
149
192
  // ── Command definition ────────────────────────────────────────────────────────
150
193
  export const feedbackCommand = defineJsonCommand({
151
194
  meta: {
152
195
  name: "feedback",
153
- description: "Record positive or negative feedback for any indexed stash asset.\n\n" +
154
- "Positive feedback boosts an asset's EMA utility score, making it rank higher\n" +
155
- "in future searches without requiring a full reindex.\n\n" +
156
- "Negative feedback records a negative signal in usage_events and state.db events.\n" +
157
- "It does NOT immediately lower the asset's ranking — the EMA utility score is\n" +
158
- "updated the next time `akm index` runs (incremental or full). Run `akm index`\n" +
159
- "after recording negative feedback to have it reflected in search results.",
196
+ description: "Record positive or negative feedback for any indexed bundle asset.\n\n" +
197
+ "Both signals adjust the asset's usefulness score right away, in the same\n" +
198
+ "process: positive feedback raises it, negative lowers it, and recent\n" +
199
+ "feedback counts for more than old feedback. No reindex is needed — the new\n" +
200
+ "score affects ranking starting with the very next `akm search`.",
160
201
  },
161
202
  args: {
162
203
  // Optional in citty so run() is invoked even when omitted; we re-validate
@@ -166,8 +207,7 @@ export const feedbackCommand = defineJsonCommand({
166
207
  positive: { type: "boolean", description: "Record positive feedback (boosts ranking immediately)", default: false },
167
208
  negative: {
168
209
  type: "boolean",
169
- description: "Record negative feedback (suppresses ranking after next `akm index`). " +
170
- "Reindexing is required for the signal to affect search results.",
210
+ description: "Record negative feedback (lowers ranking immediately, no reindex needed).",
171
211
  default: false,
172
212
  },
173
213
  reason: {
@@ -176,7 +216,7 @@ export const feedbackCommand = defineJsonCommand({
176
216
  },
177
217
  "failure-mode": {
178
218
  type: "string",
179
- description: `Structured failure-mode taxonomy for negative feedback (F-3 / #384). ` +
219
+ description: "Structured failure-mode taxonomy for negative feedback. " +
180
220
  `Accepted values: ${FEEDBACK_FAILURE_MODES.join(", ")}. ` +
181
221
  "Stored alongside --reason in event metadata for aggregation by the distill pipeline.",
182
222
  },
@@ -188,7 +228,8 @@ export const feedbackCommand = defineJsonCommand({
188
228
  type: "string",
189
229
  description: "Credit a lesson that helped resolve this task. Accepts a `lessons/<name>` ref. " +
190
230
  "When combined with --positive, appends this feedback ref to the target lesson's " +
191
- "`lessonStrength[]` frontmatter array (dedup, idempotent). Ignored on non-lesson targets.",
231
+ "`lessonStrength[]` frontmatter array (dedup, idempotent). A non-lesson target or a " +
232
+ "missing --positive produces a warning rather than silently doing nothing.",
192
233
  },
193
234
  },
194
235
  async run({ args }) {
@@ -196,7 +237,7 @@ export const feedbackCommand = defineJsonCommand({
196
237
  if (!ref) {
197
238
  throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skills/deploy` and either --positive or --negative.");
198
239
  }
199
- const parsedRef = parseRefInput(ref);
240
+ const parsedRef = parseBundleRef(ref);
200
241
  if (args.positive && args.negative) {
201
242
  throw new UsageError("Specify either --positive or --negative, not both.");
202
243
  }
@@ -256,40 +297,42 @@ export const feedbackCommand = defineJsonCommand({
256
297
  // background reindex it spawned — now that ensureIndex is removed, holding
257
298
  // the lock only causes feedback to block for the full improve run duration.
258
299
  let utilityResult;
300
+ let rankingUpdateApplied = false;
301
+ let rankingUpdateSkippedReason;
259
302
  let durableRef = ref;
260
303
  const db = openExistingDatabase();
261
304
  try {
262
305
  const config = loadConfig();
263
306
  const sources = resolveSourceEntries(undefined, config);
264
- const requestedSource = parsedRef.origin
265
- ? sources.find((source) => source.registryId === parsedRef.origin ||
266
- ((parsedRef.origin === "local" || parsedRef.origin === "stash") && source === sources[0]))
267
- : undefined;
268
- if (parsedRef.origin && !requestedSource) {
269
- throw new UsageError(`Source "${parsedRef.origin}" is not configured.`, "INVALID_FLAG_VALUE");
307
+ const requestedSource = parsedRef.bundle ? resolveSourcesForOrigin(parsedRef.bundle, sources)[0] : undefined;
308
+ if (parsedRef.bundle && !requestedSource) {
309
+ throw new UsageError(`Source "${parsedRef.bundle}" is not configured.`, "INVALID_FLAG_VALUE");
270
310
  }
271
- const entryId = findEntryIdByRef(db, ref, requestedSource?.path);
311
+ const lookupRef = makeBundleRef(parsedRef.bundle, parsedRef.conceptId);
312
+ const entryId = findEntryIdByRef(db, lookupRef, requestedSource?.path);
272
313
  if (entryId === undefined) {
273
- throw new UsageError(`Ref "${ref}" is not in the index. ` +
274
- "Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
314
+ // NotFoundError (exit 1), not UsageError (exit 2): the flags parsed
315
+ // fine, the asset just isn't there. The documented exit-code table
316
+ // reserves 1 for "requested resource missing", and scripts branch on it.
317
+ throw new NotFoundError(`Ref "${ref}" is not in the index. ` +
318
+ "Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.", "ASSET_NOT_FOUND");
275
319
  }
276
- // Persist the feedback signal into usage_events. For positive signals,
277
- // the EMA utility score is updated immediately on the next read path.
278
- // For negative signals, the score is adjusted the next time `akm index`
279
- // runs — the signal is durable in the DB but does NOT suppress ranking
280
- // in search results until after reindexing.
281
- const indexedEntry = getEntryById(db, entryId);
282
- const source = sources.find((candidate) => candidate.path === indexedEntry?.stashDir);
283
- const durableOrigin = parsedRef.origin ?? source?.registryId ?? "stash";
320
+ // Persist the feedback signal into usage_events. Both positive and
321
+ // negative signals apply the EMA utility update immediately — no
322
+ // `akm index` run is required for either signal to affect ranking in
323
+ // search results (see recordFeedbackUsage / applyFeedbackToUtilityScore).
284
324
  // WI-8.5b: the `feedback` / `improve_review_needed` events key on the
285
325
  // resolved entry's fully-qualified item_ref — the SAME durable key the
286
326
  // usage_events row carries and the SAME spelling the signal-delta
287
327
  // correlation reads (buildLatestFeedbackTsMap, collapsed to [item_ref]).
288
- // The D-R5 display spelling is the fallback only for a NULL-provenance
289
- // (pre-cutover) row that the one-time re-key has not yet finalized.
290
- const itemRef = getItemRefById(db, entryId) ?? undefined;
291
- durableRef = itemRef ?? displayRef({ type: parsedRef.type, name: parsedRef.name, bundleId: durableOrigin });
292
- utilityResult = recordFeedbackUsage(db, entryId, itemRef, signal, metadataStr);
328
+ const itemRef = getItemRefById(db, entryId);
329
+ if (!itemRef)
330
+ throw new UsageError(`Indexed ref "${ref}" has no durable item ref.`, "INVALID_PROPOSAL");
331
+ durableRef = itemRef;
332
+ const recordResult = recordFeedbackUsage(db, entryId, itemRef, signal, metadataStr);
333
+ utilityResult = recordResult.utilityResult;
334
+ rankingUpdateApplied = recordResult.rankingUpdateApplied;
335
+ rankingUpdateSkippedReason = recordResult.rankingUpdateSkippedReason;
293
336
  }
294
337
  finally {
295
338
  closeDatabase(db);
@@ -305,8 +348,8 @@ export const feedbackCommand = defineJsonCommand({
305
348
  // the improve loop. Best-effort — failure is logged but does not fail the
306
349
  // feedback command.
307
350
  // Emit a structured event rather than a proposal so the review-needed
308
- // signal is queryable via `akm events list --type improve_review_needed`
309
- // without risking accidental asset overwrite if the proposal is accepted.
351
+ // signal doesn't risk an accidental asset overwrite if the proposal is
352
+ // accepted.
310
353
  if (utilityResult?.crossedReviewThreshold) {
311
354
  try {
312
355
  appendEvent({
@@ -327,23 +370,31 @@ export const feedbackCommand = defineJsonCommand({
327
370
  // Phase 7A / Advantage D4b: --applied-to credits a lesson. When the
328
371
  // target is a `lessons/<name>` ref and the signal is positive, append
329
372
  // the feedback ref to the target lesson's `lessonStrength[]`
330
- // frontmatter array (dedup, idempotent). Non-lesson targets are
331
- // ignored. Failures here are warnings — feedback recording is the
332
- // primary contract and must not regress on lesson-write errors.
373
+ // frontmatter array (dedup, idempotent). Non-lesson targets are REJECTED
374
+ // with a loud warning (R-033b) rather than silently doing nothing.
375
+ // Failures here are warnings — feedback recording is the primary
376
+ // contract and must not regress on lesson-write errors.
333
377
  const appliedToRaw = args["applied-to"]?.trim();
334
378
  let appliedToResult = null;
335
379
  if (appliedToRaw && signal === "positive") {
380
+ let parsedApplied;
336
381
  try {
337
- const parsedApplied = parseRefInput(appliedToRaw);
382
+ parsedApplied = parseRefInput(appliedToRaw);
383
+ }
384
+ catch (err) {
385
+ warn(`[feedback] --applied-to failed for ${appliedToRaw}: ${err instanceof Error ? err.message : String(err)}`);
386
+ }
387
+ if (parsedApplied) {
338
388
  if (parsedApplied.type === "lesson") {
339
- const updated = appendLessonStrength(parsedApplied.type, parsedApplied.name, ref);
389
+ const updated = appendLessonStrength(parsedApplied, durableRef);
340
390
  if (updated) {
341
- appliedToResult = { lessonRef: appliedToRaw, strength: updated.strength };
391
+ appliedToResult = { lessonRef: updated.ref, strength: updated.strength };
342
392
  }
343
393
  }
344
- }
345
- catch (err) {
346
- warn(`[feedback] --applied-to failed for ${appliedToRaw}: ${err instanceof Error ? err.message : String(err)}`);
394
+ else {
395
+ warn(`[feedback] --applied-to ${appliedToRaw} was ignored: it resolves to a "${parsedApplied.type}" asset, ` +
396
+ "not a lesson. Only `lessons/<name>` refs can be credited via --applied-to.");
397
+ }
347
398
  }
348
399
  }
349
400
  else if (appliedToRaw && signal !== "positive") {
@@ -356,6 +407,9 @@ export const feedbackCommand = defineJsonCommand({
356
407
  reason: reason?.trim() ?? null,
357
408
  failureMode: failureMode ?? null,
358
409
  tags: validatedTags,
410
+ rankingUpdate: rankingUpdateApplied
411
+ ? { applied: true }
412
+ : { applied: false, reason: rankingUpdateSkippedReason ?? "unknown" },
359
413
  ...(appliedToResult
360
414
  ? { appliedTo: { ref: appliedToResult.lessonRef, lessonStrength: appliedToResult.strength } }
361
415
  : {}),
@@ -0,0 +1,58 @@
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
+ * Per-proposal-source accept-rate metrics, folded into `akm health --report`
6
+ * (0.9.0 CLI overhaul, S3).
7
+ *
8
+ * Provides the core self-measurement metric for recursive self-improvement:
9
+ * if reflect proposals are accepted at 20% and distill proposals at 60%,
10
+ * that guides resource allocation to higher-ROI generators.
11
+ *
12
+ * Previously exposed as the removed `akm history --accept-rate-by-source`
13
+ * flag (src/commands/sources/history.ts, F-4 / #385); `health` already reads
14
+ * the pending proposal queue for its own `--report` dataset, so this is the
15
+ * same read extended to the accepted/rejected archive.
16
+ */
17
+ import { resolveStashDir } from "../../core/common.js";
18
+ import { listProposals } from "../proposal/repository.js";
19
+ /**
20
+ * Compute accept-rate-per-source metrics from the proposal store. Defaults to
21
+ * the configured default stash when `stashDir` is omitted (same resolution
22
+ * `akm health` already uses for the rest of its report).
23
+ */
24
+ export function computeAcceptRateBySource(stashDir) {
25
+ const stash = stashDir ?? resolveStashDir();
26
+ const bySource = new Map();
27
+ const countProposals = (statuses, includeArchive) => {
28
+ for (const status of statuses) {
29
+ const proposals = listProposals(stash, { status, includeArchive });
30
+ for (const p of proposals) {
31
+ const src = p.source || "(unknown)";
32
+ const entry = bySource.get(src) ?? { accepted: 0, rejected: 0, pending: 0 };
33
+ if (status === "accepted")
34
+ entry.accepted++;
35
+ else if (status === "rejected")
36
+ entry.rejected++;
37
+ else
38
+ entry.pending++;
39
+ bySource.set(src, entry);
40
+ }
41
+ }
42
+ };
43
+ countProposals(["pending"], false);
44
+ countProposals(["accepted", "rejected"], true);
45
+ return Array.from(bySource.entries())
46
+ .map(([source, counts]) => {
47
+ const decided = counts.accepted + counts.rejected;
48
+ return {
49
+ source,
50
+ total: decided + counts.pending,
51
+ accepted: counts.accepted,
52
+ rejected: counts.rejected,
53
+ pending: counts.pending,
54
+ acceptRate: decided > 0 ? counts.accepted / decided : null,
55
+ };
56
+ })
57
+ .sort((a, b) => b.total - a.total); // Most active source first
58
+ }
@@ -106,8 +106,7 @@ export function collectImproveAdvisories(db, stateDbPath, since, improveSummary)
106
106
  const latestCycle = getLatestCycleMetrics(db);
107
107
  const cycleSummary = latestCycle
108
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).`
109
+ `distinct-content ratio ${latestCycle.distinct_content_ratio.toFixed(3)}.`
111
110
  : "";
112
111
  if (collapseAlertEvents.length > 0) {
113
112
  const kinds = [...new Set(collapseAlertEvents.map((e) => String(e.metadata?.kind ?? "unknown")))];
@@ -116,8 +115,8 @@ export function collectImproveAdvisories(db, stateDbPath, since, improveSummary)
116
115
  name: "collapse-churn-detector",
117
116
  status: "warn",
118
117
  kind: "deterministic",
119
- // Collapse kinds are measured, not inferred; churn/merge-floor
120
- // volume thresholds are still being tuned (design doc §7).
118
+ // Collapse kinds are measured, not inferred; the merge-floor
119
+ // volume threshold is still being tuned (design doc §7).
121
120
  confidence: collapseKinds.length > 0 ? "high" : "medium",
122
121
  message: `R5 detector fired ${collapseAlertEvents.length} alert(s) in window (kinds: ${kinds.join(", ")}). ` +
123
122
  `${cycleSummary} See docs/architecture/specs/improve-collapse-churn-detector-design.md §6.3 runbook queries.`,
@@ -2,13 +2,12 @@
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 { spawnSync } from "node:child_process";
5
- import { createRequire } from "node:module";
6
5
  import { loadConfig } from "../../core/config/config.js";
6
+ import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
7
7
  import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
8
8
  import { resolveModel } from "../../integrations/agent/model-aliases.js";
9
9
  import { resolveImprovePlan } from "../improve/improve-strategies.js";
10
- import { TASK_FAIL_RATE_WARN, } from "./types.js";
11
- const ACTIVE_RUN_WARN_MS = 15 * 60 * 1000;
10
+ import { ACTIVE_RUN_WARN_MS, TASK_FAIL_RATE_WARN, } from "./types.js";
12
11
  function credentialAvailable(credential, env) {
13
12
  return !credential?.required || credential.names.some((name) => Boolean(env[name]?.trim()));
14
13
  }
@@ -29,7 +28,7 @@ function runConfiguredEngineProbe(checkName, engineName, config, deps) {
29
28
  if (configuredEngine?.kind === "agent" && configuredEngine.platform === "opencode-sdk") {
30
29
  let packageAvailable = false;
31
30
  try {
32
- const resolvePackage = deps.resolvePackage ?? ((name) => createRequire(import.meta.url).resolve(name));
31
+ const resolvePackage = deps.resolvePackage ?? ((name) => import.meta.resolve(name));
33
32
  resolvePackage("@opencode-ai/sdk");
34
33
  packageAvailable = true;
35
34
  }
@@ -171,7 +170,10 @@ function runConfiguredEngineProbe(checkName, engineName, config, deps) {
171
170
  }
172
171
  }
173
172
  export function runDefaultEngineProbe(deps = {}) {
174
- const config = deps.loadConfig?.() ?? loadConfig();
173
+ // Probe the effective view: an install with no `defaults.engine` but a usable
174
+ // opencode binary DOES have a working default, and reporting otherwise would
175
+ // contradict what `workflow run` / `task run` actually do.
176
+ const { config } = withEngineFallback(deps.loadConfig?.() ?? loadConfig(), deps.which);
175
177
  return runConfiguredEngineProbe("default-engine", config.defaults?.engine, config, deps);
176
178
  }
177
179
  export function runDefaultLlmEngineProbe(deps = {}) {
@@ -289,16 +291,23 @@ export const HEALTH_CHECKS = [
289
291
  {
290
292
  name: "active-runs",
291
293
  channel: "hard",
292
- run: (ctx) => ({
293
- name: "active-runs",
294
- kind: "deterministic",
295
- status: ctx.stuckActiveRuns === 0 ? "pass" : "warn",
296
- confidence: "high",
297
- message: ctx.stuckActiveRuns === 0
298
- ? "No active task runs exceeded the stale threshold."
299
- : `${ctx.stuckActiveRuns} active task run(s) are older than ${Math.round(ACTIVE_RUN_WARN_MS / 60000)} minutes.`,
300
- evidence: { stuckActiveRuns: ctx.stuckActiveRuns },
301
- }),
294
+ run: (ctx) => {
295
+ // Name the stuck task_ids (deduped, oldest first) so an operator knows
296
+ // WHICH tasks to investigate, not just how many rows are stale. No
297
+ // pid/liveness detection — that's out of scope here.
298
+ const named = [...ctx.stuckActiveTasks].sort((a, b) => b.ageMs - a.ageMs);
299
+ const detail = named.map((t) => `${t.taskId} (${Math.round(t.ageMs / 60000)}m)`).join(", ");
300
+ return {
301
+ name: "active-runs",
302
+ kind: "deterministic",
303
+ status: ctx.stuckActiveRuns === 0 ? "pass" : "warn",
304
+ confidence: "high",
305
+ message: ctx.stuckActiveRuns === 0
306
+ ? "No active task runs exceeded the stale threshold."
307
+ : `${ctx.stuckActiveRuns} active task run(s) are older than ${Math.round(ACTIVE_RUN_WARN_MS / 60000)} minutes: ${detail}.`,
308
+ evidence: { stuckActiveRuns: ctx.stuckActiveRuns, stuckActiveTasks: ctx.stuckActiveTasks },
309
+ };
310
+ },
302
311
  },
303
312
  {
304
313
  name: "default-engine",
@@ -320,23 +329,51 @@ export const HEALTH_CHECKS = [
320
329
  // in the HTML report but never surfaced as an advisory, so a sustained
321
330
  // 15–16% fail rate stayed invisible on `akm health`. Warn at/above the SAME
322
331
  // 5% threshold the html-report badge uses (see TASK_FAIL_RATE_WARN).
332
+ //
333
+ // Item 6: the aggregate can hide a single consistently-failing task inside
334
+ // a large, mostly-healthy population (e.g. 1 flaky task at 100 rows total
335
+ // reads as <5% aggregate). Also warn when the single worst task_id (with
336
+ // enough rows to be a real signal — see MIN_ROWS_FOR_WORST_TASK_FAIL_RATE)
337
+ // crosses the same threshold, naming it explicitly.
323
338
  name: "task-fail-rate",
324
339
  channel: "advisory",
325
340
  run: (ctx) => {
326
341
  const pctStr = `${(ctx.taskFailRate * 100).toFixed(1)}%`;
327
342
  const thresholdPct = `${(TASK_FAIL_RATE_WARN * 100).toFixed(0)}%`;
328
- const warn = ctx.taskFailRate >= TASK_FAIL_RATE_WARN;
343
+ const aggregateWarn = ctx.taskFailRate >= TASK_FAIL_RATE_WARN;
344
+ const worst = ctx.worstTaskFailRate;
345
+ const worstWarn = worst !== null && worst.rate >= TASK_FAIL_RATE_WARN;
346
+ const warn = aggregateWarn || worstWarn;
347
+ let message;
348
+ if (ctx.taskRowCount === 0) {
349
+ message = `No cron tasks ran since ${ctx.since} — no task-fail-rate signal.`;
350
+ }
351
+ else if (warn) {
352
+ const parts = [];
353
+ if (aggregateWarn) {
354
+ parts.push(`aggregate ${pctStr} across ${ctx.taskRowCount} task(s) since ${ctx.since} ≥ ${thresholdPct}`);
355
+ }
356
+ if (worstWarn && worst) {
357
+ const worstPctStr = `${(worst.rate * 100).toFixed(1)}%`;
358
+ parts.push(`task "${worst.taskId}" fails ${worstPctStr} of its ${worst.rows} run(s) ≥ ${thresholdPct}`);
359
+ }
360
+ message = `Cron task fail rate warning: ${parts.join("; ")} — inspect failed runs (ok=false) for early-exit/harness errors.`;
361
+ }
362
+ else {
363
+ message = `Cron task fail rate ${pctStr} across ${ctx.taskRowCount} task(s) since ${ctx.since} (below ${thresholdPct} threshold).`;
364
+ }
329
365
  return {
330
366
  name: "task-fail-rate",
331
367
  kind: "deterministic",
332
368
  status: warn ? "warn" : "pass",
333
369
  confidence: "high",
334
- message: ctx.taskRowCount === 0
335
- ? `No cron tasks ran since ${ctx.since} — no task-fail-rate signal.`
336
- : warn
337
- ? `Cron task fail rate ${pctStr} across ${ctx.taskRowCount} task(s) since ${ctx.since} ≥ ${thresholdPct} threshold — inspect failed runs (ok=false) for early-exit/harness errors.`
338
- : `Cron task fail rate ${pctStr} across ${ctx.taskRowCount} task(s) since ${ctx.since} (below ${thresholdPct} threshold).`,
339
- evidence: { taskFailRate: ctx.taskFailRate, taskRowCount: ctx.taskRowCount, threshold: TASK_FAIL_RATE_WARN },
370
+ message,
371
+ evidence: {
372
+ taskFailRate: ctx.taskFailRate,
373
+ taskRowCount: ctx.taskRowCount,
374
+ threshold: TASK_FAIL_RATE_WARN,
375
+ worstTaskFailRate: worst,
376
+ },
340
377
  };
341
378
  },
342
379
  },
@@ -354,7 +391,7 @@ export const HEALTH_CHECKS = [
354
391
  const endpointAdvisory = blocked && remoteReason && ctx.embeddingEndpoint
355
392
  ? `Configured embedding endpoint ${ctx.embeddingEndpoint} is failing ` +
356
393
  `(${ctx.semanticStatus?.reason}${ctx.semanticStatus?.message ? `: ${ctx.semanticStatus.message}` : ""}) ` +
357
- `while semanticSearchMode is "${ctx.semanticSearchMode ?? "auto"}". Searches fall back to keyword-only. ` +
394
+ `while semanticSearchMode is "${ctx.semanticSearchMode ?? "off"}". Searches fall back to keyword-only. ` +
358
395
  `Restore the endpoint, or set semanticSearchMode to "off" (or remove embedding.endpoint to use the local model).`
359
396
  : undefined;
360
397
  return {
@@ -489,4 +526,29 @@ export const HEALTH_CHECKS = [
489
526
  };
490
527
  },
491
528
  },
529
+ {
530
+ // Item 4: stale durable-transaction journals under $DATA/txn (stranded
531
+ // recovery state seen twice in a real 0.9 migration) had zero health
532
+ // visibility. `count`/`unreadable` already exclude journals younger than
533
+ // the sweeper's grace period (a currently-running operation), so any
534
+ // nonzero count here is a real leftover.
535
+ name: "stale-txn-journals",
536
+ channel: "advisory",
537
+ run: (ctx) => {
538
+ const s = ctx.staleTxnJournals;
539
+ const warn = s.count > 0;
540
+ const unreadablePart = s.unreadable > 0 ? `, ${s.unreadable} unreadable` : "";
541
+ const agePart = s.oldestAgeMs !== null ? `, oldest ${Math.round(s.oldestAgeMs / 60000)}m old` : "";
542
+ return {
543
+ name: "stale-txn-journals",
544
+ kind: "deterministic",
545
+ status: warn ? "warn" : "pass",
546
+ confidence: "high",
547
+ message: warn
548
+ ? `${s.count} stale transaction journal(s) found under ${s.dir}${unreadablePart}${agePart} — see docs/migration/v0.9.0-troubleshooting.md for journal reconciliation steps.`
549
+ : "No stale transaction journals found.",
550
+ evidence: { dir: s.dir, count: s.count, unreadable: s.unreadable, oldestAgeMs: s.oldestAgeMs },
551
+ };
552
+ },
553
+ },
492
554
  ];