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,654 +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 { closeDatabase, DB_VERSION, deleteEntriesByDir, getAllEntries, getEntriesByDir, getEntryById, getEntryCount, getMeta, isVecAvailable, openDatabase, rebuildFts, searchFts, searchVec, setMeta, upsertEmbedding, upsertEntry, } from "../src/indexer/db";
6
- // ── Temp directory management ───────────────────────────────────────────────
7
- const createdTmpDirs = [];
8
- function tmpDir(label = "db") {
9
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), `akm-${label}-`));
10
- createdTmpDirs.push(dir);
11
- return dir;
12
- }
13
- function tmpDbPath(label = "db") {
14
- const dir = tmpDir(label);
15
- return path.join(dir, "test.db");
16
- }
17
- afterAll(() => {
18
- for (const dir of createdTmpDirs) {
19
- fs.rmSync(dir, { recursive: true, force: true });
20
- }
21
- });
22
- // ── Environment isolation ───────────────────────────────────────────────────
23
- const savedEnv = {};
24
- beforeEach(() => {
25
- savedEnv.XDG_CACHE_HOME = process.env.XDG_CACHE_HOME;
26
- savedEnv.XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME;
27
- process.env.XDG_CACHE_HOME = tmpDir("cache");
28
- process.env.XDG_CONFIG_HOME = tmpDir("config");
29
- });
30
- afterEach(() => {
31
- for (const [key, val] of Object.entries(savedEnv)) {
32
- if (val === undefined) {
33
- delete process.env[key];
34
- }
35
- else {
36
- process.env[key] = val;
37
- }
38
- }
39
- });
40
- // ── Helpers ─────────────────────────────────────────────────────────────────
41
- function makeEntry(overrides) {
42
- return {
43
- description: "A test entry",
44
- ...overrides,
45
- };
46
- }
47
- function insertTestEntry(db, key, opts) {
48
- const type = opts?.type ?? "script";
49
- const entry = makeEntry({ name: key, type, description: opts?.description ?? `Description for ${key}` });
50
- return upsertEntry(db, key, opts?.dirPath ?? "/test/dir", opts?.filePath ?? `/test/dir/${key}.ts`, opts?.stashDir ?? "/test/stash", entry, opts?.searchText ?? `${key} ${entry.description}`);
51
- }
52
- // ── Section 1.1: Schema ────────────────────────────────────────────────────
53
- describe("Schema", () => {
54
- test("openDatabase creates schema with correct version", () => {
55
- const dbPath = tmpDbPath();
56
- const db = openDatabase(dbPath);
57
- try {
58
- expect(getMeta(db, "version")).toBe(String(DB_VERSION));
59
- }
60
- finally {
61
- closeDatabase(db);
62
- }
63
- });
64
- test("openDatabase with mismatched version drops and recreates tables", () => {
65
- const dbPath = tmpDbPath();
66
- // Open and insert some data, then tamper with the version
67
- let db = openDatabase(dbPath);
68
- insertTestEntry(db, "old-entry");
69
- expect(getEntryCount(db)).toBe(1);
70
- setMeta(db, "version", "0");
71
- closeDatabase(db);
72
- // Reopen — should detect mismatch, drop tables, recreate
73
- db = openDatabase(dbPath);
74
- try {
75
- expect(getMeta(db, "version")).toBe(String(DB_VERSION));
76
- expect(getEntryCount(db)).toBe(0);
77
- }
78
- finally {
79
- closeDatabase(db);
80
- }
81
- });
82
- test("openDatabase creates FTS5 table", () => {
83
- const dbPath = tmpDbPath();
84
- const db = openDatabase(dbPath);
85
- try {
86
- const row = db.prepare("SELECT name FROM sqlite_master WHERE name = 'entries_fts'").get();
87
- expect(row).toBeDefined();
88
- expect(row?.name).toBe("entries_fts");
89
- }
90
- finally {
91
- closeDatabase(db);
92
- }
93
- });
94
- test("isVecAvailable returns true when sqlite-vec is installed", () => {
95
- const dbPath = tmpDbPath();
96
- const db = openDatabase(dbPath);
97
- try {
98
- expect(isVecAvailable(db)).toBe(true);
99
- }
100
- finally {
101
- closeDatabase(db);
102
- }
103
- });
104
- test("embeddingDim is stored and triggers vec table recreation", () => {
105
- const dbPath = tmpDbPath();
106
- let db = openDatabase(dbPath, { embeddingDim: 512 });
107
- try {
108
- if (isVecAvailable(db)) {
109
- expect(getMeta(db, "embeddingDim")).toBe("512");
110
- }
111
- }
112
- finally {
113
- closeDatabase(db);
114
- }
115
- // Reopen with a different dimension
116
- db = openDatabase(dbPath, { embeddingDim: 768 });
117
- try {
118
- if (isVecAvailable(db)) {
119
- expect(getMeta(db, "embeddingDim")).toBe("768");
120
- }
121
- }
122
- finally {
123
- closeDatabase(db);
124
- }
125
- });
126
- });
127
- // ── Section 1.2: Entry CRUD ────────────────────────────────────────────────
128
- describe("Entry CRUD", () => {
129
- test("upsertEntry inserts a new entry and returns its id", () => {
130
- const db = openDatabase(tmpDbPath());
131
- try {
132
- const id = insertTestEntry(db, "my-tool");
133
- expect(id).toBeGreaterThan(0);
134
- expect(getEntryCount(db)).toBe(1);
135
- }
136
- finally {
137
- closeDatabase(db);
138
- }
139
- });
140
- test("upsertEntry updates on conflict (same entry_key)", () => {
141
- const db = openDatabase(tmpDbPath());
142
- try {
143
- insertTestEntry(db, "my-tool", { description: "original description" });
144
- expect(getEntryCount(db)).toBe(1);
145
- // Upsert with updated description
146
- insertTestEntry(db, "my-tool", { description: "updated description" });
147
- expect(getEntryCount(db)).toBe(1);
148
- // Verify the entry reflects the update
149
- const entries = getAllEntries(db);
150
- expect(entries).toHaveLength(1);
151
- expect(entries[0].entry.description).toBe("updated description");
152
- }
153
- finally {
154
- closeDatabase(db);
155
- }
156
- });
157
- test("getEntryById returns the entry or undefined", () => {
158
- const db = openDatabase(tmpDbPath());
159
- try {
160
- const id = insertTestEntry(db, "fetch-tool", { description: "Fetches data" });
161
- const result = getEntryById(db, id);
162
- expect(result).toBeDefined();
163
- expect(result?.entry.name).toBe("fetch-tool");
164
- expect(result?.entry.description).toBe("Fetches data");
165
- expect(result?.filePath).toBe("/test/dir/fetch-tool.ts");
166
- // Non-existent ID
167
- const missing = getEntryById(db, 99999);
168
- expect(missing).toBeUndefined();
169
- }
170
- finally {
171
- closeDatabase(db);
172
- }
173
- });
174
- test("getEntriesByDir returns entries for a directory", () => {
175
- const db = openDatabase(tmpDbPath());
176
- try {
177
- insertTestEntry(db, "tool-a", { dirPath: "/project/alpha" });
178
- insertTestEntry(db, "tool-b", { dirPath: "/project/alpha" });
179
- insertTestEntry(db, "tool-c", { dirPath: "/project/beta" });
180
- const alphaEntries = getEntriesByDir(db, "/project/alpha");
181
- expect(alphaEntries).toHaveLength(2);
182
- const keys = alphaEntries.map((e) => e.entryKey).sort();
183
- expect(keys).toEqual(["tool-a", "tool-b"]);
184
- const betaEntries = getEntriesByDir(db, "/project/beta");
185
- expect(betaEntries).toHaveLength(1);
186
- expect(betaEntries[0].entryKey).toBe("tool-c");
187
- }
188
- finally {
189
- closeDatabase(db);
190
- }
191
- });
192
- test("getAllEntries returns all entries", () => {
193
- const db = openDatabase(tmpDbPath());
194
- try {
195
- insertTestEntry(db, "entry-1");
196
- insertTestEntry(db, "entry-2");
197
- insertTestEntry(db, "entry-3");
198
- const all = getAllEntries(db);
199
- expect(all).toHaveLength(3);
200
- }
201
- finally {
202
- closeDatabase(db);
203
- }
204
- });
205
- test("getAllEntries with type filter", () => {
206
- const db = openDatabase(tmpDbPath());
207
- try {
208
- insertTestEntry(db, "script-1", { type: "script" });
209
- insertTestEntry(db, "script-2", { type: "script" });
210
- insertTestEntry(db, "skill-1", { type: "skill" });
211
- const scripts = getAllEntries(db, "script");
212
- expect(scripts).toHaveLength(2);
213
- for (const t of scripts) {
214
- expect(t.entry.type).toBe("script");
215
- }
216
- const skills = getAllEntries(db, "skill");
217
- expect(skills).toHaveLength(1);
218
- expect(skills[0].entry.type).toBe("skill");
219
- }
220
- finally {
221
- closeDatabase(db);
222
- }
223
- });
224
- test("deleteEntriesByDir removes entries", () => {
225
- const db = openDatabase(tmpDbPath());
226
- try {
227
- insertTestEntry(db, "del-1", { dirPath: "/to-delete" });
228
- insertTestEntry(db, "del-2", { dirPath: "/to-delete" });
229
- insertTestEntry(db, "keep-1", { dirPath: "/to-keep" });
230
- expect(getEntryCount(db)).toBe(3);
231
- deleteEntriesByDir(db, "/to-delete");
232
- expect(getEntryCount(db)).toBe(1);
233
- const remaining = getAllEntries(db);
234
- expect(remaining[0].entryKey).toBe("keep-1");
235
- }
236
- finally {
237
- closeDatabase(db);
238
- }
239
- });
240
- });
241
- // ── Section 1.3: FTS search ────────────────────────────────────────────────
242
- describe("FTS search", () => {
243
- test("searchFts returns results ranked by BM25", () => {
244
- const db = openDatabase(tmpDbPath());
245
- try {
246
- insertTestEntry(db, "deploy-tool", {
247
- description: "Deploy applications to production servers",
248
- searchText: "deploy deploy deploy applications production servers deployment",
249
- });
250
- insertTestEntry(db, "infra-tool", {
251
- description: "Cloud infrastructure for deploy pipelines",
252
- searchText: "cloud infrastructure management scaling networking deploy pipelines automation",
253
- });
254
- rebuildFts(db);
255
- const results = searchFts(db, "deploy", 10);
256
- expect(results.length).toBe(2);
257
- expect(results[0].entry.name).toBe("deploy-tool");
258
- expect(results[1].entry.name).toBe("infra-tool");
259
- }
260
- finally {
261
- closeDatabase(db);
262
- }
263
- });
264
- test("searchFts with type filter", () => {
265
- const db = openDatabase(tmpDbPath());
266
- try {
267
- insertTestEntry(db, "build-script", {
268
- type: "script",
269
- description: "Build the project",
270
- searchText: "build project compilation",
271
- });
272
- insertTestEntry(db, "build-skill", {
273
- type: "skill",
274
- description: "Build pipeline skill",
275
- searchText: "build pipeline skill compilation",
276
- });
277
- rebuildFts(db);
278
- const scriptResults = searchFts(db, "build", 10, "script");
279
- expect(scriptResults).toHaveLength(1);
280
- expect(scriptResults[0].entry.type).toBe("script");
281
- const allResults = searchFts(db, "build", 10);
282
- expect(allResults).toHaveLength(2);
283
- }
284
- finally {
285
- closeDatabase(db);
286
- }
287
- });
288
- test("searchFts sanitizes query tokens", () => {
289
- const db = openDatabase(tmpDbPath());
290
- try {
291
- insertTestEntry(db, "hello-tool", {
292
- description: "hello world 123 greeting",
293
- searchText: "hello world 123 greeting",
294
- });
295
- rebuildFts(db);
296
- // Should not throw a SQL error despite special characters
297
- const results = searchFts(db, "hello! world@123", 10);
298
- expect(results[0].entry.name).toBe("hello-tool");
299
- // "hello" and "world" and "123" are valid tokens after sanitization
300
- expect(results.length).toBeGreaterThanOrEqual(1);
301
- }
302
- finally {
303
- closeDatabase(db);
304
- }
305
- });
306
- test("searchFts returns empty for garbage query", () => {
307
- const db = openDatabase(tmpDbPath());
308
- try {
309
- insertTestEntry(db, "some-tool", { searchText: "some useful tool" });
310
- rebuildFts(db);
311
- const results = searchFts(db, "!@#$%", 10);
312
- expect(results).toEqual([]);
313
- }
314
- finally {
315
- closeDatabase(db);
316
- }
317
- });
318
- // ── T5: sanitizeFtsQuery edge cases ──────────────────────────────────────
319
- // sanitizeFtsQuery is private, so we test it indirectly through searchFts.
320
- test("query that becomes empty after sanitization returns no results", () => {
321
- const db = openDatabase(tmpDbPath());
322
- try {
323
- insertTestEntry(db, "target", { searchText: "some useful content" });
324
- rebuildFts(db);
325
- // "! @" contains only non-alphanumeric chars; after sanitization all
326
- // tokens are stripped, leaving an empty FTS query.
327
- const results = searchFts(db, "! @", 10);
328
- expect(results).toEqual([]);
329
- }
330
- finally {
331
- closeDatabase(db);
332
- }
333
- });
334
- test("query with only 1-character tokens returns no results when content has no matching single-char terms", () => {
335
- const db = openDatabase(tmpDbPath());
336
- try {
337
- insertTestEntry(db, "abc-tool", { searchText: "alpha bravo charlie" });
338
- rebuildFts(db);
339
- // "a b c" — single-char tokens are passed to FTS5 but don't match
340
- // "alpha", "bravo", "charlie" because FTS5 doesn't do prefix matching.
341
- const results = searchFts(db, "a b c", 10);
342
- expect(results).toEqual([]);
343
- }
344
- finally {
345
- closeDatabase(db);
346
- }
347
- });
348
- test("FTS5 syntax injection is neutralized", () => {
349
- const db = openDatabase(tmpDbPath());
350
- try {
351
- insertTestEntry(db, "foo-tool", { description: "foo bar baz", searchText: "foo bar baz" });
352
- insertTestEntry(db, "bar-tool", { description: "bar qux quux", searchText: "bar qux quux" });
353
- rebuildFts(db);
354
- // "NEAR(foo, bar)" is raw FTS5 syntax that should be sanitized.
355
- // After sanitization, syntax chars and NEAR are stripped, leaving
356
- // tokens "foo" "bar" (implicit AND) — should not throw and should
357
- // return matches containing both foo and bar.
358
- const results = searchFts(db, "NEAR(foo, bar)", 10);
359
- expect(results.length).toBeGreaterThanOrEqual(1);
360
- // foo-tool has both "foo" and "bar" in its search text
361
- const names = results.map((r) => r.entry.name);
362
- expect(names).toContain("foo-tool");
363
- }
364
- finally {
365
- closeDatabase(db);
366
- }
367
- });
368
- test("normal multi-word query returns correct results", () => {
369
- const db = openDatabase(tmpDbPath());
370
- try {
371
- insertTestEntry(db, "deploy-prod", {
372
- description: "deploy application production servers",
373
- searchText: "deploy application production servers",
374
- });
375
- insertTestEntry(db, "test-runner", {
376
- description: "test runner unit integration",
377
- searchText: "test runner unit integration",
378
- });
379
- rebuildFts(db);
380
- const results = searchFts(db, "deploy production", 10);
381
- expect(results).toHaveLength(1);
382
- expect(results[0].entry.name).toBe("deploy-prod");
383
- }
384
- finally {
385
- closeDatabase(db);
386
- }
387
- });
388
- test("rebuildFts synchronizes FTS with entries table", () => {
389
- const db = openDatabase(tmpDbPath());
390
- try {
391
- insertTestEntry(db, "alpha", { description: "alpha functionality", searchText: "alpha functionality" });
392
- insertTestEntry(db, "beta", { description: "beta functionality", searchText: "beta functionality" });
393
- insertTestEntry(db, "gamma", { description: "gamma functionality", searchText: "gamma functionality" });
394
- rebuildFts(db);
395
- const alphaResults = searchFts(db, "alpha", 10);
396
- expect(alphaResults).toHaveLength(1);
397
- expect(alphaResults[0].entry.name).toBe("alpha");
398
- const allResults = searchFts(db, "functionality", 10);
399
- expect(allResults).toHaveLength(3);
400
- }
401
- finally {
402
- closeDatabase(db);
403
- }
404
- });
405
- });
406
- // ── Section 1.4: Meta helpers ──────────────────────────────────────────────
407
- describe("Meta helpers", () => {
408
- test("getMeta returns undefined for missing key", () => {
409
- const db = openDatabase(tmpDbPath());
410
- try {
411
- const val = getMeta(db, "nonexistent-key");
412
- expect(val).toBeUndefined();
413
- }
414
- finally {
415
- closeDatabase(db);
416
- }
417
- });
418
- test("setMeta and getMeta round-trip", () => {
419
- const db = openDatabase(tmpDbPath());
420
- try {
421
- setMeta(db, "test-key", "test-value");
422
- expect(getMeta(db, "test-key")).toBe("test-value");
423
- }
424
- finally {
425
- closeDatabase(db);
426
- }
427
- });
428
- test("setMeta overwrites existing key", () => {
429
- const db = openDatabase(tmpDbPath());
430
- try {
431
- setMeta(db, "overwrite-key", "first");
432
- expect(getMeta(db, "overwrite-key")).toBe("first");
433
- setMeta(db, "overwrite-key", "second");
434
- expect(getMeta(db, "overwrite-key")).toBe("second");
435
- }
436
- finally {
437
- closeDatabase(db);
438
- }
439
- });
440
- });
441
- // ── Section 1.5: Vector / Embedding integration ────────────────────────────
442
- describe("Vector / Embedding integration", () => {
443
- test("openDatabase creates vec table when extension available", () => {
444
- const dbPath = tmpDbPath();
445
- const db = openDatabase(dbPath);
446
- try {
447
- expect(isVecAvailable(db)).toBe(true);
448
- const row = db.prepare("SELECT name FROM sqlite_master WHERE name = 'entries_vec'").get();
449
- expect(row).toBeDefined();
450
- expect(row?.name).toBe("entries_vec");
451
- }
452
- finally {
453
- closeDatabase(db);
454
- }
455
- });
456
- test("upsertEmbedding stores and searchVec retrieves by similarity", () => {
457
- const dbPath = tmpDbPath();
458
- const db = openDatabase(dbPath, { embeddingDim: 4 });
459
- try {
460
- expect(isVecAvailable(db)).toBe(true);
461
- // Insert two entries with distinct embeddings
462
- const id1 = insertTestEntry(db, "vec-tool-1", { searchText: "deployment" });
463
- const id2 = insertTestEntry(db, "vec-tool-2", { searchText: "testing" });
464
- // Embedding vectors: tool-1 points "north", tool-2 points "east"
465
- upsertEmbedding(db, id1, [1, 0, 0, 0]);
466
- upsertEmbedding(db, id2, [0, 1, 0, 0]);
467
- // Query close to tool-1's embedding
468
- const results = searchVec(db, [0.9, 0.1, 0, 0], 10);
469
- expect(results.length).toBe(2);
470
- // tool-1 should be the closest (smallest distance)
471
- expect(results[0].id).toBe(id1);
472
- expect(results[0].distance).toBeLessThan(results[1].distance);
473
- }
474
- finally {
475
- closeDatabase(db);
476
- }
477
- });
478
- test("upsertEmbedding overwrites existing embedding for same entry", () => {
479
- const dbPath = tmpDbPath();
480
- const db = openDatabase(dbPath, { embeddingDim: 4 });
481
- try {
482
- const id = insertTestEntry(db, "vec-update", { searchText: "update test" });
483
- upsertEmbedding(db, id, [1, 0, 0, 0]);
484
- let results = searchVec(db, [1, 0, 0, 0], 10);
485
- expect(results.length).toBe(1);
486
- expect(results[0].distance).toBeCloseTo(0, 2);
487
- // Overwrite with a completely different direction
488
- upsertEmbedding(db, id, [0, 0, 0, 1]);
489
- results = searchVec(db, [0, 0, 0, 1], 10);
490
- expect(results.length).toBe(1);
491
- expect(results[0].distance).toBeCloseTo(0, 2);
492
- // Original direction should now be far
493
- results = searchVec(db, [1, 0, 0, 0], 10);
494
- expect(results[0].distance).toBeGreaterThan(1);
495
- }
496
- finally {
497
- closeDatabase(db);
498
- }
499
- });
500
- test("searchVec respects k limit", () => {
501
- const dbPath = tmpDbPath();
502
- const db = openDatabase(dbPath, { embeddingDim: 4 });
503
- try {
504
- // Insert 5 entries with embeddings
505
- for (let i = 0; i < 5; i++) {
506
- const id = insertTestEntry(db, `vec-k-${i}`, { searchText: `entry ${i}` });
507
- const vec = [0, 0, 0, 0];
508
- vec[i % 4] = 1;
509
- upsertEmbedding(db, id, vec);
510
- }
511
- const results = searchVec(db, [1, 0, 0, 0], 2);
512
- expect(results.length).toBe(2);
513
- }
514
- finally {
515
- closeDatabase(db);
516
- }
517
- });
518
- test("deleteEntriesByDir also removes vec rows", () => {
519
- const dbPath = tmpDbPath();
520
- const db = openDatabase(dbPath, { embeddingDim: 4 });
521
- try {
522
- const id1 = insertTestEntry(db, "vec-del-1", { dirPath: "/del-dir", searchText: "delete me" });
523
- const id2 = insertTestEntry(db, "vec-del-2", { dirPath: "/keep-dir", searchText: "keep me" });
524
- upsertEmbedding(db, id1, [1, 0, 0, 0]);
525
- upsertEmbedding(db, id2, [0, 1, 0, 0]);
526
- // Before delete, both should be searchable
527
- let results = searchVec(db, [0.5, 0.5, 0, 0], 10);
528
- expect(results.length).toBe(2);
529
- deleteEntriesByDir(db, "/del-dir");
530
- // After delete, only the kept entry should remain
531
- results = searchVec(db, [0.5, 0.5, 0, 0], 10);
532
- expect(results.length).toBe(1);
533
- expect(results[0].id).toBe(id2);
534
- }
535
- finally {
536
- closeDatabase(db);
537
- }
538
- });
539
- test("embeddingDim change recreates vec table and clears old embeddings", () => {
540
- const dbPath = tmpDbPath();
541
- // Open with dim=4 and insert an embedding
542
- let db = openDatabase(dbPath, { embeddingDim: 4 });
543
- const id = insertTestEntry(db, "dim-change", { searchText: "dimension test" });
544
- upsertEmbedding(db, id, [1, 0, 0, 0]);
545
- let results = searchVec(db, [1, 0, 0, 0], 10);
546
- expect(results.length).toBe(1);
547
- closeDatabase(db);
548
- // Reopen with dim=8 — vec table should be recreated, old embeddings gone
549
- db = openDatabase(dbPath, { embeddingDim: 8 });
550
- try {
551
- expect(getMeta(db, "embeddingDim")).toBe("8");
552
- // Old embedding was dim=4 and table was recreated for dim=8, so no results
553
- results = searchVec(db, [1, 0, 0, 0, 0, 0, 0, 0], 10);
554
- expect(results.length).toBe(0);
555
- }
556
- finally {
557
- closeDatabase(db);
558
- }
559
- });
560
- });
561
- // ── Incremental rebuildFts (#177 perf finding) ──────────────────────────────
562
- describe("rebuildFts incremental", () => {
563
- function makeEntry(name, description = "") {
564
- return {
565
- name,
566
- type: "skill",
567
- description,
568
- filename: `${name}.md`,
569
- };
570
- }
571
- function ftsCount(db) {
572
- const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries_fts").get();
573
- return row?.cnt ?? 0;
574
- }
575
- function dirtyCount(db) {
576
- try {
577
- const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries_fts_dirty").get();
578
- return row?.cnt ?? 0;
579
- }
580
- catch {
581
- return 0;
582
- }
583
- }
584
- test("upsertEntry marks rows dirty; incremental rebuild only re-indexes them", () => {
585
- const db = openDatabase(tmpDbPath("inc-fts"));
586
- try {
587
- upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha", "first"), "alpha first");
588
- upsertEntry(db, "k2", "/d", "/d/k2.md", "/stash", makeEntry("bravo", "second"), "bravo second");
589
- upsertEntry(db, "k3", "/d", "/d/k3.md", "/stash", makeEntry("charlie", "third"), "charlie third");
590
- rebuildFts(db, { incremental: false });
591
- expect(ftsCount(db)).toBe(3);
592
- expect(dirtyCount(db)).toBe(0);
593
- // Touch only one entry — its row should be the only dirty one.
594
- upsertEntry(db, "k2", "/d", "/d/k2.md", "/stash", makeEntry("bravo", "second-updated"), "bravo second-updated");
595
- expect(dirtyCount(db)).toBe(1);
596
- rebuildFts(db, { incremental: true });
597
- expect(ftsCount(db)).toBe(3);
598
- expect(dirtyCount(db)).toBe(0);
599
- const hits = db
600
- .prepare("SELECT entry_id FROM entries_fts WHERE entries_fts MATCH ?")
601
- .all(`"second-updated"`);
602
- expect(hits.length).toBe(1);
603
- }
604
- finally {
605
- closeDatabase(db);
606
- }
607
- });
608
- test("incremental rebuild with empty dirty queue is a no-op", () => {
609
- const db = openDatabase(tmpDbPath("inc-fts-empty"));
610
- try {
611
- upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha"), "alpha");
612
- rebuildFts(db, { incremental: false });
613
- expect(ftsCount(db)).toBe(1);
614
- expect(dirtyCount(db)).toBe(0);
615
- rebuildFts(db, { incremental: true });
616
- expect(ftsCount(db)).toBe(1);
617
- }
618
- finally {
619
- closeDatabase(db);
620
- }
621
- });
622
- test("full rebuild also drains the dirty queue", () => {
623
- const db = openDatabase(tmpDbPath("inc-fts-full"));
624
- try {
625
- upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha"), "alpha");
626
- expect(dirtyCount(db)).toBe(1);
627
- rebuildFts(db, { incremental: false });
628
- expect(ftsCount(db)).toBe(1);
629
- expect(dirtyCount(db)).toBe(0);
630
- }
631
- finally {
632
- closeDatabase(db);
633
- }
634
- });
635
- test("deleteEntriesByDir purges FTS rows + dirty markers immediately", () => {
636
- const db = openDatabase(tmpDbPath("inc-fts-del"));
637
- try {
638
- upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha"), "alpha");
639
- upsertEntry(db, "k2", "/d", "/d/k2.md", "/stash", makeEntry("bravo"), "bravo");
640
- rebuildFts(db, { incremental: false });
641
- expect(ftsCount(db)).toBe(2);
642
- upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha", "updated"), "alpha updated");
643
- expect(dirtyCount(db)).toBe(1);
644
- deleteEntriesByDir(db, "/d");
645
- expect(ftsCount(db)).toBe(0);
646
- expect(dirtyCount(db)).toBe(0);
647
- rebuildFts(db, { incremental: true });
648
- expect(ftsCount(db)).toBe(0);
649
- }
650
- finally {
651
- closeDatabase(db);
652
- }
653
- });
654
- });