akm-cli 0.7.0-rc1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/dist/{src/cli.js → cli.js} +100 -16
  2. package/dist/{src/commands → commands}/config-cli.js +42 -0
  3. package/dist/{src/commands → commands}/history.js +78 -7
  4. package/dist/{src/commands → commands}/registry-search.js +69 -6
  5. package/dist/{src/commands → commands}/search.js +30 -3
  6. package/dist/{src/commands → commands}/show.js +29 -0
  7. package/dist/{src/commands → commands}/source-add.js +5 -1
  8. package/dist/{src/commands → commands}/source-manage.js +7 -1
  9. package/dist/{src/core → core}/config.js +28 -0
  10. package/dist/{src/indexer → indexer}/db-search.js +1 -0
  11. package/dist/{src/indexer → indexer}/indexer.js +16 -2
  12. package/dist/{src/indexer → indexer}/matchers.js +1 -1
  13. package/dist/{src/indexer → indexer}/search-source.js +4 -2
  14. package/dist/{src/integrations → integrations}/agent/profiles.js +1 -1
  15. package/dist/{src/integrations → integrations}/agent/spawn.js +67 -16
  16. package/dist/{src/integrations → integrations}/github.js +9 -3
  17. package/dist/{src/llm → llm}/embedders/remote.js +37 -3
  18. package/dist/{src/output → output}/cli-hints.js +15 -2
  19. package/dist/{src/output → output}/renderers.js +3 -1
  20. package/dist/{src/output → output}/shapes.js +8 -1
  21. package/dist/{src/output → output}/text.js +156 -3
  22. package/dist/{src/registry → registry}/build-index.js +5 -4
  23. package/dist/{src/registry → registry}/providers/static-index.js +3 -1
  24. package/dist/{src/setup → setup}/setup.js +9 -0
  25. package/dist/{src/wiki → wiki}/wiki.js +54 -6
  26. package/dist/{src/workflows → workflows}/runs.js +37 -3
  27. package/package.json +8 -8
  28. package/dist/tests/add-website-source.test.js +0 -119
  29. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  30. package/dist/tests/agent/agent-config.test.js +0 -221
  31. package/dist/tests/agent/agent-detect.test.js +0 -100
  32. package/dist/tests/agent/agent-spawn.test.js +0 -234
  33. package/dist/tests/agent-output.test.js +0 -186
  34. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  35. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  36. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  37. package/dist/tests/asset-ref.test.js +0 -192
  38. package/dist/tests/asset-registry.test.js +0 -103
  39. package/dist/tests/asset-spec.test.js +0 -241
  40. package/dist/tests/bench/attribution.test.js +0 -995
  41. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  42. package/dist/tests/bench/cleanup.js +0 -203
  43. package/dist/tests/bench/cleanup.test.js +0 -166
  44. package/dist/tests/bench/cli.js +0 -683
  45. package/dist/tests/bench/cli.test.js +0 -177
  46. package/dist/tests/bench/compare.test.js +0 -556
  47. package/dist/tests/bench/corpus.js +0 -314
  48. package/dist/tests/bench/corpus.test.js +0 -258
  49. package/dist/tests/bench/driver.js +0 -346
  50. package/dist/tests/bench/driver.test.js +0 -443
  51. package/dist/tests/bench/evolve-metrics.js +0 -179
  52. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  53. package/dist/tests/bench/evolve.js +0 -580
  54. package/dist/tests/bench/evolve.test.js +0 -616
  55. package/dist/tests/bench/failure-modes.test.js +0 -300
  56. package/dist/tests/bench/feedback-integrity.test.js +0 -456
  57. package/dist/tests/bench/leakage.test.js +0 -125
  58. package/dist/tests/bench/learning-curve.test.js +0 -133
  59. package/dist/tests/bench/metrics.js +0 -2319
  60. package/dist/tests/bench/metrics.test.js +0 -1144
  61. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  62. package/dist/tests/bench/report.js +0 -1821
  63. package/dist/tests/bench/report.test.js +0 -989
  64. package/dist/tests/bench/runner.js +0 -536
  65. package/dist/tests/bench/runner.test.js +0 -958
  66. package/dist/tests/bench/search-bridge.test.js +0 -331
  67. package/dist/tests/bench/tmp.js +0 -41
  68. package/dist/tests/bench/trajectory.js +0 -116
  69. package/dist/tests/bench/trajectory.test.js +0 -127
  70. package/dist/tests/bench/verifier.js +0 -109
  71. package/dist/tests/bench/verifier.test.js +0 -118
  72. package/dist/tests/bench/workflow-evaluator.js +0 -557
  73. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  74. package/dist/tests/bench/workflow-spec.js +0 -358
  75. package/dist/tests/bench/workflow-spec.test.js +0 -363
  76. package/dist/tests/bench/workflow-trace.js +0 -438
  77. package/dist/tests/bench/workflow-trace.test.js +0 -254
  78. package/dist/tests/benchmark-search-quality.js +0 -536
  79. package/dist/tests/benchmark-suite.js +0 -1441
  80. package/dist/tests/capture-cli.test.js +0 -112
  81. package/dist/tests/cli-errors.test.js +0 -203
  82. package/dist/tests/commands/events.test.js +0 -370
  83. package/dist/tests/commands/history.test.js +0 -223
  84. package/dist/tests/commands/import.test.js +0 -103
  85. package/dist/tests/commands/proposal-cli.test.js +0 -209
  86. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  87. package/dist/tests/commands/remember.test.js +0 -97
  88. package/dist/tests/commands/scope-flags.test.js +0 -300
  89. package/dist/tests/commands/search.test.js +0 -537
  90. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  91. package/dist/tests/commands/show.test.js +0 -294
  92. package/dist/tests/common.test.js +0 -266
  93. package/dist/tests/completions.test.js +0 -142
  94. package/dist/tests/config-cli.test.js +0 -193
  95. package/dist/tests/config-llm-features.test.js +0 -139
  96. package/dist/tests/config.test.js +0 -544
  97. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  98. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  99. package/dist/tests/contracts/spec-helpers.js +0 -46
  100. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  101. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  102. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  103. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  104. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  105. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  106. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  107. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  108. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  109. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  110. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  111. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  112. package/dist/tests/core/write-source.test.js +0 -366
  113. package/dist/tests/curate-command.test.js +0 -87
  114. package/dist/tests/db-scoring.test.js +0 -201
  115. package/dist/tests/db.test.js +0 -654
  116. package/dist/tests/distill-cli-flag.test.js +0 -208
  117. package/dist/tests/distill.test.js +0 -515
  118. package/dist/tests/docker-install.test.js +0 -120
  119. package/dist/tests/e2e.test.js +0 -1398
  120. package/dist/tests/embedder.test.js +0 -340
  121. package/dist/tests/embedding-model-config.test.js +0 -379
  122. package/dist/tests/feedback-command.test.js +0 -172
  123. package/dist/tests/file-context.test.js +0 -552
  124. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  125. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  126. package/dist/tests/fixtures/stashes/load.js +0 -166
  127. package/dist/tests/fixtures/stashes/load.test.js +0 -88
  128. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  129. package/dist/tests/frontmatter.test.js +0 -190
  130. package/dist/tests/fts-field-weighting.test.js +0 -254
  131. package/dist/tests/fuzzy-search.test.js +0 -230
  132. package/dist/tests/git-provider-clone.test.js +0 -45
  133. package/dist/tests/github.test.js +0 -161
  134. package/dist/tests/graph-boost-ranking.test.js +0 -305
  135. package/dist/tests/graph-extraction.test.js +0 -282
  136. package/dist/tests/helpers/usage-events.js +0 -8
  137. package/dist/tests/index-pass-llm.test.js +0 -161
  138. package/dist/tests/indexer.test.js +0 -559
  139. package/dist/tests/info-command.test.js +0 -166
  140. package/dist/tests/init.test.js +0 -69
  141. package/dist/tests/install-script.test.js +0 -246
  142. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  143. package/dist/tests/issue-36-repro.test.js +0 -304
  144. package/dist/tests/issues-191-194.test.js +0 -160
  145. package/dist/tests/lesson-lint.test.js +0 -111
  146. package/dist/tests/llm-client.test.js +0 -115
  147. package/dist/tests/llm-feature-gate.test.js +0 -151
  148. package/dist/tests/llm.test.js +0 -139
  149. package/dist/tests/lockfile.test.js +0 -216
  150. package/dist/tests/manifest.test.js +0 -205
  151. package/dist/tests/markdown.test.js +0 -126
  152. package/dist/tests/matchers-unit.test.js +0 -189
  153. package/dist/tests/memory-inference.test.js +0 -299
  154. package/dist/tests/merge-scoring.test.js +0 -136
  155. package/dist/tests/metadata.test.js +0 -313
  156. package/dist/tests/migration-help.test.js +0 -89
  157. package/dist/tests/origin-resolve.test.js +0 -124
  158. package/dist/tests/output-baseline.test.js +0 -217
  159. package/dist/tests/output-shapes-unit.test.js +0 -476
  160. package/dist/tests/parallel-search.test.js +0 -272
  161. package/dist/tests/parameter-metadata.test.js +0 -365
  162. package/dist/tests/paths.test.js +0 -177
  163. package/dist/tests/progressive-disclosure.test.js +0 -280
  164. package/dist/tests/proposals.test.js +0 -279
  165. package/dist/tests/proposed-quality.test.js +0 -271
  166. package/dist/tests/provider-registry.test.js +0 -32
  167. package/dist/tests/ranking-regression.test.js +0 -548
  168. package/dist/tests/reflect-propose.test.js +0 -455
  169. package/dist/tests/registry-build-index.test.js +0 -378
  170. package/dist/tests/registry-cli.test.js +0 -290
  171. package/dist/tests/registry-index-v2.test.js +0 -430
  172. package/dist/tests/registry-install.test.js +0 -728
  173. package/dist/tests/registry-providers/parity.test.js +0 -189
  174. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  175. package/dist/tests/registry-providers/static-index.test.js +0 -204
  176. package/dist/tests/registry-resolve.test.js +0 -126
  177. package/dist/tests/registry-search.test.js +0 -723
  178. package/dist/tests/remember-frontmatter.test.js +0 -380
  179. package/dist/tests/remember-unit.test.js +0 -123
  180. package/dist/tests/ripgrep-install.test.js +0 -251
  181. package/dist/tests/ripgrep-resolve.test.js +0 -108
  182. package/dist/tests/ripgrep.test.js +0 -163
  183. package/dist/tests/save-command.test.js +0 -94
  184. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  185. package/dist/tests/scoring-pipeline.test.js +0 -648
  186. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  187. package/dist/tests/self-update.test.js +0 -442
  188. package/dist/tests/semantic-search-e2e.test.js +0 -512
  189. package/dist/tests/semantic-status.test.js +0 -471
  190. package/dist/tests/setup-run.integration.js +0 -877
  191. package/dist/tests/setup-wizard.test.js +0 -198
  192. package/dist/tests/setup.test.js +0 -131
  193. package/dist/tests/source-add.test.js +0 -11
  194. package/dist/tests/source-clone.test.js +0 -254
  195. package/dist/tests/source-manage.test.js +0 -366
  196. package/dist/tests/source-providers/filesystem.test.js +0 -82
  197. package/dist/tests/source-providers/git.test.js +0 -252
  198. package/dist/tests/source-providers/website.test.js +0 -128
  199. package/dist/tests/source-qa-fixes.test.js +0 -268
  200. package/dist/tests/source-registry.test.js +0 -350
  201. package/dist/tests/source-resolve.test.js +0 -100
  202. package/dist/tests/source-source.test.js +0 -221
  203. package/dist/tests/source.test.js +0 -533
  204. package/dist/tests/tar-utils-scan.test.js +0 -73
  205. package/dist/tests/toggle-components.test.js +0 -73
  206. package/dist/tests/usage-telemetry.test.js +0 -265
  207. package/dist/tests/utility-scoring.test.js +0 -558
  208. package/dist/tests/vault-load-error.test.js +0 -78
  209. package/dist/tests/vault-qa-fixes.test.js +0 -194
  210. package/dist/tests/vault.test.js +0 -429
  211. package/dist/tests/vector-search.test.js +0 -608
  212. package/dist/tests/walker.test.js +0 -252
  213. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  214. package/dist/tests/wave2-cluster-d.test.js +0 -180
  215. package/dist/tests/wave2-cluster-e.test.js +0 -179
  216. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  217. package/dist/tests/wiki.test.js +0 -529
  218. package/dist/tests/workflow-cli.test.js +0 -271
  219. package/dist/tests/workflow-markdown.test.js +0 -171
  220. package/dist/tests/workflow-path-escape.test.js +0 -132
  221. package/dist/tests/workflow-qa-fixes.test.js +0 -377
  222. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  223. /package/dist/{src/commands → commands}/completions.js +0 -0
  224. /package/dist/{src/commands → commands}/curate.js +0 -0
  225. /package/dist/{src/commands → commands}/distill.js +0 -0
  226. /package/dist/{src/commands → commands}/events.js +0 -0
  227. /package/dist/{src/commands → commands}/info.js +0 -0
  228. /package/dist/{src/commands → commands}/init.js +0 -0
  229. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  230. /package/dist/{src/commands → commands}/installed-stashes.js +0 -0
  231. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  232. /package/dist/{src/commands → commands}/proposal.js +0 -0
  233. /package/dist/{src/commands → commands}/propose.js +0 -0
  234. /package/dist/{src/commands → commands}/reflect.js +0 -0
  235. /package/dist/{src/commands → commands}/remember.js +0 -0
  236. /package/dist/{src/commands → commands}/self-update.js +0 -0
  237. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  238. /package/dist/{src/commands → commands}/vault.js +0 -0
  239. /package/dist/{src/core → core}/asset-ref.js +0 -0
  240. /package/dist/{src/core → core}/asset-registry.js +0 -0
  241. /package/dist/{src/core → core}/asset-spec.js +0 -0
  242. /package/dist/{src/core → core}/common.js +0 -0
  243. /package/dist/{src/core → core}/errors.js +0 -0
  244. /package/dist/{src/core → core}/events.js +0 -0
  245. /package/dist/{src/core → core}/frontmatter.js +0 -0
  246. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  247. /package/dist/{src/core → core}/markdown.js +0 -0
  248. /package/dist/{src/core → core}/paths.js +0 -0
  249. /package/dist/{src/core → core}/proposals.js +0 -0
  250. /package/dist/{src/core → core}/warn.js +0 -0
  251. /package/dist/{src/core → core}/write-source.js +0 -0
  252. /package/dist/{src/indexer → indexer}/db.js +0 -0
  253. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  254. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  255. /package/dist/{src/indexer → indexer}/graph-extraction.js +0 -0
  256. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  257. /package/dist/{src/indexer → indexer}/memory-inference.js +0 -0
  258. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  259. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  260. /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
  261. /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
  262. /package/dist/{src/indexer → indexer}/walker.js +0 -0
  263. /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
  264. /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
  265. /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
  266. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  267. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  268. /package/dist/{src/llm → llm}/client.js +0 -0
  269. /package/dist/{src/llm → llm}/embedder.js +0 -0
  270. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  271. /package/dist/{src/llm → llm}/embedders/local.js +0 -0
  272. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  273. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  274. /package/dist/{src/llm → llm}/graph-extract.js +0 -0
  275. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  276. /package/dist/{src/llm → llm}/memory-infer.js +0 -0
  277. /package/dist/{src/llm → llm}/metadata-enhance.js +0 -0
  278. /package/dist/{src/output → output}/context.js +0 -0
  279. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  280. /package/dist/{src/registry → registry}/factory.js +0 -0
  281. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  282. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  283. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  284. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  285. /package/dist/{src/registry → registry}/resolve.js +0 -0
  286. /package/dist/{src/registry → registry}/types.js +0 -0
  287. /package/dist/{src/setup → setup}/detect.js +0 -0
  288. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  289. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  290. /package/dist/{src/setup → setup}/steps.js +0 -0
  291. /package/dist/{src/sources → sources}/include.js +0 -0
  292. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  293. /package/dist/{src/sources → sources}/provider.js +0 -0
  294. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  295. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  296. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  297. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  298. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  299. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  300. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  301. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  302. /package/dist/{src/sources → sources}/providers/website.js +0 -0
  303. /package/dist/{src/sources → sources}/resolve.js +0 -0
  304. /package/dist/{src/sources → sources}/types.js +0 -0
  305. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  306. /package/dist/{src/version.js → version.js} +0 -0
  307. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  308. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  309. /package/dist/{src/workflows → workflows}/db.js +0 -0
  310. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  311. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  312. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  313. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  314. /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
- });