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,537 +0,0 @@
1
- import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { akmSearch } from "../../src/commands/search";
6
- import { saveConfig } from "../../src/core/config";
7
- import { akmIndex } from "../../src/indexer/indexer";
8
- import { createWiki, stashRaw } from "../../src/wiki/wiki";
9
- // ── Temp directory tracking ─────────────────────────────────────────────────
10
- const createdTmpDirs = [];
11
- function expectDefined(value) {
12
- expect(value).toBeDefined();
13
- if (value === undefined || value === null) {
14
- throw new Error("Expected value to be defined");
15
- }
16
- return value;
17
- }
18
- function createTmpDir(prefix = "akm-search-") {
19
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
20
- createdTmpDirs.push(dir);
21
- return dir;
22
- }
23
- afterAll(() => {
24
- for (const dir of createdTmpDirs) {
25
- fs.rmSync(dir, { recursive: true, force: true });
26
- }
27
- });
28
- // ── Helpers ─────────────────────────────────────────────────────────────────
29
- function writeFile(filePath, content = "") {
30
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
31
- fs.writeFileSync(filePath, content);
32
- }
33
- /**
34
- * Create a stash directory with all required subdirectories.
35
- */
36
- function tmpStash() {
37
- const dir = createTmpDir("akm-search-stash-");
38
- for (const sub of ["skills", "commands", "agents", "knowledge", "scripts"]) {
39
- fs.mkdirSync(path.join(dir, sub), { recursive: true });
40
- }
41
- return dir;
42
- }
43
- /**
44
- * Build an index for a stash directory from a set of files and their content.
45
- * Also writes a config with semanticSearchMode disabled so embedding is not attempted.
46
- */
47
- async function buildTestIndex(stashDir, files) {
48
- for (const [relPath, content] of Object.entries(files)) {
49
- const fullPath = path.join(stashDir, relPath);
50
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
51
- fs.writeFileSync(fullPath, content);
52
- }
53
- process.env.AKM_STASH_DIR = stashDir;
54
- saveConfig({ semanticSearchMode: "off" });
55
- await akmIndex({ stashDir, full: true });
56
- }
57
- // ── Environment isolation ───────────────────────────────────────────────────
58
- const originalXdgCacheHome = process.env.XDG_CACHE_HOME;
59
- const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
60
- const originalAkmStashDir = process.env.AKM_STASH_DIR;
61
- let testCacheDir = "";
62
- let testConfigDir = "";
63
- beforeEach(() => {
64
- testCacheDir = createTmpDir("akm-search-cache-");
65
- testConfigDir = createTmpDir("akm-search-config-");
66
- process.env.XDG_CACHE_HOME = testCacheDir;
67
- process.env.XDG_CONFIG_HOME = testConfigDir;
68
- });
69
- afterEach(() => {
70
- if (originalXdgCacheHome === undefined) {
71
- delete process.env.XDG_CACHE_HOME;
72
- }
73
- else {
74
- process.env.XDG_CACHE_HOME = originalXdgCacheHome;
75
- }
76
- if (originalXdgConfigHome === undefined) {
77
- delete process.env.XDG_CONFIG_HOME;
78
- }
79
- else {
80
- process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
81
- }
82
- if (originalAkmStashDir === undefined) {
83
- delete process.env.AKM_STASH_DIR;
84
- }
85
- else {
86
- process.env.AKM_STASH_DIR = originalAkmStashDir;
87
- }
88
- if (testCacheDir) {
89
- fs.rmSync(testCacheDir, { recursive: true, force: true });
90
- testCacheDir = "";
91
- }
92
- if (testConfigDir) {
93
- fs.rmSync(testConfigDir, { recursive: true, force: true });
94
- testConfigDir = "";
95
- }
96
- });
97
- // ── 2.1 Database search path (FTS scoring) ──────────────────────────────────
98
- describe("Database search path (FTS scoring)", () => {
99
- test("registered external wiki hits use canonical wiki refs and actions", async () => {
100
- const stashDir = tmpStash();
101
- const externalWiki = createTmpDir("akm-search-ext-wiki-");
102
- writeFile(path.join(externalWiki, "tools", "documentation", "how-to", "001-get-started-with-ics-documentation.md"), "---\ndescription: Documentation getting started guide\n---\n# Start\n");
103
- process.env.AKM_STASH_DIR = stashDir;
104
- saveConfig({
105
- semanticSearchMode: "off",
106
- sources: [{ type: "filesystem", path: externalWiki, name: "ics-docs", wikiName: "ics-docs" }],
107
- });
108
- await akmIndex({ stashDir, full: true });
109
- const result = await akmSearch({ query: "documentation", type: "wiki", source: "stash" });
110
- const localHits = result.hits.filter((h) => h.type !== "registry");
111
- const wikiHit = expectDefined(localHits.find((hit) => hit.ref === "wiki:ics-docs/tools/documentation/how-to/001-get-started-with-ics-documentation"));
112
- expect(wikiHit.origin).toBeNull();
113
- expect(wikiHit.action).toBe("akm show wiki:ics-docs/tools/documentation/how-to/001-get-started-with-ics-documentation -> read the wiki page");
114
- });
115
- test("stash-owned raw wiki pages are discoverable in global wiki search", async () => {
116
- const stashDir = tmpStash();
117
- process.env.AKM_STASH_DIR = stashDir;
118
- saveConfig({ semanticSearchMode: "off" });
119
- createWiki(stashDir, "notes");
120
- stashRaw({
121
- stashDir,
122
- wikiName: "notes",
123
- content: "# Hello World\n\nThis raw page should be searchable.\n",
124
- preferredName: "hello-world",
125
- });
126
- await akmIndex({ stashDir, full: true });
127
- const result = await akmSearch({ query: "hello world", type: "wiki", source: "stash" });
128
- const localHits = result.hits.filter((h) => h.type !== "registry");
129
- expect(localHits.some((hit) => hit.ref === "wiki:notes/raw/hello-world")).toBe(true);
130
- });
131
- test("FTS search returns scored results for matching query", async () => {
132
- const stashDir = tmpStash();
133
- writeFile(path.join(stashDir, "scripts", "deploy", "deploy.sh"), "#!/bin/bash\necho deploy\n");
134
- writeFile(path.join(stashDir, "scripts", "deploy", ".stash.json"), JSON.stringify({
135
- entries: [
136
- {
137
- name: "deploy",
138
- type: "script",
139
- description: "Deploy application to production servers",
140
- filename: "deploy.sh",
141
- },
142
- ],
143
- }));
144
- await buildTestIndex(stashDir, {});
145
- const result = await akmSearch({ query: "deploy", source: "local" });
146
- const localHits = result.hits.filter((h) => h.type !== "registry");
147
- expect(localHits.length).toBeGreaterThanOrEqual(1);
148
- const deployHit = localHits.find((h) => h.name === "deploy");
149
- const resolvedDeployHit = expectDefined(deployHit);
150
- expect(resolvedDeployHit.ref).toContain("script:deploy");
151
- expect(resolvedDeployHit.action).toContain("akm show");
152
- expect(resolvedDeployHit.size).toBeDefined();
153
- expect(resolvedDeployHit.score).toBeDefined();
154
- expect(resolvedDeployHit.score).toBeGreaterThan(0);
155
- });
156
- test("FTS search filters by asset type", async () => {
157
- const stashDir = tmpStash();
158
- writeFile(path.join(stashDir, "scripts", "lint", "lint.sh"), "#!/bin/bash\necho lint\n");
159
- writeFile(path.join(stashDir, "scripts", "lint", ".stash.json"), JSON.stringify({
160
- entries: [
161
- {
162
- name: "lint",
163
- type: "script",
164
- description: "Lint source code for errors",
165
- filename: "lint.sh",
166
- },
167
- ],
168
- }));
169
- writeFile(path.join(stashDir, "skills", "code-review", "SKILL.md"), "# Code Review\nReview code for quality.\n");
170
- writeFile(path.join(stashDir, "skills", "code-review", ".stash.json"), JSON.stringify({
171
- entries: [
172
- {
173
- name: "code-review",
174
- type: "skill",
175
- description: "Review code for quality issues",
176
- filename: "SKILL.md",
177
- },
178
- ],
179
- }));
180
- await buildTestIndex(stashDir, {});
181
- const result = await akmSearch({ query: "code", type: "script", source: "local" });
182
- const localHits = result.hits.filter((h) => h.type !== "registry");
183
- for (const hit of localHits) {
184
- expect(hit.type).toBe("script");
185
- }
186
- });
187
- test("empty query returns all entries", async () => {
188
- const stashDir = tmpStash();
189
- writeFile(path.join(stashDir, "scripts", "alpha", "alpha.sh"), "#!/bin/bash\necho alpha\n");
190
- writeFile(path.join(stashDir, "scripts", "alpha", ".stash.json"), JSON.stringify({
191
- entries: [{ name: "alpha", type: "script", description: "Alpha tool", filename: "alpha.sh" }],
192
- }));
193
- writeFile(path.join(stashDir, "scripts", "beta", "beta.sh"), "#!/bin/bash\necho beta\n");
194
- writeFile(path.join(stashDir, "scripts", "beta", ".stash.json"), JSON.stringify({
195
- entries: [{ name: "beta", type: "script", description: "Beta tool", filename: "beta.sh" }],
196
- }));
197
- writeFile(path.join(stashDir, "scripts", "gamma", "gamma.sh"), "#!/bin/bash\necho gamma\n");
198
- writeFile(path.join(stashDir, "scripts", "gamma", ".stash.json"), JSON.stringify({
199
- entries: [{ name: "gamma", type: "script", description: "Gamma tool", filename: "gamma.sh" }],
200
- }));
201
- await buildTestIndex(stashDir, {});
202
- const result = await akmSearch({ query: "", source: "local" });
203
- const localHits = result.hits.filter((h) => h.type !== "registry");
204
- expect(localHits.length).toBe(3);
205
- });
206
- test('query "." enumerates indexed agent assets when the type filter is agent', async () => {
207
- const stashDir = tmpStash();
208
- await buildTestIndex(stashDir, {
209
- "skills/plugins/dotnet-msbuild/agents/msbuild-code-review.agent.md": "---\ndescription: MSBuild code review agent\nmodel: gpt-5\n---\nReview MSBuild files\n",
210
- "skills/plugins/dotnet-msbuild/agents/msbuild.agent.md": "---\ndescription: MSBuild agent\nmodel: gpt-5\n---\nHelp with MSBuild\n",
211
- });
212
- const result = await akmSearch({ query: ".", type: "agent", source: "local" });
213
- const localHits = result.hits.filter((h) => h.type !== "registry");
214
- expect(localHits).toHaveLength(2);
215
- expect(localHits.map((hit) => hit.name).sort()).toEqual([
216
- "skills/plugins/dotnet-msbuild/agents/msbuild-code-review.agent",
217
- "skills/plugins/dotnet-msbuild/agents/msbuild.agent",
218
- ]);
219
- expect(localHits.every((hit) => hit.type === "agent")).toBe(true);
220
- expect(localHits.every((hit) => hit.ref.startsWith("agent:"))).toBe(true);
221
- expect(localHits.map((hit) => hit.ref.slice("agent:".length)).sort()).toEqual(localHits.map((hit) => hit.name).sort());
222
- expect(localHits.every((hit) => hit.path.endsWith(".agent.md"))).toBe(true);
223
- });
224
- test("limit parameter caps results", async () => {
225
- const stashDir = tmpStash();
226
- const names = ["aaa", "bbb", "ccc", "ddd", "eee"];
227
- for (const name of names) {
228
- writeFile(path.join(stashDir, "scripts", name, `${name}.sh`), `#!/bin/bash\necho ${name}\n`);
229
- writeFile(path.join(stashDir, "scripts", name, ".stash.json"), JSON.stringify({
230
- entries: [{ name, type: "script", description: `${name} tool for testing`, filename: `${name}.sh` }],
231
- }));
232
- }
233
- await buildTestIndex(stashDir, {});
234
- const result = await akmSearch({ query: "", limit: 3, source: "local" });
235
- const localHits = result.hits.filter((h) => h.type !== "registry");
236
- expect(localHits.length).toBe(3);
237
- });
238
- test("scores use multiplicative boosts without clamping", async () => {
239
- const stashDir = tmpStash();
240
- // Create an entry with tags, searchHints, and name all matching the query
241
- writeFile(path.join(stashDir, "scripts", "clamp-deploy", "clamp-deploy.sh"), "#!/bin/bash\necho deploy\n");
242
- writeFile(path.join(stashDir, "scripts", "clamp-deploy", ".stash.json"), JSON.stringify({
243
- entries: [
244
- {
245
- name: "clamp-deploy",
246
- type: "script",
247
- description: "Deploy deploy deploy application",
248
- tags: ["deploy", "deployment"],
249
- searchHints: ["deploy services", "deploy to production"],
250
- filename: "clamp-deploy.sh",
251
- },
252
- ],
253
- }));
254
- await buildTestIndex(stashDir, {});
255
- const result = await akmSearch({ query: "deploy", source: "local" });
256
- expect(result.hits.length).toBeGreaterThan(0);
257
- const deployHit = result.hits.find((h) => h.name === "clamp-deploy");
258
- const resolvedDeployHit = expectDefined(deployHit);
259
- expect(resolvedDeployHit.score).toBeDefined();
260
- expect(resolvedDeployHit.score).toBeGreaterThan(0);
261
- });
262
- });
263
- // ── 2.2 Score boosts ────────────────────────────────────────────────────────
264
- describe("Score boosts", () => {
265
- test("tag match boosts score", async () => {
266
- const stashDir = tmpStash();
267
- writeFile(path.join(stashDir, "scripts", "deploy", "deploy.sh"), "#!/bin/bash\necho deploy\n");
268
- writeFile(path.join(stashDir, "scripts", "deploy", ".stash.json"), JSON.stringify({
269
- entries: [
270
- {
271
- name: "deploy",
272
- type: "script",
273
- description: "Deploy application",
274
- tags: ["deploy", "production"],
275
- filename: "deploy.sh",
276
- },
277
- ],
278
- }));
279
- await buildTestIndex(stashDir, {});
280
- const result = await akmSearch({ query: "deploy", source: "local" });
281
- const localHits = result.hits.filter((h) => h.type !== "registry");
282
- const deployHit = localHits.find((h) => h.name === "deploy");
283
- const resolvedDeployHit = expectDefined(deployHit);
284
- expect(resolvedDeployHit.whyMatched).toBeDefined();
285
- expect(resolvedDeployHit.whyMatched).toContain("matched tags");
286
- });
287
- test("name match boosts score", async () => {
288
- const stashDir = tmpStash();
289
- writeFile(path.join(stashDir, "scripts", "formatter", "formatter.sh"), "#!/bin/bash\necho format\n");
290
- writeFile(path.join(stashDir, "scripts", "formatter", ".stash.json"), JSON.stringify({
291
- entries: [
292
- {
293
- name: "formatter",
294
- type: "script",
295
- description: "Format source files",
296
- filename: "formatter.sh",
297
- },
298
- ],
299
- }));
300
- await buildTestIndex(stashDir, {});
301
- const result = await akmSearch({ query: "formatter", source: "local" });
302
- const localHits = result.hits.filter((h) => h.type !== "registry");
303
- const hit = localHits.find((h) => h.name === "formatter");
304
- const resolvedHit = expectDefined(hit);
305
- expect(resolvedHit.whyMatched).toBeDefined();
306
- // Exact name match is now reported as "exact name match" or "near-exact name match"
307
- const hasNameMatch = resolvedHit.whyMatched?.some((r) => r.includes("name match") || r.includes("name tokens"));
308
- expect(hasNameMatch).toBe(true);
309
- });
310
- test("curated metadata gets quality boost", async () => {
311
- const stashDir = tmpStash();
312
- // Curated entry (quality absent or "curated")
313
- writeFile(path.join(stashDir, "scripts", "curated", "curated.sh"), "#!/bin/bash\necho curated\n");
314
- writeFile(path.join(stashDir, "scripts", "curated", ".stash.json"), JSON.stringify({
315
- entries: [
316
- {
317
- name: "curated",
318
- type: "script",
319
- description: "A testing utility",
320
- quality: "curated",
321
- filename: "curated.sh",
322
- },
323
- ],
324
- }));
325
- // Generated entry — identical description so FTS score is the same;
326
- // only the `quality` field differs, isolating the curated boost.
327
- writeFile(path.join(stashDir, "scripts", "generated", "generated.sh"), "#!/bin/bash\necho generated\n");
328
- writeFile(path.join(stashDir, "scripts", "generated", ".stash.json"), JSON.stringify({
329
- entries: [
330
- {
331
- name: "generated",
332
- type: "script",
333
- description: "A testing utility",
334
- quality: "generated",
335
- filename: "generated.sh",
336
- },
337
- ],
338
- }));
339
- await buildTestIndex(stashDir, {});
340
- const result = await akmSearch({ query: "testing utility", source: "local" });
341
- const localHits = result.hits.filter((h) => h.type !== "registry");
342
- const curatedHit = localHits.find((h) => h.name === "curated");
343
- const generatedHit = localHits.find((h) => h.name === "generated");
344
- const resolvedCuratedHit = expectDefined(curatedHit);
345
- const resolvedGeneratedHit = expectDefined(generatedHit);
346
- expect(resolvedCuratedHit.score).toBeDefined();
347
- expect(resolvedGeneratedHit.score).toBeDefined();
348
- // Scores are rounded to 2 decimal places, so small boosts may tie.
349
- // Verify curated ranks at least as high (sort order preserves pre-rounding order).
350
- expect(resolvedCuratedHit.score).toBeGreaterThanOrEqual(resolvedGeneratedHit.score);
351
- expect(resolvedCuratedHit.whyMatched).toBeDefined();
352
- expect(resolvedCuratedHit.whyMatched).toContain("curated metadata boost");
353
- });
354
- });
355
- // ── 2.3 Substring fallback ──────────────────────────────────────────────────
356
- describe("Substring fallback", () => {
357
- test("falls back to substring search when no index exists", async () => {
358
- const stashDir = tmpStash();
359
- // Do NOT call akmIndex — just create files on disk
360
- writeFile(path.join(stashDir, "scripts", "deploy", "deploy.sh"), "#!/bin/bash\necho deploy\n");
361
- process.env.AKM_STASH_DIR = stashDir;
362
- saveConfig({ semanticSearchMode: "off" });
363
- const result = await akmSearch({ query: "deploy", source: "local" });
364
- const localHits = result.hits.filter((h) => h.type !== "registry");
365
- expect(localHits.length).toBeGreaterThanOrEqual(1);
366
- const deployHit = localHits.find((h) => h.name.includes("deploy"));
367
- expect(deployHit).toBeDefined();
368
- // Substring fallback computes a relevance score but has no whyMatched
369
- expect(deployHit?.score).toBeGreaterThan(0);
370
- expect(deployHit?.score).toBeLessThanOrEqual(1);
371
- expect(deployHit?.whyMatched).toBeUndefined();
372
- });
373
- test("substring search is case-insensitive", async () => {
374
- const stashDir = tmpStash();
375
- writeFile(path.join(stashDir, "scripts", "Deploy", "Deploy.sh"), "#!/bin/bash\necho deploy\n");
376
- process.env.AKM_STASH_DIR = stashDir;
377
- saveConfig({ semanticSearchMode: "off" });
378
- // Do NOT call akmIndex
379
- const result = await akmSearch({ query: "deploy", source: "local" });
380
- const localHits = result.hits.filter((h) => h.type !== "registry");
381
- expect(localHits.length).toBeGreaterThanOrEqual(1);
382
- const hit = localHits.find((h) => h.name.toLowerCase().includes("deploy"));
383
- expect(hit).toBeDefined();
384
- });
385
- test("substring fallback searches descriptions and returns them", async () => {
386
- const stashDir = tmpStash();
387
- writeFile(path.join(stashDir, "agents", "agentic-systems-architect.md"), "---\ndescription: Designs agent coordination patterns and context assembly\n---\nYou are an architect.\n");
388
- process.env.AKM_STASH_DIR = stashDir;
389
- saveConfig({ semanticSearchMode: "off" });
390
- const result = await akmSearch({ query: "coordination", type: "agent", source: "local" });
391
- const localHits = result.hits.filter((h) => h.type !== "registry");
392
- expect(localHits).toHaveLength(1);
393
- expect(localHits[0]?.name).toBe("agentic-systems-architect");
394
- expect(localHits[0]?.description).toContain("agent coordination patterns");
395
- });
396
- test("nested markdown files under agents/ are indexed as agent assets", async () => {
397
- const stashDir = tmpStash();
398
- writeFile(path.join(stashDir, "agent-stash", "agents", "blog", "topic-discovery.md"), [
399
- "---",
400
- "type: agent",
401
- "mode: subagent",
402
- "description: Discovers blog topics from source material",
403
- "---",
404
- "You are a blog topic discovery agent.",
405
- ].join("\n"));
406
- process.env.AKM_STASH_DIR = stashDir;
407
- saveConfig({ semanticSearchMode: "off" });
408
- const result = await akmSearch({ query: "blog topics", type: "agent", source: "local" });
409
- const localHits = result.hits.filter((h) => h.type !== "registry");
410
- expect(localHits).toHaveLength(1);
411
- expect(localHits[0]?.type).toBe("agent");
412
- expect(localHits[0]?.name).toBe("agent-stash/agents/blog/topic-discovery");
413
- expect(localHits[0]?.description).toContain("blog topics");
414
- expect(localHits[0]?.ref).toBe("agent:agent-stash/agents/blog/topic-discovery");
415
- });
416
- test("substring fallback honors curated .stash.json metadata", async () => {
417
- const stashDir = tmpStash();
418
- writeFile(path.join(stashDir, "scripts", "doctor", "doctor.sh"), "#!/bin/bash\necho doctor\n");
419
- writeFile(path.join(stashDir, "scripts", "doctor", ".stash.json"), JSON.stringify({
420
- entries: [
421
- {
422
- name: "doctor",
423
- type: "script",
424
- description: "Diagnose workspace health issues",
425
- tags: ["health", "diagnostics"],
426
- filename: "doctor.sh",
427
- },
428
- ],
429
- }));
430
- process.env.AKM_STASH_DIR = stashDir;
431
- saveConfig({ semanticSearchMode: "off" });
432
- const result = await akmSearch({ query: "diagnostics", source: "local" });
433
- const localHits = result.hits.filter((h) => h.type !== "registry");
434
- const doctorHit = localHits.find((h) => h.name === "doctor");
435
- expect(doctorHit).toBeDefined();
436
- expect(doctorHit?.description).toBe("Diagnose workspace health issues");
437
- expect(doctorHit?.tags).toContain("diagnostics");
438
- });
439
- });
440
- // ── 2.4 Source filtering ────────────────────────────────────────────────────
441
- describe("Source filtering", () => {
442
- test("source: local skips registry search", async () => {
443
- const stashDir = tmpStash();
444
- writeFile(path.join(stashDir, "scripts", "local-tool", "local-tool.sh"), "#!/bin/bash\necho local\n");
445
- writeFile(path.join(stashDir, "scripts", "local-tool", ".stash.json"), JSON.stringify({
446
- entries: [
447
- {
448
- name: "local-tool",
449
- type: "script",
450
- description: "A local tool",
451
- filename: "local-tool.sh",
452
- },
453
- ],
454
- }));
455
- await buildTestIndex(stashDir, {});
456
- const result = await akmSearch({ query: "local", source: "local" });
457
- // All hits should be local, no registry hits
458
- for (const hit of result.hits) {
459
- expect(hit.type).not.toBe("registry");
460
- if (hit.type !== "registry") {
461
- expect(hit.origin).toBeNull();
462
- expect(hit.action).toContain("akm show");
463
- }
464
- }
465
- // No warnings from registry search failures
466
- expect(result.warnings).toBeUndefined();
467
- });
468
- test("source: registry skips local search", async () => {
469
- const stashDir = createTmpDir();
470
- // Create a local tool so we know local hits would exist if local were searched
471
- writeFile(path.join(stashDir, "scripts", "deploy.sh"), "#!/bin/bash\necho deploy\n");
472
- process.env.AKM_STASH_DIR = stashDir;
473
- saveConfig({ semanticSearchMode: "off", registries: [] });
474
- const result = await akmSearch({ query: "deploy", source: "registry" });
475
- // Registry source puts results in registryHits, hits is empty
476
- expect(result.source).toBe("registry");
477
- expect(result.hits.length).toBe(0);
478
- if (result.registryHits) {
479
- for (const hit of result.registryHits) {
480
- expect(hit.type).toBe("registry");
481
- }
482
- }
483
- });
484
- test("source: both includes local hits without crashing", async () => {
485
- const stashDir = createTmpDir();
486
- writeFile(path.join(stashDir, "scripts", "merge-test.sh"), "#!/bin/bash\necho merge\n");
487
- await buildTestIndex(stashDir, {});
488
- saveConfig({ semanticSearchMode: "off", registries: [] });
489
- const result = await akmSearch({ query: "merge", source: "both" });
490
- expect(result.source).toBe("both");
491
- // Should have at least the local hit
492
- const localHits = result.hits.filter((h) => h.type !== "registry");
493
- expect(localHits.length).toBeGreaterThan(0);
494
- });
495
- });
496
- // ── 2.5 Edge cases ──────────────────────────────────────────────────────────
497
- describe("Edge cases", () => {
498
- test("search with special characters does not crash", async () => {
499
- const stashDir = tmpStash();
500
- writeFile(path.join(stashDir, "scripts", "safe", "safe.sh"), "#!/bin/bash\necho safe\n");
501
- writeFile(path.join(stashDir, "scripts", "safe", ".stash.json"), JSON.stringify({
502
- entries: [
503
- {
504
- name: "safe",
505
- type: "script",
506
- description: "A safe tool",
507
- filename: "safe.sh",
508
- },
509
- ],
510
- }));
511
- await buildTestIndex(stashDir, {});
512
- // Should not throw
513
- const result = await akmSearch({ query: "<script>", source: "local" });
514
- expect(result).toBeDefined();
515
- expect(result.hits).toBeDefined();
516
- });
517
- test("search with very long query", async () => {
518
- const stashDir = tmpStash();
519
- writeFile(path.join(stashDir, "scripts", "simple", "simple.sh"), "#!/bin/bash\necho simple\n");
520
- writeFile(path.join(stashDir, "scripts", "simple", ".stash.json"), JSON.stringify({
521
- entries: [
522
- {
523
- name: "simple",
524
- type: "script",
525
- description: "A simple tool",
526
- filename: "simple.sh",
527
- },
528
- ],
529
- }));
530
- await buildTestIndex(stashDir, {});
531
- const longQuery = "a".repeat(10_000);
532
- // Should not throw
533
- const result = await akmSearch({ query: longQuery, source: "local" });
534
- expect(result).toBeDefined();
535
- expect(result.hits).toBeDefined();
536
- });
537
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * Parity regression for Phase 4 (spec §10 step 4 / §6.2).
3
- *
4
- * `akm show` now consults `indexer.lookup(ref)` first, then reads the file
5
- * from disk. The risk called out in the v1 implementation plan is that
6
- * origin-prefixed refs (e.g. `local//skill:foo`) silently regress when the
7
- * indexer is consulted instead of the directory walker.
8
- *
9
- * This test pins both forms — bare ref and origin-prefixed ref — and asserts
10
- * that `indexer.lookup` returns the same on-disk path that `akmShowUnified`
11
- * resolves to. If a future refactor changes how the indexer keys assets, this
12
- * test fails fast instead of silently breaking show for installed sources.
13
- */
14
- import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
15
- import fs from "node:fs";
16
- import os from "node:os";
17
- import path from "node:path";
18
- import { akmShowUnified } from "../../src/commands/show";
19
- import { parseAssetRef } from "../../src/core/asset-ref";
20
- import { resetConfigCache, saveConfig } from "../../src/core/config";
21
- import { akmIndex, lookup } from "../../src/indexer/indexer";
22
- import "../../src/sources/providers/index";
23
- const createdTmpDirs = [];
24
- function createTmpDir(prefix = "akm-parity-") {
25
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
26
- createdTmpDirs.push(dir);
27
- return dir;
28
- }
29
- function writeFile(filePath, content) {
30
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
31
- fs.writeFileSync(filePath, content, "utf8");
32
- }
33
- const originalXdgCacheHome = process.env.XDG_CACHE_HOME;
34
- const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
35
- const originalStashDir = process.env.AKM_STASH_DIR;
36
- let stashDir = "";
37
- beforeEach(() => {
38
- process.env.XDG_CACHE_HOME = createTmpDir("akm-parity-cache-");
39
- process.env.XDG_CONFIG_HOME = createTmpDir("akm-parity-config-");
40
- stashDir = createTmpDir("akm-parity-stash-");
41
- for (const sub of ["scripts", "skills", "commands", "agents", "knowledge"]) {
42
- fs.mkdirSync(path.join(stashDir, sub), { recursive: true });
43
- }
44
- process.env.AKM_STASH_DIR = stashDir;
45
- resetConfigCache();
46
- saveConfig({ semanticSearchMode: "off" });
47
- resetConfigCache();
48
- });
49
- afterEach(() => {
50
- if (originalXdgCacheHome === undefined)
51
- delete process.env.XDG_CACHE_HOME;
52
- else
53
- process.env.XDG_CACHE_HOME = originalXdgCacheHome;
54
- if (originalXdgConfigHome === undefined)
55
- delete process.env.XDG_CONFIG_HOME;
56
- else
57
- process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
58
- if (originalStashDir === undefined)
59
- delete process.env.AKM_STASH_DIR;
60
- else
61
- process.env.AKM_STASH_DIR = originalStashDir;
62
- });
63
- afterAll(() => {
64
- for (const dir of createdTmpDirs) {
65
- fs.rmSync(dir, { recursive: true, force: true });
66
- }
67
- });
68
- describe("Phase 4 parity: indexer.lookup ↔ akmShowUnified", () => {
69
- test("indexed asset: lookup() returns the same file akmShow renders", async () => {
70
- const skillBody = [
71
- "---",
72
- "name: parity-skill",
73
- "description: A skill used to verify Phase 4 parity",
74
- "---",
75
- "# Parity skill",
76
- "",
77
- "Body content used by the parity test.",
78
- ].join("\n");
79
- // Skills live at skills/<name>/SKILL.md (see asset-spec.ts)
80
- writeFile(path.join(stashDir, "skills", "parity-skill", "SKILL.md"), skillBody);
81
- await akmIndex({ stashDir, full: true });
82
- const ref = "skill:parity-skill";
83
- const parsed = parseAssetRef(ref);
84
- const indexed = await lookup(parsed);
85
- expect(indexed).not.toBeNull();
86
- if (!indexed)
87
- return;
88
- expect(indexed.type).toBe("skill");
89
- expect(indexed.name).toBe("parity-skill");
90
- // Reading the indexer-resolved path should yield the on-disk content.
91
- const fileBody = fs.readFileSync(indexed.filePath, "utf8");
92
- expect(fileBody).toBe(skillBody);
93
- // akmShow returns the same path in its rendered response.
94
- const shown = await akmShowUnified({ ref });
95
- expect(shown.path).toBe(indexed.filePath);
96
- });
97
- test("origin-prefixed ref: local//skill:foo resolves to primary stash path", async () => {
98
- const body = ["---", "name: origin-skill", "description: Test", "---", "# origin"].join("\n");
99
- writeFile(path.join(stashDir, "skills", "origin-skill", "SKILL.md"), body);
100
- await akmIndex({ stashDir, full: true });
101
- const bare = await lookup(parseAssetRef("skill:origin-skill"));
102
- const local = await lookup(parseAssetRef("local//skill:origin-skill"));
103
- expect(bare).not.toBeNull();
104
- expect(local).not.toBeNull();
105
- expect(local?.filePath).toBe(bare?.filePath);
106
- // Show parity for both ref forms.
107
- const shownBare = await akmShowUnified({ ref: "skill:origin-skill" });
108
- const shownLocal = await akmShowUnified({ ref: "local//skill:origin-skill" });
109
- expect(shownBare.path).toBe(shownLocal.path);
110
- expect(shownBare.path).toBe(bare?.filePath);
111
- });
112
- test("missing asset: lookup returns null", async () => {
113
- await akmIndex({ stashDir, full: true });
114
- const result = await lookup(parseAssetRef("skill:does-not-exist"));
115
- expect(result).toBeNull();
116
- });
117
- });