akm-cli 0.7.0 → 0.7.2

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 (332) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{src/cli.js → cli.js} +22 -8
  3. package/dist/{src/commands → commands}/installed-stashes.js +1 -1
  4. package/dist/{src/commands → commands}/source-add.js +1 -1
  5. package/dist/{src/core → core}/common.js +16 -1
  6. package/dist/{src/core → core}/config.js +5 -2
  7. package/dist/{src/indexer → indexer}/db-search.js +16 -1
  8. package/dist/{src/indexer → indexer}/graph-extraction.js +5 -3
  9. package/dist/{src/indexer → indexer}/indexer.js +27 -11
  10. package/dist/{src/indexer → indexer}/memory-inference.js +47 -58
  11. package/dist/{src/indexer → indexer}/search-source.js +1 -1
  12. package/dist/{src/llm → llm}/client.js +61 -1
  13. package/dist/{src/llm → llm}/embedder.js +8 -5
  14. package/dist/{src/llm → llm}/embedders/local.js +8 -2
  15. package/dist/{src/llm → llm}/embedders/remote.js +4 -2
  16. package/dist/{src/llm → llm}/graph-extract.js +4 -4
  17. package/dist/llm/memory-infer.js +114 -0
  18. package/dist/{src/llm → llm}/metadata-enhance.js +2 -2
  19. package/dist/{src/output → output}/cli-hints.js +2 -0
  20. package/dist/{src/setup → setup}/setup.js +30 -20
  21. package/dist/sources/providers/website.js +27 -0
  22. package/dist/{src/sources/providers/website.js → sources/website-ingest.js} +38 -51
  23. package/docs/README.md +7 -0
  24. package/docs/migration/release-notes/0.7.0.md +14 -0
  25. package/package.json +11 -8
  26. package/dist/src/llm/memory-infer.js +0 -86
  27. package/dist/tests/add-website-source.test.js +0 -119
  28. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  29. package/dist/tests/agent/agent-config.test.js +0 -221
  30. package/dist/tests/agent/agent-detect.test.js +0 -100
  31. package/dist/tests/agent/agent-spawn.test.js +0 -234
  32. package/dist/tests/agent-output.test.js +0 -186
  33. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  34. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  35. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  36. package/dist/tests/asset-ref.test.js +0 -192
  37. package/dist/tests/asset-registry.test.js +0 -103
  38. package/dist/tests/asset-spec.test.js +0 -241
  39. package/dist/tests/bench/attribution.test.js +0 -996
  40. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  41. package/dist/tests/bench/cleanup.js +0 -234
  42. package/dist/tests/bench/cleanup.test.js +0 -166
  43. package/dist/tests/bench/cli.js +0 -1018
  44. package/dist/tests/bench/cli.test.js +0 -445
  45. package/dist/tests/bench/compare.test.js +0 -556
  46. package/dist/tests/bench/corpus.js +0 -317
  47. package/dist/tests/bench/corpus.test.js +0 -258
  48. package/dist/tests/bench/doctor.js +0 -525
  49. package/dist/tests/bench/driver.js +0 -401
  50. package/dist/tests/bench/driver.test.js +0 -584
  51. package/dist/tests/bench/environment.js +0 -233
  52. package/dist/tests/bench/environment.test.js +0 -199
  53. package/dist/tests/bench/evolve-metrics.js +0 -179
  54. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  55. package/dist/tests/bench/evolve.js +0 -647
  56. package/dist/tests/bench/evolve.test.js +0 -624
  57. package/dist/tests/bench/failure-modes.test.js +0 -349
  58. package/dist/tests/bench/feedback-integrity.test.js +0 -457
  59. package/dist/tests/bench/leakage.test.js +0 -228
  60. package/dist/tests/bench/learning-curve.test.js +0 -134
  61. package/dist/tests/bench/metrics.js +0 -2395
  62. package/dist/tests/bench/metrics.test.js +0 -1150
  63. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  64. package/dist/tests/bench/opencode-config.js +0 -194
  65. package/dist/tests/bench/opencode-config.test.js +0 -370
  66. package/dist/tests/bench/report.js +0 -1885
  67. package/dist/tests/bench/report.test.js +0 -1038
  68. package/dist/tests/bench/run-config.js +0 -355
  69. package/dist/tests/bench/run-config.test.js +0 -298
  70. package/dist/tests/bench/run-curate-test.js +0 -32
  71. package/dist/tests/bench/run-failing-tasks.js +0 -56
  72. package/dist/tests/bench/run-full-bench.js +0 -51
  73. package/dist/tests/bench/run-items36-targeted.js +0 -69
  74. package/dist/tests/bench/run-nano-quick.js +0 -42
  75. package/dist/tests/bench/run-waveg-targeted.js +0 -62
  76. package/dist/tests/bench/runner.js +0 -699
  77. package/dist/tests/bench/runner.test.js +0 -958
  78. package/dist/tests/bench/search-bridge.test.js +0 -331
  79. package/dist/tests/bench/tmp.js +0 -131
  80. package/dist/tests/bench/trajectory.js +0 -116
  81. package/dist/tests/bench/trajectory.test.js +0 -127
  82. package/dist/tests/bench/verifier.js +0 -114
  83. package/dist/tests/bench/verifier.test.js +0 -118
  84. package/dist/tests/bench/workflow-evaluator.js +0 -557
  85. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  86. package/dist/tests/bench/workflow-spec.js +0 -345
  87. package/dist/tests/bench/workflow-spec.test.js +0 -363
  88. package/dist/tests/bench/workflow-trace.js +0 -472
  89. package/dist/tests/bench/workflow-trace.test.js +0 -254
  90. package/dist/tests/benchmark-search-quality.js +0 -536
  91. package/dist/tests/benchmark-suite.js +0 -1441
  92. package/dist/tests/capture-cli.test.js +0 -112
  93. package/dist/tests/cli-errors.test.js +0 -204
  94. package/dist/tests/commands/events.test.js +0 -370
  95. package/dist/tests/commands/history.test.js +0 -418
  96. package/dist/tests/commands/import.test.js +0 -103
  97. package/dist/tests/commands/proposal-cli.test.js +0 -209
  98. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  99. package/dist/tests/commands/remember.test.js +0 -97
  100. package/dist/tests/commands/scope-flags.test.js +0 -300
  101. package/dist/tests/commands/search.test.js +0 -537
  102. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  103. package/dist/tests/commands/show.test.js +0 -294
  104. package/dist/tests/common.test.js +0 -266
  105. package/dist/tests/completions.test.js +0 -142
  106. package/dist/tests/config-cli.test.js +0 -193
  107. package/dist/tests/config-llm-features.test.js +0 -139
  108. package/dist/tests/config.test.js +0 -569
  109. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  110. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  111. package/dist/tests/contracts/spec-helpers.js +0 -46
  112. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  113. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  114. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  115. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  116. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  117. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  118. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  119. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  120. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  121. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  122. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  123. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  124. package/dist/tests/core/write-source.test.js +0 -366
  125. package/dist/tests/curate-command.test.js +0 -87
  126. package/dist/tests/db-scoring.test.js +0 -201
  127. package/dist/tests/db.test.js +0 -654
  128. package/dist/tests/distill-cli-flag.test.js +0 -208
  129. package/dist/tests/distill.test.js +0 -515
  130. package/dist/tests/docker-install.test.js +0 -120
  131. package/dist/tests/e2e.test.js +0 -1419
  132. package/dist/tests/embedder.test.js +0 -340
  133. package/dist/tests/embedding-model-config.test.js +0 -379
  134. package/dist/tests/feedback-command.test.js +0 -172
  135. package/dist/tests/file-context.test.js +0 -552
  136. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  137. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  138. package/dist/tests/fixtures/stashes/load.js +0 -166
  139. package/dist/tests/fixtures/stashes/load.test.js +0 -97
  140. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  141. package/dist/tests/frontmatter.test.js +0 -190
  142. package/dist/tests/fts-field-weighting.test.js +0 -254
  143. package/dist/tests/fuzzy-search.test.js +0 -230
  144. package/dist/tests/git-provider-clone.test.js +0 -45
  145. package/dist/tests/github.test.js +0 -161
  146. package/dist/tests/graph-boost-ranking.test.js +0 -305
  147. package/dist/tests/graph-extraction.test.js +0 -282
  148. package/dist/tests/helpers/usage-events.js +0 -8
  149. package/dist/tests/index-pass-llm.test.js +0 -161
  150. package/dist/tests/indexer.test.js +0 -570
  151. package/dist/tests/info-command.test.js +0 -166
  152. package/dist/tests/init.test.js +0 -69
  153. package/dist/tests/install-script.test.js +0 -246
  154. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  155. package/dist/tests/issue-36-repro.test.js +0 -304
  156. package/dist/tests/issues-191-194.test.js +0 -160
  157. package/dist/tests/lesson-lint.test.js +0 -111
  158. package/dist/tests/llm-client.test.js +0 -115
  159. package/dist/tests/llm-feature-gate.test.js +0 -151
  160. package/dist/tests/llm.test.js +0 -139
  161. package/dist/tests/lockfile.test.js +0 -216
  162. package/dist/tests/manifest.test.js +0 -205
  163. package/dist/tests/markdown.test.js +0 -126
  164. package/dist/tests/matchers-unit.test.js +0 -189
  165. package/dist/tests/memory-inference.test.js +0 -299
  166. package/dist/tests/merge-scoring.test.js +0 -136
  167. package/dist/tests/metadata.test.js +0 -313
  168. package/dist/tests/migration-help.test.js +0 -89
  169. package/dist/tests/origin-resolve.test.js +0 -124
  170. package/dist/tests/output-baseline.test.js +0 -218
  171. package/dist/tests/output-shapes-unit.test.js +0 -478
  172. package/dist/tests/parallel-search.test.js +0 -272
  173. package/dist/tests/parameter-metadata.test.js +0 -365
  174. package/dist/tests/paths.test.js +0 -177
  175. package/dist/tests/progressive-disclosure.test.js +0 -280
  176. package/dist/tests/proposals.test.js +0 -279
  177. package/dist/tests/proposed-quality.test.js +0 -271
  178. package/dist/tests/provider-registry.test.js +0 -32
  179. package/dist/tests/ranking-regression.test.js +0 -548
  180. package/dist/tests/reflect-propose.test.js +0 -455
  181. package/dist/tests/registry-build-index.test.js +0 -394
  182. package/dist/tests/registry-cli.test.js +0 -290
  183. package/dist/tests/registry-index-v2.test.js +0 -430
  184. package/dist/tests/registry-install.test.js +0 -728
  185. package/dist/tests/registry-providers/parity.test.js +0 -189
  186. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  187. package/dist/tests/registry-providers/static-index.test.js +0 -238
  188. package/dist/tests/registry-resolve.test.js +0 -126
  189. package/dist/tests/registry-search.test.js +0 -923
  190. package/dist/tests/remember-frontmatter.test.js +0 -378
  191. package/dist/tests/remember-unit.test.js +0 -123
  192. package/dist/tests/ripgrep-install.test.js +0 -251
  193. package/dist/tests/ripgrep-resolve.test.js +0 -108
  194. package/dist/tests/ripgrep.test.js +0 -163
  195. package/dist/tests/save-command.test.js +0 -94
  196. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  197. package/dist/tests/scoring-pipeline.test.js +0 -648
  198. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  199. package/dist/tests/self-update.test.js +0 -442
  200. package/dist/tests/semantic-search-e2e.test.js +0 -512
  201. package/dist/tests/semantic-status.test.js +0 -471
  202. package/dist/tests/setup-run.integration.js +0 -877
  203. package/dist/tests/setup-wizard.test.js +0 -198
  204. package/dist/tests/setup.test.js +0 -131
  205. package/dist/tests/source-add.test.js +0 -11
  206. package/dist/tests/source-clone.test.js +0 -254
  207. package/dist/tests/source-manage.test.js +0 -366
  208. package/dist/tests/source-providers/filesystem.test.js +0 -82
  209. package/dist/tests/source-providers/git.test.js +0 -252
  210. package/dist/tests/source-providers/website.test.js +0 -128
  211. package/dist/tests/source-qa-fixes.test.js +0 -286
  212. package/dist/tests/source-registry.test.js +0 -350
  213. package/dist/tests/source-resolve.test.js +0 -100
  214. package/dist/tests/source-source.test.js +0 -281
  215. package/dist/tests/source.test.js +0 -533
  216. package/dist/tests/tar-utils-scan.test.js +0 -73
  217. package/dist/tests/toggle-components.test.js +0 -73
  218. package/dist/tests/usage-telemetry.test.js +0 -265
  219. package/dist/tests/utility-scoring.test.js +0 -558
  220. package/dist/tests/vault-load-error.test.js +0 -78
  221. package/dist/tests/vault-qa-fixes.test.js +0 -194
  222. package/dist/tests/vault.test.js +0 -429
  223. package/dist/tests/vector-search.test.js +0 -608
  224. package/dist/tests/walker.test.js +0 -252
  225. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  226. package/dist/tests/wave2-cluster-d.test.js +0 -180
  227. package/dist/tests/wave2-cluster-e.test.js +0 -179
  228. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  229. package/dist/tests/wiki.test.js +0 -529
  230. package/dist/tests/workflow-cli.test.js +0 -271
  231. package/dist/tests/workflow-markdown.test.js +0 -171
  232. package/dist/tests/workflow-path-escape.test.js +0 -132
  233. package/dist/tests/workflow-qa-fixes.test.js +0 -395
  234. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  235. /package/dist/{src/commands → commands}/completions.js +0 -0
  236. /package/dist/{src/commands → commands}/config-cli.js +0 -0
  237. /package/dist/{src/commands → commands}/curate.js +0 -0
  238. /package/dist/{src/commands → commands}/distill.js +0 -0
  239. /package/dist/{src/commands → commands}/events.js +0 -0
  240. /package/dist/{src/commands → commands}/history.js +0 -0
  241. /package/dist/{src/commands → commands}/info.js +0 -0
  242. /package/dist/{src/commands → commands}/init.js +0 -0
  243. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  244. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  245. /package/dist/{src/commands → commands}/proposal.js +0 -0
  246. /package/dist/{src/commands → commands}/propose.js +0 -0
  247. /package/dist/{src/commands → commands}/reflect.js +0 -0
  248. /package/dist/{src/commands → commands}/registry-search.js +0 -0
  249. /package/dist/{src/commands → commands}/remember.js +0 -0
  250. /package/dist/{src/commands → commands}/search.js +0 -0
  251. /package/dist/{src/commands → commands}/self-update.js +0 -0
  252. /package/dist/{src/commands → commands}/show.js +0 -0
  253. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  254. /package/dist/{src/commands → commands}/source-manage.js +0 -0
  255. /package/dist/{src/commands → commands}/vault.js +0 -0
  256. /package/dist/{src/core → core}/asset-ref.js +0 -0
  257. /package/dist/{src/core → core}/asset-registry.js +0 -0
  258. /package/dist/{src/core → core}/asset-spec.js +0 -0
  259. /package/dist/{src/core → core}/errors.js +0 -0
  260. /package/dist/{src/core → core}/events.js +0 -0
  261. /package/dist/{src/core → core}/frontmatter.js +0 -0
  262. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  263. /package/dist/{src/core → core}/markdown.js +0 -0
  264. /package/dist/{src/core → core}/paths.js +0 -0
  265. /package/dist/{src/core → core}/proposals.js +0 -0
  266. /package/dist/{src/core → core}/warn.js +0 -0
  267. /package/dist/{src/core → core}/write-source.js +0 -0
  268. /package/dist/{src/indexer → indexer}/db.js +0 -0
  269. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  270. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  271. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  272. /package/dist/{src/indexer → indexer}/matchers.js +0 -0
  273. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  274. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  275. /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
  276. /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
  277. /package/dist/{src/indexer → indexer}/walker.js +0 -0
  278. /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
  279. /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
  280. /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
  281. /package/dist/{src/integrations → integrations}/agent/profiles.js +0 -0
  282. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  283. /package/dist/{src/integrations → integrations}/agent/spawn.js +0 -0
  284. /package/dist/{src/integrations → integrations}/github.js +0 -0
  285. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  286. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  287. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  288. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  289. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  290. /package/dist/{src/output → output}/context.js +0 -0
  291. /package/dist/{src/output → output}/renderers.js +0 -0
  292. /package/dist/{src/output → output}/shapes.js +0 -0
  293. /package/dist/{src/output → output}/text.js +0 -0
  294. /package/dist/{src/registry → registry}/build-index.js +0 -0
  295. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  296. /package/dist/{src/registry → registry}/factory.js +0 -0
  297. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  298. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  299. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  300. /package/dist/{src/registry → registry}/providers/static-index.js +0 -0
  301. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  302. /package/dist/{src/registry → registry}/resolve.js +0 -0
  303. /package/dist/{src/registry → registry}/types.js +0 -0
  304. /package/dist/{src/setup → setup}/detect.js +0 -0
  305. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  306. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  307. /package/dist/{src/setup → setup}/steps.js +0 -0
  308. /package/dist/{src/sources → sources}/include.js +0 -0
  309. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  310. /package/dist/{src/sources → sources}/provider.js +0 -0
  311. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  312. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  313. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  314. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  315. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  316. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  317. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  318. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  319. /package/dist/{src/sources → sources}/resolve.js +0 -0
  320. /package/dist/{src/sources → sources}/types.js +0 -0
  321. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  322. /package/dist/{src/version.js → version.js} +0 -0
  323. /package/dist/{src/wiki → wiki}/wiki.js +0 -0
  324. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  325. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  326. /package/dist/{src/workflows → workflows}/db.js +0 -0
  327. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  328. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  329. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  330. /package/dist/{src/workflows → workflows}/runs.js +0 -0
  331. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  332. /package/dist/{src/workflows → workflows}/validator.js +0 -0
@@ -1,548 +0,0 @@
1
- /**
2
- * Ranking regression tests for akm search system.
3
- *
4
- * Uses the shared `ranking-baseline` fixture under
5
- * tests/fixtures/stashes/ranking-baseline/ to validate search ranking
6
- * invariants: score differentiation, exact name matching, type ranking,
7
- * fuzzy/prefix matching, score preservation, and provider merge behavior.
8
- *
9
- * The fixture stash is materialised once in beforeAll via the shared
10
- * `loadFixtureStash` helper, then re-indexed in place through the internal
11
- * indexer DB API so all tests share the same index.
12
- */
13
- import { afterAll, beforeAll, describe, expect, test } from "bun:test";
14
- import fs from "node:fs";
15
- import os from "node:os";
16
- import path from "node:path";
17
- import { akmSearch } from "../src/commands/search";
18
- import { saveConfig } from "../src/core/config";
19
- import { getDbPath } from "../src/core/paths";
20
- import { closeDatabase, openDatabase, rebuildFts, setMeta, upsertEntry } from "../src/indexer/db";
21
- import { buildSearchText } from "../src/indexer/search-fields";
22
- import { loadFixtureStash } from "./fixtures/stashes/load";
23
- // Local test helper — mirrors the pre-v1 mergeStashHits logic that was removed
24
- // from production code when the OpenViking provider was dropped (Phase 1).
25
- function mergeStashHits(localHits, additionalHits, limit) {
26
- if (additionalHits.length === 0)
27
- return localHits.slice(0, limit);
28
- const localKeys = new Set();
29
- for (const h of localHits)
30
- localKeys.add(h.path ?? h.ref ?? h.name);
31
- const providerOnly = additionalHits.filter((h) => !localKeys.has(h.path ?? h.ref ?? h.name));
32
- return [...localHits, ...providerOnly].sort((a, b) => (b.score ?? 0) - (a.score ?? 0)).slice(0, limit);
33
- }
34
- // ── Fixture path ────────────────────────────────────────────────────────────
35
- let FIXTURE_STASH;
36
- let fixtureCleanup;
37
- // ── Temp directory tracking ─────────────────────────────────────────────────
38
- const createdTmpDirs = [];
39
- function createTmpDir(prefix = "akm-ranking-") {
40
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
41
- createdTmpDirs.push(dir);
42
- return dir;
43
- }
44
- // ── Environment isolation ───────────────────────────────────────────────────
45
- let originalXdgCacheHome;
46
- let originalXdgConfigHome;
47
- let originalAkmStashDir;
48
- let testCacheDir;
49
- let testConfigDir;
50
- beforeAll(async () => {
51
- originalXdgCacheHome = process.env.XDG_CACHE_HOME;
52
- originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
53
- originalAkmStashDir = process.env.AKM_STASH_DIR;
54
- testCacheDir = createTmpDir("akm-ranking-cache-");
55
- testConfigDir = createTmpDir("akm-ranking-config-");
56
- // Materialise the shared ranking-baseline fixture into a tmp dir. This
57
- // test rebuilds the index from scratch via `buildFixtureIndex()` below
58
- // against its own XDG_CACHE_HOME, so we skip the helper's `akm index`
59
- // spawn (~200-300ms saved per run).
60
- const loaded = loadFixtureStash("ranking-baseline", { skipIndex: true });
61
- FIXTURE_STASH = loaded.stashDir;
62
- fixtureCleanup = loaded.cleanup;
63
- process.env.XDG_CACHE_HOME = testCacheDir;
64
- process.env.XDG_CONFIG_HOME = testConfigDir;
65
- process.env.AKM_STASH_DIR = FIXTURE_STASH;
66
- saveConfig({
67
- semanticSearchMode: "off",
68
- sources: [{ type: "filesystem", path: FIXTURE_STASH }],
69
- registries: [],
70
- });
71
- buildFixtureIndex();
72
- });
73
- afterAll(() => {
74
- if (originalXdgCacheHome === undefined)
75
- delete process.env.XDG_CACHE_HOME;
76
- else
77
- process.env.XDG_CACHE_HOME = originalXdgCacheHome;
78
- if (originalXdgConfigHome === undefined)
79
- delete process.env.XDG_CONFIG_HOME;
80
- else
81
- process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
82
- if (originalAkmStashDir === undefined)
83
- delete process.env.AKM_STASH_DIR;
84
- else
85
- process.env.AKM_STASH_DIR = originalAkmStashDir;
86
- fixtureCleanup?.();
87
- for (const dir of createdTmpDirs) {
88
- fs.rmSync(dir, { recursive: true, force: true });
89
- }
90
- });
91
- // ── Index builder ───────────────────────────────────────────────────────────
92
- /**
93
- * Walk the fixture stash, read .stash.json files, and index all entries
94
- * directly into the SQLite database.
95
- */
96
- function buildFixtureIndex() {
97
- const dbPath = getDbPath();
98
- const db = openDatabase(dbPath);
99
- try {
100
- const stashJsonPaths = findStashJsonFiles(FIXTURE_STASH);
101
- for (const stashJsonPath of stashJsonPaths) {
102
- const dirPath = path.dirname(stashJsonPath);
103
- const raw = JSON.parse(fs.readFileSync(stashJsonPath, "utf8"));
104
- if (!raw || !Array.isArray(raw.entries))
105
- continue;
106
- const stash = { entries: raw.entries };
107
- for (const entry of stash.entries) {
108
- const entryPath = entry.filename ? path.join(dirPath, entry.filename) : dirPath;
109
- const entryKey = `${FIXTURE_STASH}:${entry.type}:${entry.name}`;
110
- const searchText = buildSearchText(entry);
111
- let entryWithSize = entry;
112
- try {
113
- const size = fs.statSync(entryPath).size;
114
- entryWithSize = { ...entry, fileSize: size };
115
- }
116
- catch {
117
- // File might not exist for some entries
118
- }
119
- upsertEntry(db, entryKey, dirPath, entryPath, FIXTURE_STASH, entryWithSize, searchText);
120
- }
121
- }
122
- rebuildFts(db);
123
- setMeta(db, "stashDir", FIXTURE_STASH);
124
- setMeta(db, "builtAt", new Date().toISOString());
125
- setMeta(db, "stashDirs", JSON.stringify([FIXTURE_STASH]));
126
- setMeta(db, "hasEmbeddings", "0");
127
- }
128
- finally {
129
- closeDatabase(db);
130
- }
131
- }
132
- function findStashJsonFiles(dir) {
133
- const results = [];
134
- const entries = fs.readdirSync(dir, { withFileTypes: true });
135
- for (const entry of entries) {
136
- const fullPath = path.join(dir, entry.name);
137
- if (entry.isDirectory()) {
138
- results.push(...findStashJsonFiles(fullPath));
139
- }
140
- else if (entry.name === ".stash.json") {
141
- results.push(fullPath);
142
- }
143
- }
144
- return results;
145
- }
146
- // ── Helpers ─────────────────────────────────────────────────────────────────
147
- async function search(query, limit = 20) {
148
- const result = await akmSearch({ query, source: "stash", limit });
149
- return result.hits.filter((h) => h.type !== "registry");
150
- }
151
- function findHit(hits, name) {
152
- return hits.find((h) => h.name === name);
153
- }
154
- /** Assert that a hit exists and return it (avoids non-null assertions). */
155
- function expectHit(hits, name) {
156
- const hit = findHit(hits, name);
157
- expect(hit).toBeDefined();
158
- // biome-ignore lint/style/noNonNullAssertion: guarded by expect above
159
- return hit;
160
- }
161
- /** Get the score of a hit, asserting it is defined. */
162
- function scoreOf(hit) {
163
- expect(hit.score).toBeDefined();
164
- return hit.score ?? 0;
165
- }
166
- function rankOf(hits, name) {
167
- const idx = hits.findIndex((h) => h.name === name);
168
- return idx === -1 ? Infinity : idx + 1; // 1-based rank
169
- }
170
- // ── Tests ───────────────────────────────────────────────────────────────────
171
- describe("Score differentiation", () => {
172
- test('"docker homelab" returns skill:docker-homelab in top 3', async () => {
173
- const hits = await search("docker homelab");
174
- expect(hits.length).toBeGreaterThanOrEqual(2);
175
- // docker-homelab should appear in the top results (within top 3)
176
- // Sub-references also contain "docker-homelab" in their name, so they
177
- // may rank highly on FTS name-field matching.
178
- const skillRank = rankOf(hits, "docker-homelab");
179
- expect(skillRank).toBeLessThanOrEqual(3);
180
- // The skill should have a meaningful score (not RRF-compressed)
181
- const skillHit = expectHit(hits, "docker-homelab");
182
- expect(scoreOf(skillHit)).toBeGreaterThan(0.5);
183
- });
184
- test('"docker" returns docker-homelab and docker-clean', async () => {
185
- const hits = await search("docker");
186
- expect(hits.length).toBeGreaterThanOrEqual(2);
187
- // Both docker-related assets should appear in results
188
- expectHit(hits, "docker-homelab");
189
- expectHit(hits, "docker-clean");
190
- });
191
- test('"svelte component" -> skill:svelte-components ranks #1, above sub-references', async () => {
192
- const hits = await search("svelte component");
193
- expect(hits.length).toBeGreaterThanOrEqual(1);
194
- const skillRank = rankOf(hits, "svelte-components");
195
- expect(skillRank).toBe(1);
196
- // Sub-reference should rank below the skill
197
- const refHit = findHit(hits, "svelte-components/references/web-components");
198
- if (refHit) {
199
- const refRank = rankOf(hits, "svelte-components/references/web-components");
200
- expect(refRank).toBeGreaterThan(skillRank);
201
- }
202
- });
203
- test('"code review" -> command or agent ranks above knowledge docs', async () => {
204
- const hits = await search("code review");
205
- expect(hits.length).toBeGreaterThanOrEqual(2);
206
- // Find the top-ranked actionable asset (skill, command, or agent)
207
- const actionableTypes = new Set(["skill", "command", "agent"]);
208
- const topActionable = hits.find((h) => actionableTypes.has(h.type));
209
- expect(topActionable).toBeDefined();
210
- // Find the top-ranked knowledge doc
211
- const topKnowledge = hits.find((h) => h.type === "knowledge");
212
- if (topKnowledge && topActionable) {
213
- const actionableRank = rankOf(hits, topActionable.name);
214
- const knowledgeRank = rankOf(hits, topKnowledge.name);
215
- expect(actionableRank).toBeLessThan(knowledgeRank);
216
- }
217
- });
218
- test('"mem0 search" -> script:mem0-search ranks #1', async () => {
219
- const hits = await search("mem0 search");
220
- expect(hits.length).toBeGreaterThanOrEqual(1);
221
- expect(rankOf(hits, "mem0-search")).toBe(1);
222
- });
223
- });
224
- describe("Exact/near-exact name matching", () => {
225
- test('"docker-homelab" (exact) -> skill:docker-homelab appears in top 3', async () => {
226
- const hits = await search("docker-homelab");
227
- expect(hits.length).toBeGreaterThanOrEqual(2);
228
- // The skill entry and its sub-references all contain "docker-homelab"
229
- // in their names. The skill gets a name-match boost but sub-references
230
- // also match on FTS name field. Verify the skill is in the top 3.
231
- const skillRank = rankOf(hits, "docker-homelab");
232
- expect(skillRank).toBeLessThanOrEqual(3);
233
- // The skill should have a strong score
234
- const skillHit = expectHit(hits, "docker-homelab");
235
- expect(scoreOf(skillHit)).toBeGreaterThan(0.5);
236
- });
237
- test('"mem0-search" (exact) -> script:mem0-search is #1', async () => {
238
- const hits = await search("mem0-search");
239
- expect(hits.length).toBeGreaterThanOrEqual(1);
240
- expect(hits[0].name).toBe("mem0-search");
241
- });
242
- test('"security-review" (exact) -> command:security-review is #1', async () => {
243
- const hits = await search("security-review");
244
- expect(hits.length).toBeGreaterThanOrEqual(1);
245
- expect(hits[0].name).toBe("security-review");
246
- expect(hits[0].type).toBe("command");
247
- });
248
- test('"k8s-deploy" (exact) -> skill:k8s-deploy is #1', async () => {
249
- const hits = await search("k8s-deploy");
250
- expect(hits.length).toBeGreaterThanOrEqual(1);
251
- expect(hits[0].name).toBe("k8s-deploy");
252
- expect(hits[0].type).toBe("skill");
253
- });
254
- test('"code-reviewer" (exact) -> agent:code-reviewer is #1', async () => {
255
- const hits = await search("code-reviewer");
256
- expect(hits.length).toBeGreaterThanOrEqual(1);
257
- expect(hits[0].name).toBe("code-reviewer");
258
- expect(hits[0].type).toBe("agent");
259
- });
260
- });
261
- describe("Type ranking", () => {
262
- test('for "deploy", skills/commands/scripts rank above knowledge docs', async () => {
263
- const hits = await search("deploy");
264
- expect(hits.length).toBeGreaterThanOrEqual(2);
265
- const actionableTypes = new Set(["skill", "command", "agent", "script"]);
266
- const topActionable = hits.find((h) => actionableTypes.has(h.type));
267
- const topKnowledge = hits.find((h) => h.type === "knowledge");
268
- expect(topActionable).toBeDefined();
269
- if (topKnowledge && topActionable) {
270
- expect(rankOf(hits, topActionable.name)).toBeLessThan(rankOf(hits, topKnowledge.name));
271
- }
272
- });
273
- test('for "review", agents/commands/skills rank above knowledge docs', async () => {
274
- const hits = await search("review");
275
- expect(hits.length).toBeGreaterThanOrEqual(2);
276
- const actionableTypes = new Set(["skill", "command", "agent"]);
277
- const topActionable = hits.find((h) => actionableTypes.has(h.type));
278
- const topKnowledge = hits.find((h) => h.type === "knowledge");
279
- expect(topActionable).toBeDefined();
280
- if (topKnowledge && topActionable) {
281
- expect(rankOf(hits, topActionable.name)).toBeLessThan(rankOf(hits, topKnowledge.name));
282
- }
283
- });
284
- });
285
- describe("Fuzzy/prefix matching", () => {
286
- test('"kube" finds k8s-deploy via alias', async () => {
287
- const hits = await search("kube");
288
- expectHit(hits, "k8s-deploy");
289
- });
290
- test('"dock" finds docker-homelab via prefix', async () => {
291
- const hits = await search("dock");
292
- expectHit(hits, "docker-homelab");
293
- });
294
- test('"incident" finds the runbook', async () => {
295
- const hits = await search("incident");
296
- expectHit(hits, "incident-response-runbook");
297
- });
298
- });
299
- describe("Score preservation (not RRF-flattened)", () => {
300
- test("top result score > 0.5 (not capped at 0.0164)", async () => {
301
- const hits = await search("docker homelab");
302
- expect(hits.length).toBeGreaterThanOrEqual(1);
303
- expect(scoreOf(hits[0])).toBeGreaterThan(0.5);
304
- });
305
- test("top result for exact name query has strong differentiation", async () => {
306
- // Use a query that uniquely targets one asset.
307
- // Per the locked v1 contract (CLAUDE.md / spec §9), SearchHit.score is
308
- // bounded in [0,1]. An exact-name match accumulates large additive
309
- // boosts that are clamped at the final emit step, so the top score for
310
- // a uniquely-matching exact-name query must reach the ceiling (1.0).
311
- const hits = await search("mem0 search");
312
- expect(hits.length).toBeGreaterThanOrEqual(1);
313
- const topScore = scoreOf(hits[0]);
314
- expect(topScore).toBe(1);
315
- // If there are additional results, the top should be at least as high.
316
- // Below-ceiling differentiation is asserted by the broader
317
- // "scores are monotonically decreasing" case below; here we just
318
- // confirm the top hit reaches the maximum.
319
- if (hits.length >= 2) {
320
- expect(topScore).toBeGreaterThanOrEqual(scoreOf(hits[1]));
321
- }
322
- });
323
- test("scores are monotonically decreasing", async () => {
324
- const hits = await search("docker");
325
- for (let i = 1; i < hits.length; i++) {
326
- const prev = hits[i - 1].score ?? 0;
327
- const curr = hits[i].score ?? 0;
328
- expect(prev).toBeGreaterThanOrEqual(curr);
329
- }
330
- });
331
- test("scores are not compressed to a narrow range", async () => {
332
- const hits = await search("docker");
333
- expect(hits.length).toBeGreaterThanOrEqual(3);
334
- const topScore = scoreOf(hits[0]);
335
- const lastScore = scoreOf(hits[hits.length - 1]);
336
- const range = topScore - lastScore;
337
- // Score range should be meaningful, not compressed to ~0.001 like RRF.
338
- // Per the locked v1 contract (CLAUDE.md / spec §9), scores are bounded
339
- // in [0,1] — multiple top hits on a popular query may all clamp to 1.0,
340
- // which collapses the visible top-end differential. The bottom of the
341
- // range still shows clear separation from the top, well above what RRF
342
- // would compress to (~0.001).
343
- expect(range).toBeGreaterThan(0.01);
344
- });
345
- });
346
- describe("Provider merge (score not destroyed)", () => {
347
- test("when additional provider hits exist, local scores are preserved", () => {
348
- const localHits = [
349
- {
350
- type: "skill",
351
- name: "local-skill-1",
352
- path: "/test/skills/local-1/SKILL.md",
353
- ref: "skill:local-skill-1",
354
- origin: null,
355
- score: 2.5,
356
- },
357
- {
358
- type: "command",
359
- name: "local-cmd-1",
360
- path: "/test/commands/local-1.md",
361
- ref: "command:local-cmd-1",
362
- origin: null,
363
- score: 1.8,
364
- },
365
- {
366
- type: "knowledge",
367
- name: "local-doc-1",
368
- path: "/test/knowledge/local-1.md",
369
- ref: "knowledge:local-doc-1",
370
- origin: null,
371
- score: 0.9,
372
- },
373
- ];
374
- const additionalHits = [
375
- {
376
- type: "skill",
377
- name: "remote-skill-1",
378
- path: "/remote/skills/remote-1/SKILL.md",
379
- ref: "skill:remote-skill-1",
380
- origin: "remote",
381
- score: 0.85,
382
- },
383
- ];
384
- const merged = mergeStashHits(localHits, additionalHits, 20);
385
- // Local hits should retain their original scores
386
- const mergedLocal1 = expectHit(merged, "local-skill-1");
387
- expect(mergedLocal1.score).toBe(2.5);
388
- const mergedLocal2 = expectHit(merged, "local-cmd-1");
389
- expect(mergedLocal2.score).toBe(1.8);
390
- });
391
- test("provider hits sort fairly by score alongside local hits", () => {
392
- const localHits = [
393
- {
394
- type: "skill",
395
- name: "local-high",
396
- path: "/test/skills/high/SKILL.md",
397
- ref: "skill:local-high",
398
- origin: null,
399
- score: 2.0,
400
- },
401
- {
402
- type: "skill",
403
- name: "local-low",
404
- path: "/test/skills/low/SKILL.md",
405
- ref: "skill:local-low",
406
- origin: null,
407
- score: 0.5,
408
- },
409
- ];
410
- const additionalHits = [
411
- {
412
- type: "skill",
413
- name: "remote-1",
414
- path: "/remote/skills/1/SKILL.md",
415
- ref: "skill:remote-1",
416
- origin: "remote",
417
- score: 1.0, // Normalized provider score between local-high and local-low
418
- },
419
- ];
420
- const merged = mergeStashHits(localHits, additionalHits, 20);
421
- // Provider hit keeps its original score and sorts by score
422
- const remoteRank = merged.findIndex((h) => h.name === "remote-1") + 1;
423
- const localHighRank = merged.findIndex((h) => h.name === "local-high") + 1;
424
- const localLowRank = merged.findIndex((h) => h.name === "local-low") + 1;
425
- // remote-1 (1.0) should rank between local-high (2.0) and local-low (0.5)
426
- expect(remoteRank).toBeGreaterThan(localHighRank);
427
- expect(remoteRank).toBeLessThan(localLowRank);
428
- });
429
- test("duplicate provider hits are deduplicated (local version wins)", () => {
430
- const localHits = [
431
- {
432
- type: "skill",
433
- name: "shared-skill",
434
- path: "/test/skills/shared/SKILL.md",
435
- ref: "skill:shared-skill",
436
- origin: null,
437
- score: 2.0,
438
- },
439
- ];
440
- const additionalHits = [
441
- {
442
- type: "skill",
443
- name: "shared-skill",
444
- path: "/test/skills/shared/SKILL.md", // Same path = duplicate
445
- ref: "skill:shared-skill",
446
- origin: "remote",
447
- score: 0.5,
448
- },
449
- ];
450
- const merged = mergeStashHits(localHits, additionalHits, 20);
451
- // Only one instance of the shared skill should appear
452
- const sharedHits = merged.filter((h) => h.name === "shared-skill");
453
- expect(sharedHits.length).toBe(1);
454
- // And it should have the local score
455
- expect(sharedHits[0].score).toBe(2.0);
456
- });
457
- test("merge preserves sort order by score descending", () => {
458
- const localHits = [
459
- { type: "skill", name: "a", path: "/a", ref: "skill:a", origin: null, score: 3.0 },
460
- { type: "skill", name: "b", path: "/b", ref: "skill:b", origin: null, score: 1.0 },
461
- ];
462
- const additionalHits = [
463
- { type: "skill", name: "c", path: "/c", ref: "skill:c", origin: "remote", score: 2.0 },
464
- ];
465
- const merged = mergeStashHits(localHits, additionalHits, 20);
466
- for (let i = 1; i < merged.length; i++) {
467
- const prev = merged[i - 1].score ?? 0;
468
- const curr = merged[i].score ?? 0;
469
- expect(prev).toBeGreaterThanOrEqual(curr);
470
- }
471
- });
472
- });
473
- describe("Cross-type search consistency", () => {
474
- test("searching for 'docker' returns docker-homelab and docker-clean", async () => {
475
- const hits = await search("docker");
476
- expect(hits.length).toBeGreaterThanOrEqual(2);
477
- const dockerNames = hits.map((h) => h.name);
478
- expect(dockerNames).toContain("docker-homelab");
479
- expect(dockerNames).toContain("docker-clean");
480
- });
481
- test("multi-word queries narrow results appropriately", async () => {
482
- const narrowHits = await search("deploy check");
483
- // The narrow query should return deploy-check at a high rank
484
- const deployCheckRank = rankOf(narrowHits, "deploy-check");
485
- expect(deployCheckRank).toBeLessThanOrEqual(3);
486
- });
487
- test("searching for svelte returns both skill and agent", async () => {
488
- const hits = await search("svelte");
489
- const svelteNames = hits.map((h) => h.name);
490
- expect(svelteNames).toContain("svelte-components");
491
- expect(svelteNames).toContain("svelte-expert");
492
- });
493
- test("searching for 'release' finds the release-manager command", async () => {
494
- const hits = await search("release");
495
- const releaseHit = expectHit(hits, "release-manager");
496
- expect(releaseHit.type).toBe("command");
497
- });
498
- });
499
- describe("Metadata signal strength", () => {
500
- test("skill with rich metadata appears in results for broad queries", async () => {
501
- // docker-homelab has rich tags, aliases, searchHints, and curated quality
502
- const hits = await search("container management");
503
- const skillHit = expectHit(hits, "docker-homelab");
504
- expect(scoreOf(skillHit)).toBeGreaterThan(0);
505
- });
506
- test("curated quality assets include the curated metadata boost reason", async () => {
507
- const hits = await search("kubernetes deploy");
508
- expect(hits.length).toBeGreaterThanOrEqual(1);
509
- const k8sHit = expectHit(hits, "k8s-deploy");
510
- expect(k8sHit.whyMatched).toContain("curated metadata boost");
511
- });
512
- test("searchHints contribute to matching", async () => {
513
- // "troubleshoot docker" is a search hint on docker-homelab
514
- const hits = await search("troubleshoot docker");
515
- const skillHit = expectHit(hits, "docker-homelab");
516
- expect(skillHit.whyMatched).toContain("matched searchHints");
517
- });
518
- test("aliases contribute to matching", async () => {
519
- // "docker-compose" is an alias for docker-homelab
520
- const hits = await search("docker compose");
521
- const skillHit = expectHit(hits, "docker-homelab");
522
- expect(skillHit.whyMatched).toContain("matched aliases");
523
- });
524
- test("tags contribute to matching", async () => {
525
- const hits = await search("homelab");
526
- const skillHit = expectHit(hits, "docker-homelab");
527
- expect(skillHit.whyMatched).toContain("matched tags");
528
- });
529
- });
530
- describe("Empty and edge case queries", () => {
531
- test("empty query returns all entries", async () => {
532
- const result = await akmSearch({ query: "", source: "stash" });
533
- expect(result.hits.length).toBeGreaterThan(0);
534
- });
535
- test("query with no matches returns empty results with tip", async () => {
536
- const result = await akmSearch({ query: "xyznonexistent123", source: "stash" });
537
- const hits = result.hits.filter((h) => h.type !== "registry");
538
- expect(hits.length).toBe(0);
539
- });
540
- test("single character query returns results when prefix matches", async () => {
541
- // Single char queries are too short for prefix expansion (< 3 chars)
542
- // but may still match on exact tokens
543
- const result = await akmSearch({ query: "k", source: "stash" });
544
- // This may or may not return results depending on FTS tokenizer behavior
545
- // The important thing is it does not crash
546
- expect(result).toBeDefined();
547
- });
548
- });