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
@@ -8,65 +8,50 @@
8
8
  * high-signal set of stash + registry hits and enrich each with the data
9
9
  * needed to act (ref, run, parameters, follow-up command).
10
10
  *
11
- * The exported `akmCurate()` API is the single entry point. Internal helpers
12
- * stay private. Tests can drive the public API or call the smaller pure
13
- * helpers (`curateSearchResults`, `deriveCurateFallbackQueries`,
14
- * `mergeCurateSearchResponses`) by importing them directly.
11
+ * Curation is one search with the fused ranking, the top `limit` hits, and
12
+ * per-hit enrichment (preview, run and parameters, support refs from the
13
+ * hit's declared links). An optional reranker reorders the top fused
14
+ * candidates first.
15
+ *
16
+ * The exported `akmCurate()` API is the single entry point; tests can also
17
+ * drive `curateSearchResults` with a fixture search response.
15
18
  */
16
- import fs from "node:fs";
17
- import { parseFrontmatter } from "../../core/asset/frontmatter.js";
18
- import { parseRefInput } from "../../core/asset/resolve-ref.js";
19
- import { getIndexPassConfig, loadConfig } from "../../core/config/config.js";
19
+ import { parseBundleRef } from "../../core/asset/asset-ref.js";
20
+ import { typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
21
+ import { loadConfig } from "../../core/config/config.js";
20
22
  import { rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
21
23
  import { appendEvent } from "../../core/events.js";
24
+ import { nonTaskInput } from "../../core/non-task-input.js";
25
+ import { redactCredentialPatterns } from "../../core/redaction.js";
22
26
  import { withStateDbTelemetry } from "../../core/state-db.js";
23
- import { enqueueGraphExtraction, hasGraphData } from "../../indexer/db/graph-db.js";
27
+ import { searchHitContent } from "../../indexer/search/db-search.js";
24
28
  import { copySearchHitAttribution, getSearchHitAttribution, usageEventAttributionMetadata, } from "../../indexer/search/search-attribution.js";
25
- import { findSourceForPath, resolveSourceEntries } from "../../indexer/search/search-source.js";
26
29
  import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
27
30
  import { estimateTokenCount } from "../../llm/embedders/remote.js";
28
- import { tryLlmFeature } from "../../llm/feature-gate.js";
31
+ import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
29
32
  import { rerankDocuments } from "../../llm/rerank-client.js";
30
33
  import { truncateDescription } from "../../output/shapes/helpers.js";
31
34
  import { TELEMETRY_BUSY_TIMEOUT_MS, withIndexDb } from "../../storage/repositories/index-db.js";
32
35
  import { findEntryIdByRef, getItemRefById } from "../../storage/repositories/index-entries-repository.js";
33
- import { computeBodyHash } from "../../storage/repositories/index-llm-cache-repository.js";
34
36
  import { akmSearch, parseSearchSource } from "./search.js";
35
37
  import { akmShowUnified } from "./show.js";
36
- const CURATE_FALLBACK_FILTER_WORDS = new Set([
37
- "a",
38
- "an",
39
- "and",
40
- "for",
41
- "how",
42
- "i",
43
- "in",
44
- "of",
45
- "or",
46
- "the",
47
- "to",
48
- "with",
49
- ]);
50
- const CURATE_SHORT_FALLBACK_TOKENS = new Set(["ai", "ci", "cd", "go", "js", "ts"]);
51
- const MIN_CURATE_FALLBACK_TOKEN_LENGTH = 3;
52
- const MAX_CURATE_FALLBACK_KEYWORDS = 6;
53
- export const CURATE_SEARCH_LIMIT_MULTIPLIER = 4;
54
- export const MIN_CURATE_SEARCH_LIMIT = 12;
55
38
  const DEFAULT_CURATE_LIMIT = 4;
56
- const CURATE_CLOSE_SCORE_BAND = 0.12;
57
- // Used by `shouldRunCurateFallback` to judge whether the initial search
58
- // already returned strong results (a different purpose from ranking/keeping
59
- // hits — see that function for its own rationale).
60
- const CURATE_RELATIVE_SCORE_FLOOR = 0.7;
61
- const CURATE_FALLBACK_TOP_SCORE_THRESHOLD = 0.8;
62
- const CURATE_FALLBACK_STRONG_SCORE_FLOOR = 0.35;
63
39
  const MAX_CURATE_SUPPORT_REFS = 2;
64
- const CURATE_REFERENCE_QUERY_RE = /\b(?:reference|docs?|guide|how|explain|learn|readme|why)\b/;
40
+ /** Fused candidates the reranker reorders when `search.curateRerank.topN` is unset. */
41
+ const DEFAULT_CURATE_RERANK_TOP_N = 30;
42
+ /** Characters of name, description and content sent to the reranker per candidate. */
43
+ const RERANK_DOCUMENT_CHARS = 2000;
65
44
  /**
66
45
  * Fire-and-forget: log a curate event to the usage_events table and events.jsonl.
67
46
  * Never blocks the caller; errors are silently ignored.
68
47
  */
69
- function logCurateEvent(query, result, eventSource = "user", attributionProjection = "full") {
48
+ function logCurateEvent(rawQuery, result, eventSource = "user", attributionProjection = "full") {
49
+ // Credentials pasted into a query (e.g. by the Claude Code hook that
50
+ // curates every user prompt) must never reach state.db verbatim — see
51
+ // `redactCredentialPatterns`. Redacted once here so every persistence call
52
+ // below (events.metadata_json via appendEvent, usage_events.query via
53
+ // insertUsageEvent) gets the same scrubbed text.
54
+ const query = redactCredentialPatterns(rawQuery);
70
55
  const itemRefs = result.items.map((item) => ("ref" in item ? item.ref : `registry:${item.id}`));
71
56
  appendEvent({
72
57
  eventType: "curate",
@@ -118,14 +103,29 @@ export async function akmCurate(options) {
118
103
  if (!trimmedQuery) {
119
104
  throw new UsageError('A curation query is required. Usage: akm curate "<task or prompt>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
120
105
  }
106
+ const nonTask = nonTaskInput(trimmedQuery);
107
+ if (nonTask) {
108
+ const abstained = {
109
+ query: options.query,
110
+ summary: `Curate abstained: the input is ${nonTask}, not a task.`,
111
+ items: [],
112
+ tip: 'Nothing was selected on purpose. To curate for it, pass the task itself: akm curate "<what you are trying to do>".',
113
+ };
114
+ if (!options.skipLogging) {
115
+ logCurateEvent(options.query, abstained, options.eventSource, options.attributionProjection);
116
+ }
117
+ return abstained;
118
+ }
121
119
  const limit = options.limit && options.limit > 0 ? options.limit : DEFAULT_CURATE_LIMIT;
122
120
  const source = options.source ?? parseSearchSource("local");
123
121
  const searchResponse = options.searchResponse ??
124
- (await searchForCuration({
122
+ (await akmSearch({
125
123
  query: options.query,
126
124
  type: options.type,
127
- limit: Math.max(limit * CURATE_SEARCH_LIMIT_MULTIPLIER, MIN_CURATE_SEARCH_LIMIT),
125
+ // An enabled reranker reorders the top fused candidates, not just the final `limit`.
126
+ limit: Math.max(limit, rerankTopN(loadConfig())),
128
127
  source,
128
+ skipLogging: true,
129
129
  }));
130
130
  const result = await curateSearchResults(options.query, searchResponse, limit, options.type, options.eventSource);
131
131
  if (!options.skipLogging) {
@@ -136,27 +136,18 @@ export async function akmCurate(options) {
136
136
  export async function curateSearchResults(query, result, limit, selectedType, eventSource) {
137
137
  const allStashHits = result.hits.filter((hit) => hit.type !== "registry");
138
138
  const registryHits = result.registryHits ?? [];
139
- // F3/R-018: `--type` must NARROW the candidate pool, not bypass curation.
140
- // Previously a set `selectedType` skipped `selectCuratedStashHits` (ranking,
141
- // intent nudges, score floor) entirely, taking the raw top-N of whatever
142
- // order the hits arrived in. Filtering the pool down to the requested type up front and
143
- // then running the SAME pipeline over it gives `--type` its intended
144
- // "narrow, don't disable" semantics — and matters even when the caller's
145
- // search already applied a type filter at the DB layer, because
146
- // `curateSearchResults` is also driven directly (tests, `searchResponse`
147
- // fixtures) with a `SearchResponse` that was never type-filtered.
139
+ // F3/R-018: `--type` NARROWS the candidate pool. The caller's search
140
+ // usually applied the filter already, but `curateSearchResults` is also
141
+ // driven directly (tests, `searchResponse` fixtures) with a
142
+ // `SearchResponse` that was never type-filtered.
148
143
  const stashHits = selectedType && selectedType !== "any" ? allStashHits.filter((hit) => hit.type === selectedType) : allStashHits;
149
- const selected = selectCuratedStashHits(query, stashHits, limit);
150
- const selectedStashHits = await maybeRerankCuratedStashHits(query, selected.selected);
151
- const supportRefsByRef = selected.supportRefsByRef;
144
+ const selectedStashHits = (await maybeRerankCuratedStashHits(query, stashHits)).slice(0, limit);
152
145
  // F4/R-019: respect `--limit` for registry fill instead of hard-capping it
153
146
  // at a bare literal 2 — the remaining slots after stash hits ARE the cap.
154
147
  const selectedRegistryHits = selectedStashHits.length >= limit ? [] : registryHits.slice(0, limit - selectedStashHits.length);
155
148
  const selectedRefs = new Set(selectedStashHits.map((hit) => hit.ref));
156
149
  const items = [
157
- ...(await Promise.all(selectedStashHits
158
- .slice(0, limit)
159
- .map((hit) => enrichCuratedStashHit(query, hit, supportRefsByRef.get(hit.ref) ?? [], selectedRefs, eventSource)))),
150
+ ...(await Promise.all(selectedStashHits.map((hit) => enrichCuratedStashHit(query, hit, selectedRefs, eventSource)))),
160
151
  ...selectedRegistryHits.map((hit) => buildCuratedRegistryItem(query, hit)),
161
152
  ].slice(0, limit);
162
153
  return {
@@ -214,7 +205,7 @@ export async function packCuratedHits(result, budgetTokens) {
214
205
  }
215
206
  return { query: result.query, budget: budgetTokens, tokens: used, items: packed };
216
207
  }
217
- async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, eventSource) {
208
+ async function enrichCuratedStashHit(query, hit, selectedRefs, eventSource) {
218
209
  let shown;
219
210
  try {
220
211
  shown = await akmShowUnified({ ref: hit.ref, eventSource, skipLogging: true });
@@ -222,14 +213,9 @@ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, even
222
213
  catch {
223
214
  shown = undefined;
224
215
  }
225
- // #624-P3: when lazy graph extraction is opted in, enqueue an ungraphed
226
- // asset for a later pass to extract. Fire-and-forget, non-blocking, NO inline
227
- // extraction and NO LLM call here. Default-off (flag unset) = byte-identical.
228
- if (shown?.path)
229
- maybeEnqueueLazyGraph(shown.path);
230
216
  const description = shown?.description ?? hit.description;
231
217
  const preview = buildCuratedPreview(shown, hit);
232
- const mergedSupportRefs = mergeCurateSupportRefs(supportRefs, shown?.related?.hits, selectedRefs, hit.ref);
218
+ const supportRefs = buildCurateSupportRefs(shown?.links, selectedRefs, hit.ref);
233
219
  const item = {
234
220
  source: "local",
235
221
  type: shown?.type ?? hit.type,
@@ -245,7 +231,7 @@ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, even
245
231
  ...(shown?.keys?.length ? { keys: shown.keys } : {}),
246
232
  ...(shown?.parameters?.length ? { parameters: shown.parameters } : {}),
247
233
  ...(shown?.run ? { run: shown.run } : {}),
248
- ...(mergedSupportRefs.length > 0 ? { supportRefs: mergedSupportRefs } : {}),
234
+ ...(supportRefs.length > 0 ? { supportRefs } : {}),
249
235
  followUp: `akm show ${hit.ref}`,
250
236
  reason: buildCuratedReason(query, shown?.type ?? hit.type),
251
237
  ...(hit.score !== undefined ? { score: hit.score } : {}),
@@ -253,44 +239,6 @@ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, even
253
239
  copySearchHitAttribution(hit, item, item.description);
254
240
  return item;
255
241
  }
256
- /**
257
- * #624-P3 — enqueue an ungraphed asset for lazy graph extraction when the
258
- * `index.graph.lazyGraphExtraction` flag is on. Pure side-effect, fully
259
- * best-effort: any failure (config, fs, db) is swallowed so curate never fails
260
- * on it. NO LLM call and NO inline extraction — only a cheap queue insert.
261
- * Default-off (flag unset) returns immediately = byte-identical behavior.
262
- */
263
- function maybeEnqueueLazyGraph(assetPath) {
264
- try {
265
- const config = loadConfig();
266
- if (getIndexPassConfig(config.index, "graph")?.lazyGraphExtraction !== true)
267
- return;
268
- const sources = resolveSourceEntries();
269
- const source = findSourceForPath(assetPath, sources);
270
- const stashRoot = source?.path;
271
- if (!stashRoot)
272
- return;
273
- let raw;
274
- try {
275
- raw = fs.readFileSync(assetPath, "utf8");
276
- }
277
- catch {
278
- return;
279
- }
280
- const body = parseFrontmatter(raw).content.trim();
281
- if (!body)
282
- return;
283
- const bodyHash = computeBodyHash(body);
284
- withIndexDb((db) => {
285
- if (!hasGraphData(db, stashRoot, assetPath)) {
286
- enqueueGraphExtraction(db, stashRoot, assetPath, bodyHash, 0);
287
- }
288
- }, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
289
- }
290
- catch (err) {
291
- rethrowIfTestIsolationError(err);
292
- }
293
- }
294
242
  function buildCuratedRegistryItem(query, hit) {
295
243
  return {
296
244
  source: "registry",
@@ -341,274 +289,20 @@ function buildCurateSummary(query, items) {
341
289
  const labels = items.map((item) => ("ref" in item ? item.ref : `${item.type}:${item.name}`));
342
290
  return `Selected ${items.length} curated result${items.length === 1 ? "" : "s"}: ${labels.join(", ")}.`;
343
291
  }
344
- export function deriveCurateFallbackQueries(query) {
345
- const normalizedWhole = query
346
- .toLowerCase()
347
- .replace(/[^a-z0-9]+/g, " ")
348
- .trim();
349
- const tokens = Array.from(new Set(query
350
- .toLowerCase()
351
- .split(/[^a-z0-9]+/)
352
- .map((token) => token.trim())
353
- .filter((token) => token.length > 0 &&
354
- !CURATE_FALLBACK_FILTER_WORDS.has(token) &&
355
- (token.length >= MIN_CURATE_FALLBACK_TOKEN_LENGTH || CURATE_SHORT_FALLBACK_TOKENS.has(token))))).slice(0, MAX_CURATE_FALLBACK_KEYWORDS);
356
- if (tokens.length === 1 && tokens[0] === normalizedWhole)
357
- return [];
358
- return tokens;
359
- }
360
- export function mergeCurateSearchResponses(base, extras) {
361
- // The base (full-query) ranking is authoritative by default; keyword
362
- // fallbacks add recall without comparing their raw scores to the full-query
363
- // score scale. Base order is preserved and fallback-only hits append below,
364
- // except for the narrow weak-provenance promotion documented below. Duplicate
365
- // refs keep their base position but take the maximum score for the downstream
366
- // relevance floor.
367
- const bestExtraStashScore = new Map();
368
- for (const result of extras) {
369
- for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
370
- const prev = bestExtraStashScore.get(hit.ref);
371
- if (prev === undefined || (hit.score ?? 0) > prev)
372
- bestExtraStashScore.set(hit.ref, hit.score ?? 0);
373
- }
374
- }
375
- const baseRefs = new Set();
376
- const baseStash = [];
377
- for (const hit of base.hits.filter((entry) => entry.type !== "registry")) {
378
- baseRefs.add(hit.ref);
379
- const extraScore = bestExtraStashScore.get(hit.ref);
380
- baseStash.push(extraScore !== undefined && extraScore > (hit.score ?? 0) ? { ...hit, score: extraScore } : hit);
381
- }
382
- const extraOnly = new Map();
383
- for (const result of extras) {
384
- for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
385
- if (baseRefs.has(hit.ref))
386
- continue;
387
- const existing = extraOnly.get(hit.ref);
388
- if (!existing || (hit.score ?? 0) > (existing.score ?? 0))
389
- extraOnly.set(hit.ref, hit);
390
- }
391
- }
392
- // Fallback-only hits ordinarily stay below every full-query hit through the
393
- // downstream selector, which re-sorts by score and derives its relevance
394
- // floor from the leader. Restamping them below the minimum base score keeps a
395
- // single-token score from leapfrogging contextual results. The one exception
396
- // is a strong name match repairing a lexically weak base, selected below.
397
- const sortedExtra = [...extraOnly.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
398
- const lexicalAttribution = (hit) => getSearchHitAttribution(hit)?.lexical;
399
- // The full-query base remains authoritative unless its lexical provenance is
400
- // itself weak: relaxed recovery, or no hit whose name covers the query. In
401
- // that case one fallback discovery with strong name coverage may lead. This
402
- // preserves contextual base ranking while allowing measured recall to repair
403
- // a thin match without comparing unrelated raw FTS score scales.
404
- const weakLexicalBase = baseStash.length > 0 &&
405
- baseStash.every((hit) => {
406
- const lexical = lexicalAttribution(hit);
407
- return lexical !== undefined && (lexical.execution === "relaxed" || lexical.nameMatchTier < 2);
408
- });
409
- const promotedExtraIndex = weakLexicalBase
410
- ? sortedExtra.findIndex((hit) => (lexicalAttribution(hit)?.nameMatchTier ?? 0) >= 2)
411
- : -1;
412
- const promotedExtra = promotedExtraIndex >= 0 ? sortedExtra[promotedExtraIndex] : undefined;
413
- const remainingExtra = promotedExtraIndex >= 0 ? sortedExtra.filter((_, index) => index !== promotedExtraIndex) : sortedExtra;
414
- const minBaseScore = baseStash.length
415
- ? Math.min(...baseStash.map((hit) => hit.score ?? 0))
416
- : Number.POSITIVE_INFINITY;
417
- const cappedExtra = baseStash.length
418
- ? remainingExtra.map((hit, i) => ({ ...hit, score: minBaseScore - 1e-6 * (i + 1) }))
419
- : remainingExtra;
420
- const mergedHits = [
421
- ...(promotedExtra ? [{ ...promotedExtra, score: minBaseScore }] : []),
422
- ...baseStash,
423
- ...cappedExtra,
424
- ];
425
- // Registry hits are supplemental fill — same rule: base first (max score on
426
- // dups), then fallback-only registry hits appended by score.
427
- const bestExtraRegScore = new Map();
428
- for (const result of extras) {
429
- for (const hit of result.registryHits ?? []) {
430
- const prev = bestExtraRegScore.get(hit.id);
431
- if (prev === undefined || (hit.score ?? 0) > prev)
432
- bestExtraRegScore.set(hit.id, hit.score ?? 0);
433
- }
434
- }
435
- const baseRegIds = new Set();
436
- const baseReg = [];
437
- for (const hit of base.registryHits ?? []) {
438
- baseRegIds.add(hit.id);
439
- const extraScore = bestExtraRegScore.get(hit.id);
440
- baseReg.push(extraScore !== undefined && extraScore > (hit.score ?? 0) ? { ...hit, score: extraScore } : hit);
441
- }
442
- const extraRegOnly = new Map();
443
- for (const result of extras) {
444
- for (const hit of result.registryHits ?? []) {
445
- if (baseRegIds.has(hit.id))
446
- continue;
447
- const existing = extraRegOnly.get(hit.id);
448
- if (!existing || (hit.score ?? 0) > (existing.score ?? 0))
449
- extraRegOnly.set(hit.id, hit);
450
- }
451
- }
452
- const warnings = Array.from(new Set([...(base.warnings ?? []), ...extras.flatMap((result) => result.warnings ?? [])]));
453
- const mergedRegistryHits = [
454
- ...baseReg,
455
- ...[...extraRegOnly.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0)),
456
- ];
457
- const modes = [base.searchMode, ...extras.map((result) => result.searchMode)].filter((mode) => mode !== undefined);
458
- const searchMode = modes.includes("fts-fallback")
459
- ? "fts-fallback"
460
- : modes.includes("semantic")
461
- ? "semantic"
462
- : modes.includes("keyword")
463
- ? "keyword"
464
- : undefined;
465
- return {
466
- ...base,
467
- hits: mergedHits,
468
- ...(mergedRegistryHits.length > 0 ? { registryHits: mergedRegistryHits } : {}),
469
- ...(warnings.length > 0 ? { warnings } : {}),
470
- ...(searchMode ? { searchMode } : {}),
471
- ...(mergedHits.length > 0 || mergedRegistryHits.length > 0 ? { tip: undefined } : {}),
472
- };
473
- }
474
- export async function searchForCuration(input) {
475
- const initial = await akmSearch({ ...input, skipLogging: true });
476
- if (!shouldRunCurateFallback(initial, input.limit))
477
- return initial;
478
- const fallbackQueries = deriveCurateFallbackQueries(input.query);
479
- if (fallbackQueries.length === 0)
480
- return initial;
481
- const fallbackResults = await Promise.all(fallbackQueries.map((token) => akmSearch({
482
- query: token,
483
- type: input.type,
484
- limit: input.limit,
485
- source: input.source,
486
- skipLogging: true,
487
- })));
488
- return mergeCurateSearchResponses(initial, fallbackResults);
489
- }
490
- function parseCurateIntent(query) {
491
- const lower = query.toLowerCase();
492
- return {
493
- executionHeavy: /(run|script|bash|shell|cli|execute|automation|deploy|build|test|lint)/.test(lower),
494
- multiStep: /(plan|workflow|steps?|procedure|rollout|review|migration|release|checklist)/.test(lower),
495
- delegation: /(agent|assistant|planner|reviewer|architect|prompt)/.test(lower),
496
- recall: /(memory|context|recall|remember)/.test(lower),
497
- reference: CURATE_REFERENCE_QUERY_RE.test(lower),
498
- };
499
- }
500
- function computeCurateTypeNudge(type, intent) {
501
- let nudge = 0;
502
- if (intent.executionHeavy) {
503
- if (type === "script")
504
- nudge += 0.06;
505
- else if (type === "command")
506
- nudge += 0.04;
507
- else if (type === "memory")
508
- nudge -= 0.04;
509
- }
510
- if (intent.multiStep) {
511
- if (type === "workflow")
512
- nudge += 0.06;
513
- else if (type === "skill")
514
- nudge += 0.04;
515
- else if (type === "knowledge")
516
- nudge -= 0.02;
517
- }
518
- if (intent.delegation && type === "agent")
519
- nudge += 0.06;
520
- if (intent.recall && type === "memory")
521
- nudge += 0.08;
522
- if (intent.reference) {
523
- if (type === "knowledge")
524
- nudge += 0.05;
525
- else if (type === "skill")
526
- nudge += 0.02;
527
- }
528
- return nudge;
529
- }
530
- function getCurateFamily(ref) {
531
- try {
532
- // F4b: `ref` is a search-hit ref in the 0.9.0 conceptId grammar — parse via
533
- // the new-grammar `parseRefInput` so skill/reference family grouping still
534
- // recognizes it. Search may add an opaque Markdown selector; identity and
535
- // family ownership are on the parent asset, not that selector.
536
- const parsed = parseRefInput(ref.split("#", 1)[0]);
537
- if (parsed.type === "skill") {
538
- return { key: parsed.name, role: "root" };
539
- }
540
- if (parsed.type !== "knowledge")
541
- return undefined;
542
- const match = /^skills\/(.+?)\/references\/(.+)$/.exec(parsed.name);
543
- if (!match)
544
- return undefined;
545
- return {
546
- key: match[1],
547
- role: "reference",
548
- topicTokens: match[2]
549
- .split(/[^a-z0-9]+/i)
550
- .map((token) => token.trim().toLowerCase())
551
- .filter(Boolean),
552
- };
553
- }
554
- catch {
555
- return undefined;
556
- }
557
- }
558
- function annotateCurateHit(query, hit, index, intent) {
559
- const rawScore = hit.score ?? 0;
560
- const family = getCurateFamily(hit.ref);
561
- let adjustedScore = rawScore + computeCurateTypeNudge(hit.type, intent);
562
- if (family?.role === "root" && !isNarrowReferenceFamilyQuery(query, family))
563
- adjustedScore += 0.07;
564
- if (family?.role === "reference" && isNarrowReferenceFamilyQuery(query, family))
565
- adjustedScore += 0.07;
566
- return {
567
- hit,
568
- rawScore,
569
- adjustedScore,
570
- originalIndex: index,
571
- family,
572
- };
573
- }
574
- function compareCurateHits(a, b) {
575
- const rawDiff = b.rawScore - a.rawScore;
576
- if (Math.abs(rawDiff) > CURATE_CLOSE_SCORE_BAND)
577
- return rawDiff;
578
- const adjustedDiff = b.adjustedScore - a.adjustedScore;
579
- if (adjustedDiff !== 0)
580
- return adjustedDiff;
581
- if (rawDiff !== 0)
582
- return rawDiff;
583
- return a.originalIndex - b.originalIndex;
584
- }
585
- function isNarrowReferenceFamilyQuery(query, family) {
586
- if (!family || family.role !== "reference")
587
- return false;
588
- const lower = query.toLowerCase();
589
- if (CURATE_REFERENCE_QUERY_RE.test(lower))
590
- return true;
591
- return family.topicTokens.some((token) => token.length >= 3 && lower.includes(token));
292
+ /** How many fused candidates curate fetches for the reranker; 0 when reranking is off. */
293
+ function rerankTopN(config) {
294
+ if (!isLlmFeatureEnabled(config, "curate_rerank"))
295
+ return 0;
296
+ return config.search?.curateRerank?.topN ?? DEFAULT_CURATE_RERANK_TOP_N;
592
297
  }
593
- function appendCurateSupportRef(supportRefsByRef, ownerRef, supportRef) {
594
- const existing = supportRefsByRef.get(ownerRef) ?? [];
595
- if (existing.some((entry) => entry.ref === supportRef.ref))
596
- return;
597
- supportRefsByRef.set(ownerRef, [...existing, supportRef]);
598
- }
599
- /** Default number of `selectCuratedStashHits` candidates sent to the reranker when `search.curateRerank.topN` isn't set. */
600
- const DEFAULT_CURATE_RERANK_TOP_N = 8;
601
298
  /**
602
- * Optional cross-encoder rerank pass over curate's already-selected, already-
603
- * ranked candidates (#951). Disabled by default (`search.curateRerank.enabled`
604
- * is falsy) and, when enabled, best-effort: any failure (misconfigured
605
- * endpoint, network error, timeout, malformed response) falls back to
606
- * `selectCuratedStashHits`'s own ranking unchanged — a reranker outage must
607
- * never turn into a curate failure.
608
- *
609
- * Only the top `topN` (default {@link DEFAULT_CURATE_RERANK_TOP_N}) already-
610
- * selected hits are sent (bounded request size); anything past that keeps its
611
- * original position appended after the reranked prefix.
299
+ * Optional cross-encoder rerank of the top fused candidates (#951). Disabled
300
+ * by default (`search.curateRerank.enabled`) and, when enabled, best-effort:
301
+ * any failure (misconfigured endpoint, network error, timeout, malformed
302
+ * response) keeps the fused order — a reranker outage must never turn into a
303
+ * curate failure. The top `topN` candidates (default
304
+ * {@link DEFAULT_CURATE_RERANK_TOP_N}) are sent as name, description and the
305
+ * start of the indexed content; the rest keep their fused order after them.
612
306
  */
613
307
  async function maybeRerankCuratedStashHits(query, hits) {
614
308
  if (hits.length <= 1)
@@ -616,109 +310,58 @@ async function maybeRerankCuratedStashHits(query, hits) {
616
310
  const config = loadConfig();
617
311
  const rerankConfig = config.search?.curateRerank;
618
312
  return tryLlmFeature("curate_rerank", config, async () => {
619
- const topN = rerankConfig?.topN ?? DEFAULT_CURATE_RERANK_TOP_N;
620
- const head = hits.slice(0, topN);
621
- const tail = hits.slice(topN);
622
- const documents = head.map((hit) => [hit.name, hit.description].filter(Boolean).join(" — "));
623
- const ranked = await rerankDocuments(rerankConfig ?? {}, query, documents);
313
+ const head = hits.slice(0, rerankTopN(config));
314
+ const tail = hits.slice(head.length);
315
+ const ranked = await rerankDocuments(rerankConfig ?? {}, query, rerankDocumentTexts(head));
624
316
  const rerankedHead = ranked
625
317
  .map(({ index }) => head[index])
626
318
  .filter((hit) => hit !== undefined);
627
319
  return [...rerankedHead, ...tail];
628
320
  }, hits, { timeoutMs: rerankConfig?.timeoutMs ?? null });
629
321
  }
630
- function selectCuratedStashHits(query, hits, limit) {
631
- const intent = parseCurateIntent(query);
632
- const collapsed = collapseCurateFamilies(query, hits);
633
- const ranked = collapsed.hits
634
- .map(({ hit, originalIndex }) => annotateCurateHit(query, hit, originalIndex, intent))
635
- .sort(compareCurateHits);
636
- const supportRefsByRef = collapsed.supportRefsByRef;
637
- return { selected: ranked.slice(0, limit).map((entry) => entry.hit), supportRefsByRef };
322
+ /**
323
+ * Name, description and the start of each hit's indexed content, capped for
324
+ * the reranker. The content is the index's safe projection (env and secret
325
+ * values never reach it), never the raw file.
326
+ */
327
+ function rerankDocumentTexts(hits) {
328
+ return hits.map((hit) => [hit.name, hit.description, searchHitContent(hit)].filter(Boolean).join("\n").slice(0, RERANK_DOCUMENT_CHARS));
638
329
  }
639
- function collapseCurateFamilies(query, hits) {
640
- const passthrough = [];
641
- const supportRefsByRef = new Map();
642
- const groups = new Map();
643
- for (const [index, hit] of hits.entries()) {
644
- const family = getCurateFamily(hit.ref);
645
- if (!family) {
646
- passthrough.push({ hit, originalIndex: index });
647
- continue;
648
- }
649
- const group = groups.get(family.key) ?? { references: [] };
650
- if (family.role === "root") {
651
- if (!group.root)
652
- group.root = { hit, originalIndex: index };
653
- }
654
- else {
655
- group.references.push({ hit, originalIndex: index });
656
- }
657
- groups.set(family.key, group);
658
- }
659
- const collapsedFamilies = [];
660
- for (const group of groups.values()) {
661
- const bestReference = group.references[0];
662
- const representative = group.root && !isNarrowReferenceFamilyQuery(query, getCurateFamily(bestReference?.hit.ref ?? group.root.hit.ref))
663
- ? group.root
664
- : (bestReference ?? group.root);
665
- if (!representative)
666
- continue;
667
- collapsedFamilies.push(representative);
668
- const supportCandidates = [group.root, ...group.references]
669
- .filter((entry) => {
670
- return entry !== undefined && entry.hit.ref !== representative.hit.ref;
671
- })
672
- .sort((a, b) => {
673
- if (a === group.root)
674
- return -1;
675
- if (b === group.root)
676
- return 1;
677
- return a.hit.ref.localeCompare(b.hit.ref);
678
- });
679
- for (const support of supportCandidates) {
680
- appendCurateSupportRef(supportRefsByRef, representative.hit.ref, {
681
- ref: support.hit.ref,
682
- type: support.hit.type,
683
- reason: "Related family asset to inspect next.",
684
- });
330
+ /**
331
+ * Up to {@link MAX_CURATE_SUPPORT_REFS} assets the hit's declared links (#935)
332
+ * name, not already selected: what the hit links to first, then what links to
333
+ * it, in the order `akm show` lists them. The LLM entity graph's `related`
334
+ * list no longer feeds them.
335
+ */
336
+ function buildCurateSupportRefs(links, selectedRefs, ownerRef) {
337
+ const supportRefs = [];
338
+ for (const [part, direction] of [
339
+ ["outgoing", "from"],
340
+ ["incoming", "to"],
341
+ ]) {
342
+ for (const [kind, group] of Object.entries(links?.[part] ?? {})) {
343
+ for (const ref of group.refs) {
344
+ if (ref === ownerRef || selectedRefs.has(ref) || supportRefs.some((existing) => existing.ref === ref))
345
+ continue;
346
+ const type = supportRefType(ref);
347
+ supportRefs.push({
348
+ ref,
349
+ ...(type ? { type } : {}),
350
+ reason: `Declared link (${kind}) ${direction} this asset.`,
351
+ });
352
+ if (supportRefs.length >= MAX_CURATE_SUPPORT_REFS)
353
+ return supportRefs;
354
+ }
685
355
  }
686
356
  }
687
- return {
688
- hits: [...passthrough, ...collapsedFamilies].sort((a, b) => a.originalIndex - b.originalIndex),
689
- supportRefsByRef,
690
- };
357
+ return supportRefs;
691
358
  }
692
- function mergeCurateSupportRefs(seeded, relatedHits, selectedRefs, ownerRef) {
693
- const merged = [];
694
- for (const entry of seeded) {
695
- if (entry.ref === ownerRef || selectedRefs.has(entry.ref))
696
- continue;
697
- if (merged.some((existing) => existing.ref === entry.ref))
698
- continue;
699
- merged.push(entry);
700
- if (merged.length >= MAX_CURATE_SUPPORT_REFS)
701
- return merged;
359
+ /** The asset type a ref's conceptId names (`memories/x` → `memory`), when it names one. */
360
+ function supportRefType(ref) {
361
+ try {
362
+ return typeNameFromConceptId(parseBundleRef(ref).conceptId)?.type;
702
363
  }
703
- if (!Array.isArray(relatedHits))
704
- return merged;
705
- for (const hit of relatedHits) {
706
- if (!hit.ref || hit.ref === ownerRef || selectedRefs.has(hit.ref))
707
- continue;
708
- if (merged.some((existing) => existing.ref === hit.ref))
709
- continue;
710
- merged.push({ ref: hit.ref, type: hit.type, reason: "Related asset via shared entities." });
711
- if (merged.length >= MAX_CURATE_SUPPORT_REFS)
712
- break;
364
+ catch {
365
+ return undefined;
713
366
  }
714
- return merged;
715
- }
716
- function shouldRunCurateFallback(initial, desiredCount) {
717
- const stashHits = initial.hits.filter((hit) => hit.type !== "registry");
718
- if (stashHits.length === 0)
719
- return true;
720
- const topScore = stashHits[0]?.score ?? 0;
721
- const strongFloor = Math.max(CURATE_FALLBACK_STRONG_SCORE_FLOOR, topScore * CURATE_RELATIVE_SCORE_FLOOR);
722
- const strongCount = stashHits.filter((hit) => (hit.score ?? 0) >= strongFloor).length;
723
- return !(topScore >= CURATE_FALLBACK_TOP_SCORE_THRESHOLD && strongCount >= Math.min(2, desiredCount));
724
367
  }