akm-cli 0.9.16 → 0.9.17-alpha.10

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 (403) hide show
  1. package/CHANGELOG.md +2101 -0
  2. package/STABILITY.md +11 -10
  3. package/dist/akm +124 -193
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/prompts/consolidate-system.md +4 -11
  15. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  17. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  18. package/dist/assets/templates/html/health.html +3 -5
  19. package/dist/cli/retired-commands.js +1 -1
  20. package/dist/cli/shared.js +6 -2
  21. package/dist/cli/unknown-flags.js +24 -1
  22. package/dist/cli.js +68 -10
  23. package/dist/commands/agent/agent-dispatch.js +1 -1
  24. package/dist/commands/command/command-execution.js +24 -62
  25. package/dist/commands/feedback-cli.js +0 -1
  26. package/dist/commands/health/accept-rate.js +6 -0
  27. package/dist/commands/health/archive-usage.js +92 -0
  28. package/dist/commands/health/checks.js +83 -74
  29. package/dist/commands/health/config-skew.js +38 -0
  30. package/dist/commands/health/data-dir-usage.js +25 -13
  31. package/dist/commands/health/egress.js +54 -0
  32. package/dist/commands/health/html-report.js +1 -42
  33. package/dist/commands/health/improve-metrics.js +136 -591
  34. package/dist/commands/health/md-report.js +1 -6
  35. package/dist/commands/health/plugin-staleness.js +53 -3
  36. package/dist/commands/health/renderers.js +12 -4
  37. package/dist/commands/health/report-view-model.js +14 -120
  38. package/dist/commands/health/types-improve.js +4 -19
  39. package/dist/commands/health/windows.js +64 -74
  40. package/dist/commands/health.js +145 -143
  41. package/dist/commands/improve/consolidate/chunking.js +26 -117
  42. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  43. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  44. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  45. package/dist/commands/improve/consolidate.js +589 -1127
  46. package/dist/commands/improve/content-hash.js +16 -24
  47. package/dist/commands/improve/distill/content-repair.js +18 -100
  48. package/dist/commands/improve/distill-guards.js +20 -81
  49. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  50. package/dist/commands/improve/distill.js +608 -1041
  51. package/dist/commands/improve/eligibility.js +126 -390
  52. package/dist/commands/improve/execution.js +8 -10
  53. package/dist/commands/improve/extract-prompt.js +1 -2
  54. package/dist/commands/improve/extract.js +487 -1046
  55. package/dist/commands/improve/feedback-valence.js +0 -25
  56. package/dist/commands/improve/improve-cli.js +75 -169
  57. package/dist/commands/improve/improve-result-file.js +10 -66
  58. package/dist/commands/improve/improve-strategies.js +52 -4
  59. package/dist/commands/improve/improve-usage-report.js +18 -64
  60. package/dist/commands/improve/improve.js +480 -1074
  61. package/dist/commands/improve/ledger.js +119 -0
  62. package/dist/commands/improve/locks.js +2 -8
  63. package/dist/commands/improve/loop-stages.js +415 -1073
  64. package/dist/commands/improve/memory/derived-ref.js +12 -77
  65. package/dist/commands/improve/memory/memory-belief.js +16 -118
  66. package/dist/commands/improve/memory/memory-improve.js +266 -14
  67. package/dist/commands/improve/outcome-loop.js +28 -156
  68. package/dist/commands/improve/planner.js +5 -15
  69. package/dist/commands/improve/preparation.js +779 -2319
  70. package/dist/commands/improve/proactive-maintenance.js +34 -101
  71. package/dist/commands/improve/reflect-noise.js +104 -280
  72. package/dist/commands/improve/reflect.js +642 -1353
  73. package/dist/commands/improve/retrieval-gate.js +127 -0
  74. package/dist/commands/improve/retrieval-scope.js +92 -0
  75. package/dist/commands/improve/salience.js +41 -240
  76. package/dist/commands/improve/session-asset.js +19 -100
  77. package/dist/commands/improve/stage.js +322 -0
  78. package/dist/commands/lint/base-linter.js +37 -15
  79. package/dist/commands/proposal/drain.js +261 -578
  80. package/dist/commands/proposal/proposal-cli.js +19 -20
  81. package/dist/commands/proposal/proposal-types.js +31 -24
  82. package/dist/commands/proposal/proposal.js +38 -8
  83. package/dist/commands/proposal/propose.js +134 -160
  84. package/dist/commands/proposal/repository.js +1097 -1394
  85. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  86. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  87. package/dist/commands/proposal/validators/proposals.js +22 -89
  88. package/dist/commands/read/curate.js +105 -462
  89. package/dist/commands/read/knowledge.js +3 -2
  90. package/dist/commands/read/search-cli.js +16 -33
  91. package/dist/commands/read/search.js +17 -23
  92. package/dist/commands/read/show.js +57 -108
  93. package/dist/commands/sources/bundle-cli.js +25 -2
  94. package/dist/commands/sources/bundle-config-ops.js +4 -0
  95. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  96. package/dist/commands/sources/info.js +127 -29
  97. package/dist/commands/sources/installed-stashes.js +197 -746
  98. package/dist/commands/sources/schema-repair.js +98 -129
  99. package/dist/commands/sources/source-add.js +62 -12
  100. package/dist/commands/sources/source-manage.js +9 -2
  101. package/dist/commands/sources/stash-cli.js +24 -4
  102. package/dist/commands/tasks/explain.js +10 -13
  103. package/dist/commands/tasks/tasks-cli.js +12 -13
  104. package/dist/commands/tasks/tasks.js +350 -936
  105. package/dist/commands/tasks/validate.js +26 -24
  106. package/dist/commands/workflow/plan.js +22 -29
  107. package/dist/commands/workflow-cli.js +4 -4
  108. package/dist/core/adapter/adapters/akm-adapter.js +2 -1
  109. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  110. package/dist/core/adapter/adapters/akm-metadata.js +42 -12
  111. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  112. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  113. package/dist/core/adapter/execution-source.js +17 -29
  114. package/dist/core/asset/asset-placement.js +4 -13
  115. package/dist/core/asset/frontmatter.js +106 -1
  116. package/dist/core/asset/resolve-ref.js +1 -1
  117. package/dist/core/bundle-id.js +42 -5
  118. package/dist/core/bundle-rename.js +285 -0
  119. package/dist/core/config/config-io.js +1 -2
  120. package/dist/core/config/config-schema.js +9 -34
  121. package/dist/core/config/config-walker.js +1 -1
  122. package/dist/core/config/config.js +184 -111
  123. package/dist/core/config/engine-semantics.js +0 -2
  124. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  125. package/dist/core/config/schema/embedding.js +20 -5
  126. package/dist/core/config/schema/engines.js +5 -0
  127. package/dist/core/config/schema/execution.js +1 -1
  128. package/dist/core/config/schema/experimental.js +1 -1
  129. package/dist/core/config/schema/improve-processes.js +54 -125
  130. package/dist/core/config/schema/improve.js +4 -42
  131. package/dist/core/config/schema/index-config.js +9 -48
  132. package/dist/core/config/schema/scheduler.js +12 -12
  133. package/dist/core/config/schema/search.js +6 -22
  134. package/dist/core/env-secret-ref.js +0 -1
  135. package/dist/core/errors.js +8 -9
  136. package/dist/core/file-change.js +13 -5
  137. package/dist/core/file-lock.js +76 -173
  138. package/dist/core/improve-result.js +35 -7
  139. package/dist/core/improve-types.js +0 -1
  140. package/dist/core/logs-db.js +2 -2
  141. package/dist/core/loopback.js +7 -12
  142. package/dist/core/non-task-input.js +20 -0
  143. package/dist/core/parse.js +13 -16
  144. package/dist/core/paths.js +0 -24
  145. package/dist/core/redaction.js +109 -2
  146. package/dist/core/run-lock.js +2 -5
  147. package/dist/core/spawn-env.js +1 -1
  148. package/dist/core/state/migrations.js +123 -61
  149. package/dist/core/state-db-scope.js +2 -4
  150. package/dist/core/state-db.js +126 -692
  151. package/dist/core/time.js +0 -20
  152. package/dist/core/type-presentation.js +1 -9
  153. package/dist/core/write-source.js +294 -1005
  154. package/dist/execution/input-contract.js +1 -1
  155. package/dist/execution/resolved-request.js +135 -689
  156. package/dist/execution/source.js +63 -257
  157. package/dist/execution/target-ref.js +1 -1
  158. package/dist/indexer/bundle-identity-guard.js +2 -2
  159. package/dist/indexer/db/llm-cache.js +2 -2
  160. package/dist/indexer/ensure-index.js +77 -73
  161. package/dist/indexer/index-rebuild-lock.js +3 -11
  162. package/dist/indexer/index-writer-lock.js +8 -17
  163. package/dist/indexer/index-written-assets.js +141 -154
  164. package/dist/indexer/indexer.js +400 -1124
  165. package/dist/indexer/links/declared-links.js +90 -0
  166. package/dist/indexer/materialize-embeddings.js +60 -397
  167. package/dist/indexer/passes/memory-inference.js +96 -90
  168. package/dist/indexer/passes/metadata.js +132 -219
  169. package/dist/indexer/read-preflight.js +0 -7
  170. package/dist/indexer/scan/doc-to-entry.js +2 -3
  171. package/dist/indexer/scan/drain-dir.js +1 -1
  172. package/dist/indexer/search/db-search.js +190 -590
  173. package/dist/indexer/search/fts-query.js +30 -41
  174. package/dist/indexer/search/ranking.js +28 -154
  175. package/dist/indexer/search/search-attribution.js +12 -32
  176. package/dist/indexer/search/search-fields.js +11 -15
  177. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  178. package/dist/indexer/search/search-source.js +1 -4
  179. package/dist/indexer/usage/usage-events.js +36 -7
  180. package/dist/indexer/walk/walker.js +3 -4
  181. package/dist/integrations/agent/engine-fallback.js +23 -40
  182. package/dist/integrations/agent/engine-resolution.js +93 -183
  183. package/dist/integrations/agent/execution.js +507 -0
  184. package/dist/integrations/agent/model-map.js +28 -156
  185. package/dist/integrations/agent/request-lowering.js +66 -141
  186. package/dist/integrations/agent/runner-dispatch.js +143 -321
  187. package/dist/integrations/agent/runner.js +54 -14
  188. package/dist/integrations/lockfile.js +53 -101
  189. package/dist/llm/client.js +18 -6
  190. package/dist/llm/embedders/deterministic.js +2 -3
  191. package/dist/llm/embedders/profile.js +71 -0
  192. package/dist/llm/embedders/remote.js +11 -17
  193. package/dist/llm/feature-gate.js +0 -8
  194. package/dist/llm/index-passes.js +3 -5
  195. package/dist/llm/memory-infer.js +1 -2
  196. package/dist/llm/structured-call.js +5 -24
  197. package/dist/output/generic-render.js +23 -11
  198. package/dist/output/html-render.js +13 -10
  199. package/dist/output/render-registry.js +3 -32
  200. package/dist/output/shapes/helpers.js +25 -38
  201. package/dist/output/shapes/passthrough.js +1 -9
  202. package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
  203. package/dist/output/text/command-format.js +69 -31
  204. package/dist/output/text/helpers.js +1 -1
  205. package/dist/output/text/migrate.js +5 -14
  206. package/dist/output/text/proposal-format.js +48 -3
  207. package/dist/output/text/show-format.js +13 -17
  208. package/dist/output/text/workflow-format.js +0 -32
  209. package/dist/output/text.js +2 -0
  210. package/dist/registry/factory.js +4 -19
  211. package/dist/registry/network.js +66 -220
  212. package/dist/registry/providers/index.js +0 -2
  213. package/dist/registry/providers/skills-sh.js +3 -14
  214. package/dist/registry/providers/static-index.js +24 -26
  215. package/dist/registry/resolve.js +55 -131
  216. package/dist/scripts/akm-migrate-node.js +42948 -92369
  217. package/dist/scripts/akm-migrate.js +42935 -92354
  218. package/dist/setup/registry-stash-loader.js +4 -13
  219. package/dist/setup/semantic-assets.js +3 -44
  220. package/dist/setup/setup.js +1 -1
  221. package/dist/setup/steps/connection.js +5 -6
  222. package/dist/setup/steps/platforms.js +2 -2
  223. package/dist/setup/steps/tasks.js +25 -15
  224. package/dist/sources/provider-factory.js +17 -18
  225. package/dist/sources/providers/filesystem.js +2 -3
  226. package/dist/sources/providers/git-install.js +7 -1
  227. package/dist/sources/providers/git-provider.js +0 -3
  228. package/dist/sources/providers/git-stash.js +83 -21
  229. package/dist/sources/providers/npm.js +2 -4
  230. package/dist/sources/providers/provider-utils.js +5 -10
  231. package/dist/sources/providers/website.js +0 -2
  232. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  233. package/dist/sources/website-url.js +2 -2
  234. package/dist/storage/database.js +9 -35
  235. package/dist/storage/repositories/improve-ledger-repository.js +209 -0
  236. package/dist/storage/repositories/index-connection.js +39 -72
  237. package/dist/storage/repositories/index-entries-repository.js +131 -129
  238. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  239. package/dist/storage/repositories/index-entry-schema.js +101 -268
  240. package/dist/storage/repositories/index-fts-repository.js +86 -256
  241. package/dist/storage/repositories/index-links-repository.js +143 -0
  242. package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
  243. package/dist/storage/repositories/index-meta-repository.js +6 -4
  244. package/dist/storage/repositories/index-schema.js +257 -325
  245. package/dist/storage/repositories/index-utility-repository.js +8 -29
  246. package/dist/storage/repositories/index-vec-repository.js +133 -414
  247. package/dist/storage/repositories/outcome-repository.js +2 -1
  248. package/dist/storage/repositories/proposals-repository.js +104 -1
  249. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  250. package/dist/storage/repositories/salience-repository.js +1 -19
  251. package/dist/storage/repositories/task-history-repository.js +26 -4
  252. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  253. package/dist/storage/sqlite-migrations.js +136 -0
  254. package/dist/storage/sqlite-pragmas.js +11 -9
  255. package/dist/storage/sqlite-transaction.js +170 -0
  256. package/dist/storage/state-db-integrity.js +130 -0
  257. package/dist/tasks/activation-config.js +134 -62
  258. package/dist/tasks/backends/cron.js +191 -302
  259. package/dist/tasks/backends/exec-utils.js +2 -5
  260. package/dist/tasks/backends/launchd.js +141 -748
  261. package/dist/tasks/backends/schtasks.js +119 -623
  262. package/dist/tasks/prepare/prepare-support.js +5 -15
  263. package/dist/tasks/prepare/prepare.js +0 -2
  264. package/dist/tasks/resolve-akm-bin.js +20 -79
  265. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  266. package/dist/tasks/run/load-task.js +1 -1
  267. package/dist/tasks/scheduler-binding.js +20 -238
  268. package/dist/tasks/scheduler-invocation.js +136 -244
  269. package/dist/tasks/scheduler-lock.js +53 -0
  270. package/dist/tasks/scheduler-sync.js +368 -679
  271. package/dist/tasks/source/parse-task-source.js +55 -9
  272. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  273. package/dist/tasks/source/task-to-v4.js +464 -88
  274. package/dist/workflows/authoring/authoring.js +3 -12
  275. package/dist/workflows/compile.js +211 -0
  276. package/dist/workflows/concurrency-policy.js +13 -74
  277. package/dist/workflows/exec/child-invocation.js +3 -17
  278. package/dist/workflows/exec/child-workflow.js +32 -141
  279. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  280. package/dist/workflows/exec/environment.js +98 -0
  281. package/dist/workflows/exec/exec-unit.js +33 -140
  282. package/dist/workflows/exec/frozen-judge.js +7 -59
  283. package/dist/workflows/exec/native-executor.js +82 -341
  284. package/dist/workflows/exec/param-secrets.js +29 -47
  285. package/dist/workflows/exec/run-workflow.js +154 -387
  286. package/dist/workflows/exec/scheduler.js +9 -36
  287. package/dist/workflows/exec/step-work.js +127 -430
  288. package/dist/workflows/exec/unit-dispatch.js +11 -63
  289. package/dist/workflows/exec/unit-writer.js +8 -52
  290. package/dist/workflows/exec/worktree.js +39 -273
  291. package/dist/workflows/freeze/child-output-references.js +4 -15
  292. package/dist/workflows/freeze/environment.js +99 -92
  293. package/dist/workflows/freeze/freeze.js +172 -0
  294. package/dist/workflows/freeze/step-values.js +19 -21
  295. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  296. package/dist/workflows/freeze/targets/command.js +10 -33
  297. package/dist/workflows/freeze/targets/script.js +5 -12
  298. package/dist/workflows/freeze/targets/shell.js +3 -6
  299. package/dist/workflows/freeze/targets/task.js +25 -80
  300. package/dist/workflows/freeze/task-bindings.js +20 -67
  301. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  302. package/dist/workflows/ir/params.js +6 -51
  303. package/dist/workflows/ir/plan-hash.js +2 -34
  304. package/dist/workflows/parser.js +140 -43
  305. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  306. package/dist/workflows/renderer.js +36 -69
  307. package/dist/workflows/resource-limits.js +12 -120
  308. package/dist/workflows/runtime/agent-identity.js +8 -40
  309. package/dist/workflows/runtime/run-outputs.js +3 -6
  310. package/dist/workflows/runtime/run-plan.js +316 -0
  311. package/dist/workflows/runtime/runs.js +48 -200
  312. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  313. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  314. package/dist/workflows/validate-summary.js +2 -7
  315. package/docs/integration/bundling-akm.md +49 -42
  316. package/docs/migration/README.md +1 -0
  317. package/docs/migration/release-notes/0.9.17.md +43 -0
  318. package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
  319. package/docs/reference/cli.md +232 -135
  320. package/docs/reference/configuration.md +71 -57
  321. package/docs/reference/data-and-telemetry.md +20 -21
  322. package/docs/reference/tasks.md +105 -39
  323. package/docs/reference/workflow-schema.md +14 -18
  324. package/docs/reference/workflows.md +6 -9
  325. package/package.json +1 -1
  326. package/schemas/akm-config.json +115 -738
  327. package/schemas/akm-workflow.json +1 -0
  328. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  329. package/dist/assets/prompts/contradiction-judge.md +0 -33
  330. package/dist/assets/prompts/graph-extract-system.md +0 -1
  331. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  332. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  333. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  334. package/dist/commands/health/advisories.js +0 -150
  335. package/dist/commands/health/metrics.js +0 -329
  336. package/dist/commands/health/surfaces.js +0 -102
  337. package/dist/commands/improve/anti-collapse.js +0 -83
  338. package/dist/commands/improve/collapse-detector.js +0 -432
  339. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  340. package/dist/commands/improve/consolidate/merge.js +0 -149
  341. package/dist/commands/improve/distill/promote-memory.js +0 -291
  342. package/dist/commands/improve/distill/quality-gate.js +0 -337
  343. package/dist/commands/improve/eval-cases.js +0 -52
  344. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  345. package/dist/commands/improve/proposal-envelope.js +0 -31
  346. package/dist/commands/improve/run-context.js +0 -123
  347. package/dist/commands/improve/shared.js +0 -31
  348. package/dist/commands/improve/source-identity.js +0 -28
  349. package/dist/commands/improve/triage.js +0 -96
  350. package/dist/commands/proposal/drain-policies.js +0 -151
  351. package/dist/commands/sources/update-transaction.js +0 -220
  352. package/dist/core/action-contributors.js +0 -28
  353. package/dist/core/config/config-version-shim.js +0 -101
  354. package/dist/core/fs-txn.js +0 -405
  355. package/dist/core/lexical-score.js +0 -25
  356. package/dist/core/maintenance-barrier.js +0 -167
  357. package/dist/execution/executable-identity.js +0 -105
  358. package/dist/execution/guarded-source.js +0 -427
  359. package/dist/indexer/db/graph-db.js +0 -444
  360. package/dist/indexer/graph/graph-boost.js +0 -427
  361. package/dist/indexer/graph/graph-dedup.js +0 -95
  362. package/dist/indexer/graph/graph-extraction.js +0 -1108
  363. package/dist/indexer/search/name-match.js +0 -35
  364. package/dist/indexer/search/ranking-contributors.js +0 -515
  365. package/dist/indexer/search/ranking-types.js +0 -4
  366. package/dist/indexer/walk/project-context.js +0 -192
  367. package/dist/integrations/agent/execution-cascade.js +0 -566
  368. package/dist/integrations/agent/execution-definitions.js +0 -202
  369. package/dist/integrations/agent/execution-lowering.js +0 -841
  370. package/dist/integrations/agent/execution-preparation.js +0 -98
  371. package/dist/integrations/agent/inline-execution.js +0 -74
  372. package/dist/llm/graph-extract.js +0 -728
  373. package/dist/llm/metadata-enhance.js +0 -96
  374. package/dist/registry/create-provider-registry.js +0 -29
  375. package/dist/registry/pinned-request-helper.js +0 -247
  376. package/dist/registry/pinned-transport.js +0 -717
  377. package/dist/sources/providers/index.js +0 -14
  378. package/dist/storage/engines/sqlite-migrations.js +0 -271
  379. package/dist/storage/repositories/canaries-repository.js +0 -107
  380. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  381. package/dist/storage/repositories/registry-cache.js +0 -113
  382. package/dist/tasks/scheduler-sync-preview.js +0 -52
  383. package/dist/tasks/source/task-to-v3.js +0 -507
  384. package/dist/workflows/freeze/resolve-steps.js +0 -86
  385. package/dist/workflows/freeze/source-freeze.js +0 -64
  386. package/dist/workflows/ir/compile.js +0 -321
  387. package/dist/workflows/ir/environment-v4.js +0 -330
  388. package/dist/workflows/ir/freeze-v4.js +0 -153
  389. package/dist/workflows/ir/schema-v4.js +0 -745
  390. package/dist/workflows/ir/schema.js +0 -354
  391. package/dist/workflows/program/schema.js +0 -77
  392. package/dist/workflows/runtime/checkin.js +0 -57
  393. package/dist/workflows/runtime/plan-classifier.js +0 -196
  394. package/dist/workflows/runtime/unit-checkin.js +0 -45
  395. package/dist/workflows/runtime/unit-phases.js +0 -20
  396. package/dist/workflows/schema.js +0 -4
  397. package/dist/workflows/source-ir/compile.js +0 -200
  398. package/dist/workflows/source-ir/program.js +0 -50
  399. package/dist/workflows/source-ir/result.js +0 -26
  400. package/dist/workflows/source-ir/schema.js +0 -786
  401. package/dist/workflows/source-ir/triggers.js +0 -79
  402. package/dist/workflows/source-ir/uses.js +0 -40
  403. package/dist/workflows/validator.js +0 -60
@@ -1,50 +1,39 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
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
- const UNICODE_TOKEN = /[\p{L}\p{N}]+/gu;
5
- function quoteToken(token) {
6
- return `"${token}"`;
7
- }
8
- function prefixToken(token) {
9
- return [...token].length >= 3 ? `${quoteToken(token)}*` : quoteToken(token);
10
- }
11
4
  /**
12
- * Build the sole lexical retrieval plan from raw user input.
5
+ * Pure FTS5 query building and ref-query helpers.
13
6
  *
14
- * Tokenization follows the useful portion of SQLite FTS5's `unicode61`
15
- * tokenizer (Unicode letters and numbers). Quoting every term makes FTS
16
- * operators ordinary searchable words. Tokens are normalized and deduplicated
17
- * case-insensitively.
18
- *
19
- * There is deliberately NO cap on token count. `MAX_LEXICAL_QUERY_TOKENS = 16`
20
- * used to truncate here, silently: a query past 16 unique tokens searched only
21
- * its first 16, dropping the tail — which for natural-language input is
22
- * usually where the discriminating words are. It was unexplained in both the
23
- * code and the commit that introduced it, unreachable from any flag, config
24
- * key, or env var, and the user was never told their query had been altered.
25
- * A wrong answer delivered silently is worse than a slow one.
7
+ * The lexical channel matches ANY query word: BM25 over an OR of the query's
8
+ * non-stopword tokens. Requiring every word first and relaxing only when that
9
+ * found nothing cost 0.108 nDCG@10 on the retrieval suite
10
+ * (`akm/eval/retrieval/reports/baseline-2026-09-27.md`): one conjunctive match
11
+ * in a long document suppressed every better OR candidate.
12
+ * `parseRefPrefixQuery` is the one non-FTS helper: it decides whether a raw
13
+ * query should bypass FTS entirely (SPEC-4 ref-prefix enumeration).
26
14
  */
27
- export function buildLexicalQueryPlan(query) {
28
- const tokens = [];
29
- const seen = new Set();
30
- const normalized = query.normalize("NFKC");
31
- for (const match of normalized.matchAll(UNICODE_TOKEN)) {
32
- const token = match[0];
33
- const key = token.toLowerCase();
34
- if (seen.has(key))
35
- continue;
36
- seen.add(key);
37
- tokens.push(token);
38
- }
39
- const exact = tokens.map(quoteToken).join(" ");
40
- const prefixTokens = tokens.map(prefixToken);
41
- const exactPrefix = prefixTokens.some((token) => token.endsWith("*")) ? prefixTokens.join(" ") : undefined;
42
- // A slash-bearing, whitespace-free input is an identifier/ref lookup, not
43
- // sentence prose. Keep it conjunctive so a mistyped/bare ref never fans out
44
- // across every path token through OR recovery.
45
- const isRefLikeIdentifier = !/\s/u.test(query.trim()) && query.includes("/");
46
- const relaxed = tokens.length > 1 && !isRefLikeIdentifier ? prefixTokens.join(" OR ") : undefined;
47
- return { tokens, exact, exactPrefix, relaxed };
15
+ /** English function words dropped from the lexical query (the retrieval lab's list). */
16
+ const STOPWORDS = new Set(`a about above after again against all am an and any are as at be because been before being below between both but
17
+ by can could did do does doing down during each few for from further had has have having he her here hers herself
18
+ him himself his how i if in into is it its itself just me more most my myself no nor not now of off on once only or
19
+ other our ours out over own same she should so some such than that the their theirs them then there these they this
20
+ those through to too under until up very was we were what when where which while who whom why will with would you
21
+ your yours yourself`.split(/\s+/));
22
+ const UNICODE_TOKEN = /[\p{L}\p{N}]+/gu;
23
+ /**
24
+ * The query's lexical tokens: Unicode letters and numbers (the useful part of
25
+ * FTS5's `unicode61` tokenizer), NFKC-normalized, lowercased and deduplicated,
26
+ * with stopwords removed. A query made only of stopwords keeps all of them, so
27
+ * "how to" still searches for something.
28
+ */
29
+ export function ftsQueryTokens(query) {
30
+ const tokens = [...new Set(query.normalize("NFKC").toLowerCase().match(UNICODE_TOKEN) ?? [])];
31
+ const content = tokens.filter((token) => !STOPWORDS.has(token));
32
+ return content.length > 0 ? content : tokens;
33
+ }
34
+ /** FTS5 MATCH expression matching any token. Quoting makes FTS operators ordinary words. */
35
+ export function ftsOrMatch(tokens) {
36
+ return tokens.map((token) => `"${token}"`).join(" OR ");
48
37
  }
49
38
  /**
50
39
  * D4 — parse a conceptId-prefix browse query.
@@ -1,163 +1,37 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
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
- import { stableFtsScore } from "../../core/lexical-score.js";
5
- import { getUtilityScoresByIds } from "../../storage/repositories/index-utility-repository.js";
6
- import { buildLexicalQueryPlan } from "./fts-query.js";
7
- import { lexicalNameTokens, structuralNameTokenMatch } from "./name-match.js";
8
- import { applyBeliefStateScoreCeiling, applyScoreContributors, applyUtilityContributors, defaultRankingContributors, defaultUtilityRankingContributors, } from "./ranking-contributors.js";
9
4
  /**
10
- * Lower bounds keep a lexical hit competitive with a vector-only neighbour;
11
- * the upper bound deliberately leaves room for the ranking contributors that
12
- * run after retrieval (notably the bounded graph boost). This is a
13
- * calibration for the one search pipeline, not a claim that BM25 is
14
- * comparable across different queries or FTS tables.
15
- */
16
- /**
17
- * Convert FTS5's negative BM25 value into the lexical contribution used by
18
- * this pipeline. The transform is fixed and monotone: it depends only on a
19
- * row's own BM25 value, so appending weaker candidates cannot rewrite an
20
- * existing row's score. FTS5 commonly emits relevance near 1e-6 for broad
21
- * queries, so first put relevance on a log scale around that observed value.
22
- * The shape constant intentionally makes the curve approach its ceiling
23
- * slowly: rare-term scores retain separation instead of all reading as 0.8.
5
+ * Reciprocal rank fusion (Cormack, Clarke and Büttcher, SIGIR 2009) of the
6
+ * lexical and vector candidate lists — the whole of search ranking.
24
7
  *
25
- * FTS5 produces finite non-positive values in normal operation. Keeping the
26
- * defensive cases here finite makes this boundary safe if a driver or fixture
27
- * hands us an invalid value: `-Infinity` is the strongest possible match,
28
- * while NaN, +Infinity, and positive scores contribute no lexical evidence.
29
- */
30
- export function normalizeFtsScores(results) {
31
- const ftsScoreMap = new Map();
32
- for (const result of results) {
33
- ftsScoreMap.set(result.id, { score: result.lexicalScore ?? stableFtsScore(result.bm25Score), result });
34
- }
35
- return ftsScoreMap;
36
- }
37
- export function combineSearchScores(options) {
38
- const FTS_WEIGHT = 0.7;
39
- const VEC_WEIGHT = 0.3;
40
- const excludeTypeSet = options.excludeTypes && options.excludeTypes.length > 0 ? new Set(options.excludeTypes) : null;
41
- const scored = [];
42
- const seenIds = new Set();
43
- for (const [id, { score: ftsScore, result }] of options.ftsScoreMap) {
44
- seenIds.add(id);
45
- const embedScore = options.embedScoreMap.get(id);
46
- const combinedScore = embedScore !== undefined ? ftsScore * FTS_WEIGHT + embedScore * VEC_WEIGHT : ftsScore;
47
- scored.push({
48
- id,
49
- entry: result.entry,
50
- filePath: result.filePath,
51
- score: combinedScore,
52
- rankingMode: embedScore !== undefined ? "hybrid" : "fts",
53
- lexicalMatch: result.lexicalMatch,
54
- itemRef: result.itemRef,
55
- bundleId: result.bundleId,
56
- conceptId: result.conceptId,
57
- fragmentId: result.fragmentId,
58
- });
59
- }
60
- for (const [id, cosine] of options.embedScoreMap) {
61
- if (seenIds.has(id))
62
- continue;
63
- const found = options.getEntryById(id);
64
- if (!found)
65
- continue;
66
- if (options.typeFilter && found.entry.type !== options.typeFilter)
67
- continue;
68
- // #627 — drop vector-only neighbors whose type is excluded on the default path.
69
- if (excludeTypeSet?.has(found.entry.type))
70
- continue;
71
- scored.push({
72
- id,
73
- entry: found.entry,
74
- filePath: found.filePath,
75
- score: cosine * VEC_WEIGHT,
76
- rankingMode: "semantic",
77
- itemRef: found.itemRef,
78
- bundleId: found.bundleId,
79
- conceptId: found.conceptId,
80
- });
81
- }
82
- return scored;
83
- }
84
- export function applyRankingRules(options) {
85
- const queryTokens = buildLexicalQueryPlan(options.query).tokens.map((token) => token.toLowerCase());
86
- const queryLower = options.query.toLowerCase().trim();
87
- const rankingContext = {
88
- db: options.db,
89
- query: options.query,
90
- queryLower,
91
- queryTokens,
92
- graphContext: options.graphContext,
93
- projectContext: options.projectContext,
94
- };
95
- for (const item of options.items) {
96
- applyScoreContributors(item, rankingContext, defaultRankingContributors);
97
- }
98
- const { global: utilScoresMap, scoped: scopedUtilScoresMap } = getUtilityScoresByIds(options.db, options.items.map((item) => item.id), options.scopeKey);
99
- // R2 / #692 — salience-ranking is not in defaultUtilityRankingContributors
100
- // (see ranking-contributors.ts), so this is never consumed by the default
101
- // ranking path below; it exists only for a caller that explicitly builds a
102
- // contributor list including salienceRankingContributor. undefined/null
103
- // both normalize to "no data" — there is no state.db fallback load.
104
- const salienceRankScores = options.salienceRankScores ?? new Map();
105
- const utilityContext = {
106
- ...rankingContext,
107
- utilityScores: utilScoresMap,
108
- scopedUtilityScores: scopedUtilScoresMap,
109
- utilityDecayConfig: options.utilityDecayConfig,
110
- positiveFeedbackCounts: options.positiveFeedbackCounts,
111
- salienceRankScores,
112
- };
113
- for (const item of options.items) {
114
- applyUtilityContributors(item, utilityContext, defaultUtilityRankingContributors);
115
- applyRelaxedLexicalScoreCeiling(item, queryTokens);
116
- // SPEC-5: demoting belief states (superseded/contradicted/archived/
117
- // deprecated) cap the FINAL score. The additive belief penalty inside the
118
- // multiplicative boost sum can still overwhelm an additive belief penalty,
119
- // so without the ceiling a superseded incumbent can outrank its correction.
120
- applyBeliefStateScoreCeiling(item);
121
- }
122
- return options.items;
123
- }
124
- const RELAXED_NON_NAME_SCORE_CEILING = 0.65;
125
- /**
126
- * Rank name evidence without relying on punctuation or ASCII-only splitting.
127
- * The tiers are intentionally structural: an exact normalized name, all query
128
- * tokens in a longer name, any query token in the name, or no name evidence.
8
+ * Fusing ranks rather than scores needs no calibration between BM25 and cosine
9
+ * similarity, so it holds for every embedding model. On the retrieval suite,
10
+ * BM25 fused with document vectors this way scored nDCG@10 0.568 against 0.349
11
+ * for the additive boost stack it replaced
12
+ * (`akm/eval/retrieval/reports/baseline-2026-09-27.md`).
129
13
  */
130
- export function lexicalNameMatchTier(entry, queryTokens) {
131
- if (queryTokens.length === 0)
132
- return 0;
133
- const nameBase = entry.name.toLowerCase().split("/").pop() ?? entry.name.toLowerCase();
134
- const nameTokens = lexicalNameTokens(nameBase);
135
- if (nameTokens.length === queryTokens.length &&
136
- nameTokens.every((token, index) => structuralNameTokenMatch(token, queryTokens[index]))) {
137
- return 3;
138
- }
139
- const matched = queryTokens.filter((token) => nameTokens.some((nameToken) => structuralNameTokenMatch(nameToken, token))).length;
140
- if (matched === queryTokens.length)
141
- return 2;
142
- return matched > 0 ? 1 : 0;
143
- }
14
+ import { compareCodePoints } from "../../core/common.js";
15
+ /** The RRF constant: a candidate at rank r in a channel adds 1 / (RRF_K + r). */
16
+ export const RRF_K = 60;
144
17
  /**
145
- * A relaxed OR query admits intentionally weak candidates. Candidates with no
146
- * query token in their name remain visible for body-only recall, but cannot
147
- * share the same bounded displayed score as stronger name-bearing recoveries.
148
- * The raw ceiling is 0.65; the public score projection is applied later, so
149
- * callers never literally receive `0.65` just because this ceiling bound.
150
- *
151
- * Preserve the pre-ceiling relevance separately from `preCeilingScore`, which
152
- * belongs to belief-state demotion and may be written afterwards. A relaxed,
153
- * belief-demoted candidate otherwise loses both its body relevance and its
154
- * ordering signal when the second ceiling overwrites the first.
18
+ * Fuse ranked channels with equal weights. Candidates are ordered by fused
19
+ * score, then by `itemRef`, so equal scores always come back in the same order.
155
20
  */
156
- function applyRelaxedLexicalScoreCeiling(item, queryTokens) {
157
- if (item.lexicalMatch !== "relaxed" || lexicalNameMatchTier(item.entry, queryTokens) > 0)
158
- return;
159
- if (item.score > RELAXED_NON_NAME_SCORE_CEILING) {
160
- item.preRelaxedCeilingScore = item.score;
161
- item.score = RELAXED_NON_NAME_SCORE_CEILING;
162
- }
21
+ export function reciprocalRankFusion(channels) {
22
+ const fused = new Map();
23
+ channels.forEach((channel, channelIndex) => {
24
+ channel.forEach((candidate, position) => {
25
+ let entry = fused.get(candidate.id);
26
+ if (entry === undefined) {
27
+ entry = { id: candidate.id, itemRef: candidate.itemRef, score: 0, ranks: channels.map(() => undefined) };
28
+ fused.set(candidate.id, entry);
29
+ }
30
+ if (entry.ranks[channelIndex] !== undefined)
31
+ return;
32
+ entry.ranks[channelIndex] = position + 1;
33
+ entry.score += 1 / (RRF_K + position + 1);
34
+ });
35
+ });
36
+ return [...fused.values()].sort((a, b) => b.score - a.score || compareCodePoints(a.itemRef, b.itemRef));
163
37
  }
@@ -10,52 +10,32 @@ export function attachSearchHitAttribution(target, attribution) {
10
10
  };
11
11
  }
12
12
  export function copySearchHitAttribution(from, to, outputDescription) {
13
- const attribution = from[ATTRIBUTION];
14
- if (!attribution)
13
+ const memoryInference = from[ATTRIBUTION]?.memoryInference;
14
+ if (!memoryInference)
15
15
  return;
16
- const memoryInference = attribution.memoryInference;
17
- const memorySurvives = memoryInference?.exposure !== "surface" ||
16
+ const memorySurvives = memoryInference.exposure !== "surface" ||
18
17
  (memoryInference.surfaceDescription !== undefined && memoryInference.surfaceDescription === outputDescription);
19
- const applicable = {
20
- ...(attribution.lexical ? { lexical: attribution.lexical } : {}),
21
- ...(memorySurvives && memoryInference ? { memoryInference } : {}),
22
- ...(attribution.graphExtraction ? { graphExtraction: attribution.graphExtraction } : {}),
23
- };
24
- if (applicable.lexical || applicable.memoryInference || applicable.graphExtraction) {
25
- attachSearchHitAttribution(to, applicable);
26
- }
18
+ if (memorySurvives)
19
+ attachSearchHitAttribution(to, { memoryInference });
27
20
  }
28
21
  export function getSearchHitAttribution(target) {
29
22
  return target[ATTRIBUTION];
30
23
  }
31
24
  export function buildUsageEventAttribution(attribution, entryRef, projection = "full") {
32
- if (!attribution)
25
+ const memoryInference = attribution?.memoryInference;
26
+ if (!memoryInference)
33
27
  return undefined;
34
- const memoryInference = attribution.memoryInference;
35
- const childRef = memoryInference?.exposure === "direct" ? entryRef : memoryInference?.childRef;
36
- const graphExtraction = attribution.graphExtraction;
37
- const graphApplies = graphExtraction !== undefined && Number.isFinite(graphExtraction.boost) && graphExtraction.boost > 0;
38
- const surfaceFields = memoryInference?.surfaceFields ?? [];
39
- const surfaceVisible = memoryInference?.exposure !== "surface" ||
28
+ const childRef = memoryInference.exposure === "direct" ? entryRef : memoryInference.childRef;
29
+ const surfaceFields = memoryInference.surfaceFields ?? [];
30
+ const surfaceVisible = memoryInference.exposure !== "surface" ||
40
31
  (projection === "full"
41
32
  ? surfaceFields.length > 0
42
33
  : projection === "normal" || projection === "agent"
43
34
  ? surfaceFields.includes("description")
44
35
  : false);
45
- const memoryApplies = memoryInference !== undefined && childRef?.includes("//") === true && surfaceVisible;
46
- if (!memoryApplies && !graphApplies)
36
+ if (childRef?.includes("//") !== true || !surfaceVisible)
47
37
  return undefined;
48
- return {
49
- ...(memoryApplies
50
- ? {
51
- memoryInference: {
52
- exposure: memoryInference.exposure,
53
- childRef,
54
- },
55
- }
56
- : {}),
57
- ...(graphApplies ? { graphExtraction } : {}),
58
- };
38
+ return { memoryInference: { exposure: memoryInference.exposure, childRef } };
59
39
  }
60
40
  export function usageEventAttributionMetadata(attribution, entryRef, projection = "full") {
61
41
  const applicable = buildUsageEventAttribution(attribution, entryRef, projection);
@@ -4,6 +4,9 @@
4
4
  /**
5
5
  * Return per-field search text for multi-column FTS5 indexing.
6
6
  *
7
+ * Text keeps its case: FTS5's `unicode61` tokenizer folds case itself, and
8
+ * `buildSearchText` feeds the embedder, where case carries meaning.
9
+ *
7
10
  * Fields:
8
11
  * - name: entry name with hyphens/underscores replaced by spaces
9
12
  * - description: entry description
@@ -12,22 +15,15 @@
12
15
  * - content: bounded native/adapter body projection + TOC headings + parameters
13
16
  * (lowest-weight catch-all)
14
17
  */
15
- // NOTE (R5): the collapse detector's frozen canary queries are built from the
16
- // same surface this function indexes (name tokens / tags / description) and
17
- // scored via FTS against it. Changing what buildSearchFields includes shifts
18
- // the detector's recall baseline for ALL existing canary sets — coordinate
19
- // with src/commands/improve/collapse-detector.ts (buildCanaryQuery) and expect
20
- // operators to re-mint via `bun scripts/refresh-canary-set.ts --refresh` after
21
- // such a change.
22
18
  export function buildSearchFields(entry) {
23
- const name = entry.name.replace(/[-_]/g, " ").toLowerCase();
24
- const description = (entry.description ?? "").toLowerCase();
19
+ const name = entry.name.replace(/[-_]/g, " ");
20
+ const description = entry.description ?? "";
25
21
  const tagParts = [];
26
22
  if (entry.tags)
27
23
  tagParts.push(entry.tags.join(" "));
28
24
  if (entry.aliases)
29
25
  tagParts.push(entry.aliases.join(" "));
30
- const tags = tagParts.join(" ").toLowerCase();
26
+ const tags = tagParts.join(" ");
31
27
  const hintParts = [];
32
28
  if (entry.hints)
33
29
  hintParts.push(entry.hints.join(" "));
@@ -51,7 +47,7 @@ export function buildSearchFields(entry) {
51
47
  hintParts.push(entry.pageKind);
52
48
  if (entry.whenToUse)
53
49
  hintParts.push(entry.whenToUse);
54
- const hints = hintParts.join(" ").toLowerCase();
50
+ const hints = hintParts.join(" ");
55
51
  const contentParts = [];
56
52
  if (entry.toc) {
57
53
  contentParts.push(entry.toc.map((h) => h.text).join(" "));
@@ -65,13 +61,13 @@ export function buildSearchFields(entry) {
65
61
  }
66
62
  if (entry.content)
67
63
  contentParts.push(entry.content);
68
- const content = contentParts.join(" ").toLowerCase();
64
+ const content = contentParts.join(" ");
69
65
  return { name, description, tags, hints, content };
70
66
  }
71
67
  /**
72
- * Build a single concatenated search text string for an entry.
73
- * Used for the `search_text` column in the entries table.
74
- * and for generating embedding text.
68
+ * Build a single concatenated search text string for an entry: the text its
69
+ * vector is embedded from, derived from the stored document when the entry is
70
+ * embedded (`entries.embed_hash` holds its hash).
75
71
  */
76
72
  export function buildSearchText(entry) {
77
73
  const fields = buildSearchFields(entry);
@@ -2,100 +2,69 @@
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 { displayRef } from "../../core/asset/resolve-ref.js";
5
+ import { presentationFor } from "../../core/type-presentation.js";
5
6
  import { getDerivedForParent, getItemRefById } from "../../storage/repositories/index-entries-repository.js";
6
7
  import { getRenderer } from "../walk/file-context.js";
7
8
  import { attachSearchHitAttribution } from "./search-attribution.js";
8
- const rendererSearchHitEnricher = {
9
- name: "renderer-search-hit-enricher",
10
- appliesTo(ctx) {
11
- return ctx.rendererRegistry.rendererNameFor(ctx.type) !== undefined;
12
- },
13
- async enrich(hit, ctx) {
14
- const rendererName = ctx.rendererRegistry.rendererNameFor(ctx.type);
15
- if (!rendererName)
16
- return;
9
+ /**
10
+ * Let the hit's type renderer enrich it, then — for a memory, when a DB is
11
+ * open — surface its derived child ({@link surfaceDerivedMemory}).
12
+ */
13
+ export async function enrichSearchHit(hit, ctx) {
14
+ const rendererName = presentationFor(ctx.type).renderer;
15
+ if (rendererName) {
17
16
  const renderer = await getRenderer(rendererName);
18
17
  renderer?.enrichSearchHit?.(hit, ctx.stashDir);
19
- },
20
- };
18
+ }
19
+ if (ctx.type === "memory" && ctx.db)
20
+ surfaceDerivedMemory(hit, ctx.db, ctx.bundleId);
21
+ }
21
22
  /**
22
- * Phase 5A / Advantage D5 — derived-memory enricher.
23
- *
24
- * When a parent memory has a `.derived` child indexed (the LLM-distilled
25
- * lesson surface), this enricher rewrites the parent hit to surface the
26
- * derived child's description / searchHints / tags AND sets `expandTo` to
27
- * the derived child's ref so callers can fetch it via `akm show <ref>`.
23
+ * Phase 5A / Advantage D5 — when a parent memory has a `.derived` child
24
+ * indexed (the LLM-distilled lesson surface), rewrite the parent hit to
25
+ * surface the derived child's description / tags AND set `expandTo` to the
26
+ * derived child's ref so callers can fetch it via `akm show <ref>`.
28
27
  *
29
28
  * The parent ref is preserved on the hit — only the surface text is
30
29
  * swapped, so links and provenance still point at the canonical parent.
31
- *
32
- * Skipped for:
33
- * - non-memory hits
34
- * - memory hits that are themselves derived children (name ends with
35
- * `.derived`) — we never recurse parent→child→grandchild
36
- * - contexts without an open DB connection
37
30
  */
38
- export const derivedMemoryEnricher = {
39
- name: "derived-memory-enricher",
40
- appliesTo(ctx) {
41
- return ctx.type === "memory" && ctx.db !== undefined;
42
- },
43
- enrich(hit, ctx) {
44
- if (!ctx.db)
45
- return;
46
- // Never recurse: a `.derived` hit is itself the child surface; leaving
47
- // it untouched also avoids `<parent>.derived.derived` chains.
48
- if (hit.name.toLowerCase().endsWith(".derived"))
49
- return;
50
- // Parent ref shape: the 0.9.0 `memories/<name>` conceptId. Re-build from the
51
- // entry's name so we don't depend on whatever wiki/registry prefix `hit.ref`
52
- // carries. INTERNAL lookup key into `getDerivedForParent`: the `derived_from`
53
- // column now stores this same conceptId grammar (Group-C item 2 flip — the
54
- // metadata producer + this consumer move together).
55
- const parentRef = `memories/${hit.name}`;
56
- const derived = getDerivedForParent(ctx.db, parentRef, ctx.bundleId);
57
- if (!derived)
58
- return;
59
- // Swap description / searchHints / tags from the derived child.
60
- // The parent ref itself is preserved — only the surface text is swapped.
61
- const surfaceFields = [];
62
- let surfaceDescription;
63
- if (typeof derived.entry.description === "string" && derived.entry.description.length > 0) {
64
- hit.description = derived.entry.description;
65
- surfaceDescription = derived.entry.description;
66
- surfaceFields.push("description");
67
- }
68
- if (Array.isArray(derived.entry.searchHints) && derived.entry.searchHints.length > 0) {
69
- // We don't have a `searchHints` field on SourceSearchHit today — it's
70
- // only used inside ranking. The plan says to swap when present; we
71
- // record it onto the hit only if a future renderer surfaces it. For
72
- // now, treat as advisory (no-op when SearchHit lacks the field).
73
- }
74
- if (Array.isArray(derived.entry.tags) && derived.entry.tags.length > 0) {
75
- hit.tags = derived.entry.tags;
76
- surfaceFields.push("tags");
77
- }
78
- // F4b output-spelling flip: `expandTo` is a user-facing `akm show <ref>`
79
- // target, so emit the 0.9.0 short conceptId grammar (`memories/<name>`).
80
- hit.expandTo = displayRef({ type: "memory", name: derived.entry.name });
81
- const childRef = getItemRefById(ctx.db, derived.id);
82
- if (childRef && surfaceFields.length > 0) {
83
- attachSearchHitAttribution(hit, {
84
- memoryInference: {
85
- exposure: "surface",
86
- childRef,
87
- surfaceFields,
88
- ...(surfaceDescription ? { surfaceDescription } : {}),
89
- },
90
- });
91
- }
92
- },
93
- };
94
- export const defaultSearchHitEnrichers = [rendererSearchHitEnricher, derivedMemoryEnricher];
95
- export async function enrichSearchHit(hit, ctx, enrichers = defaultSearchHitEnrichers) {
96
- for (const enricher of enrichers) {
97
- if (!enricher.appliesTo(ctx))
98
- continue;
99
- await enricher.enrich(hit, ctx);
31
+ function surfaceDerivedMemory(hit, db, bundleId) {
32
+ // Never recurse: a `.derived` hit is itself the child surface; leaving
33
+ // it untouched also avoids `<parent>.derived.derived` chains.
34
+ if (hit.name.toLowerCase().endsWith(".derived"))
35
+ return;
36
+ // Parent ref shape: the 0.9.0 `memories/<name>` conceptId. Re-build from the
37
+ // entry's name so we don't depend on whatever wiki/registry prefix `hit.ref`
38
+ // carries. INTERNAL lookup key into `getDerivedForParent`: the `derived_from`
39
+ // column stores this same conceptId grammar.
40
+ const derived = getDerivedForParent(db, `memories/${hit.name}`, bundleId);
41
+ if (!derived)
42
+ return;
43
+ // Swap description / tags from the derived child (SourceSearchHit carries no
44
+ // `searchHints`).
45
+ const surfaceFields = [];
46
+ let surfaceDescription;
47
+ if (typeof derived.entry.description === "string" && derived.entry.description.length > 0) {
48
+ hit.description = derived.entry.description;
49
+ surfaceDescription = derived.entry.description;
50
+ surfaceFields.push("description");
51
+ }
52
+ if (Array.isArray(derived.entry.tags) && derived.entry.tags.length > 0) {
53
+ hit.tags = derived.entry.tags;
54
+ surfaceFields.push("tags");
55
+ }
56
+ // `expandTo` is a user-facing `akm show <ref>` target, so emit the 0.9.0
57
+ // short conceptId grammar (`memories/<name>`).
58
+ hit.expandTo = displayRef({ type: "memory", name: derived.entry.name });
59
+ const childRef = getItemRefById(db, derived.id);
60
+ if (childRef && surfaceFields.length > 0) {
61
+ attachSearchHitAttribution(hit, {
62
+ memoryInference: {
63
+ exposure: "surface",
64
+ childRef,
65
+ surfaceFields,
66
+ ...(surfaceDescription ? { surfaceDescription } : {}),
67
+ },
68
+ });
100
69
  }
101
70
  }
@@ -7,14 +7,11 @@ import { isWithin, resolveStashDir } from "../../core/common.js";
7
7
  import { bundleComponentConfig, bundleKeyForContentRoot, bundlesToSourceEntries, getSources, isBundleEnabled, loadConfig, } from "../../core/config/config.js";
8
8
  import { ConfigError } from "../../core/errors.js";
9
9
  import { getUnresolvedSourcesDir } from "../../core/paths.js";
10
+ import { warn } from "../../core/warn.js";
10
11
  import { resolveGitContentRoot, resolveWritable } from "../../core/write-source.js";
11
12
  import { lockContentRootFor } from "../../integrations/lockfile.js";
12
13
  import { resolveSourceProviderFactory } from "../../sources/provider-factory.js";
13
14
  import { ensureWebsiteMirror } from "../../sources/snapshot-fetchers/website-ingest.js";
14
- // Eager side-effect imports so all built-in source providers self-register
15
- // before resolveEntryContentDir() runs.
16
- import "../../sources/providers/index.js";
17
- import { warn } from "../../core/warn.js";
18
15
  // ── Resolution ──────────────────────────────────────────────────────────────
19
16
  /**
20
17
  * Build the ordered list of stash sources, walking every configured stash
@@ -91,20 +91,49 @@ export function countUsageEventsByType(db, eventType) {
91
91
  return db.prepare("SELECT COUNT(*) AS cnt FROM usage_events WHERE event_type = ?").get(eventType)
92
92
  .cnt;
93
93
  }
94
+ /**
95
+ * Durable refs a user-attributed `search`, `curate` or `show` returned, or a
96
+ * user `feedback` named, at or after `sinceIso`. Machine traffic (`improve`,
97
+ * `task`, `audit`, `unknown`) is not demand, as in {@link countFeedbackSignals}.
98
+ */
99
+ export function listUsedEntryRefs(db, sinceIso) {
100
+ const rows = db
101
+ .prepare(`SELECT DISTINCT entry_ref FROM usage_events
102
+ WHERE event_type IN ('search', 'curate', 'show', 'feedback')
103
+ AND source = 'user'
104
+ AND entry_ref IS NOT NULL
105
+ AND julianday(created_at) >= julianday(?)`)
106
+ .all(sinceIso);
107
+ return rows.map((row) => row.entry_ref);
108
+ }
109
+ /**
110
+ * The distinct query texts of user `search` and `curate` events that returned
111
+ * `conceptId` (any bundle prefix), most recent first.
112
+ */
113
+ export function listRetrievalQueries(db, conceptId) {
114
+ const rows = db
115
+ .prepare(`SELECT query, MAX(created_at) AS last_at FROM usage_events
116
+ WHERE event_type IN ('search', 'curate')
117
+ AND source = 'user'
118
+ AND query IS NOT NULL AND trim(query) != ''
119
+ AND instr(entry_ref, '//') > 0
120
+ AND substr(entry_ref, instr(entry_ref, '//') + 2) = ?
121
+ GROUP BY query
122
+ ORDER BY last_at DESC`)
123
+ .all(conceptId);
124
+ return rows.map((row) => row.query);
125
+ }
126
+ /** Usage events older than this many days are purged on every `akm index`. */
127
+ export const USAGE_EVENT_RETENTION_DAYS = 90;
94
128
  /**
95
129
  * Delete usage events older than the given number of days.
96
130
  */
97
- export function purgeOldUsageEvents(db, retentionDays, options) {
131
+ export function purgeOldUsageEvents(db, retentionDays) {
98
132
  if (!Number.isFinite(retentionDays) || retentionDays <= 0)
99
133
  return;
100
134
  try {
101
- const stateSchema = options?.stateSchema;
102
- if (stateSchema !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(stateSchema)) {
103
- throw new Error("Invalid attached state schema name.");
104
- }
105
- const usageEvents = stateSchema === undefined ? "usage_events" : `"${stateSchema}".usage_events`;
106
135
  const cutoff = new Date(Date.now() - retentionDays * 86_400_000).toISOString();
107
- db.prepare(`DELETE FROM ${usageEvents} WHERE created_at < ?`).run(cutoff);
136
+ db.prepare("DELETE FROM usage_events WHERE created_at < ?").run(cutoff);
108
137
  }
109
138
  catch {
110
139
  /* Table may not exist yet */