bluera-knowledge 0.9.21

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 (652) hide show
  1. package/.claude/commands/commit.md +37 -0
  2. package/.claude/hooks/post-edit-check.sh +41 -0
  3. package/.claude/settings.local.json.example +40 -0
  4. package/.claude/skills/atomic-commits/SKILL.md +53 -0
  5. package/.claude-plugin/plugin.json +13 -0
  6. package/.editorconfig +15 -0
  7. package/.github/workflows/auto-release.yml +59 -0
  8. package/.github/workflows/ci.yml +142 -0
  9. package/.github/workflows/release.yml +66 -0
  10. package/.github/workflows/update-marketplace.yml +96 -0
  11. package/.husky/pre-commit +47 -0
  12. package/.husky/pre-push +29 -0
  13. package/.versionrc.json +28 -0
  14. package/CHANGELOG.md +410 -0
  15. package/CLAUDE.md +109 -0
  16. package/LICENSE +21 -0
  17. package/NOTICE +47 -0
  18. package/README.md +1546 -0
  19. package/SECURITY.md +65 -0
  20. package/bun.lock +1758 -0
  21. package/commands/add-folder.md +48 -0
  22. package/commands/add-repo.md +50 -0
  23. package/commands/cancel.md +63 -0
  24. package/commands/check-status.md +78 -0
  25. package/commands/crawl.md +51 -0
  26. package/commands/index.md +48 -0
  27. package/commands/remove-store.md +52 -0
  28. package/commands/search.md +79 -0
  29. package/commands/search.sh +63 -0
  30. package/commands/stores.md +54 -0
  31. package/commands/suggest.md +82 -0
  32. package/dist/chunk-5QMHZUC4.js +3617 -0
  33. package/dist/chunk-5QMHZUC4.js.map +1 -0
  34. package/dist/chunk-BICFAWMN.js +656 -0
  35. package/dist/chunk-BICFAWMN.js.map +1 -0
  36. package/dist/chunk-J7J6LXOJ.js +958 -0
  37. package/dist/chunk-J7J6LXOJ.js.map +1 -0
  38. package/dist/chunk-L2YVNC63.js +59 -0
  39. package/dist/chunk-L2YVNC63.js.map +1 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +1429 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/mcp/server.d.ts +15 -0
  44. package/dist/mcp/server.js +11 -0
  45. package/dist/mcp/server.js.map +1 -0
  46. package/dist/watch.service-YAIKKDCF.js +7 -0
  47. package/dist/watch.service-YAIKKDCF.js.map +1 -0
  48. package/dist/workers/background-worker-cli.d.ts +1 -0
  49. package/dist/workers/background-worker-cli.js +310 -0
  50. package/dist/workers/background-worker-cli.js.map +1 -0
  51. package/docs/plans/2024-12-17-ai-search-quality-implementation.md +752 -0
  52. package/docs/plans/2024-12-17-ai-search-quality-testing-design.md +201 -0
  53. package/docs/plans/2025-12-16-bluera-knowledge-cli.md +2951 -0
  54. package/docs/plans/2025-12-16-phase2-features.md +1518 -0
  55. package/docs/plans/2025-12-17-hil-implementation.md +926 -0
  56. package/docs/plans/2025-12-17-hil-quality-testing.md +224 -0
  57. package/docs/plans/2025-12-17-search-quality-phase1-implementation.md +1416 -0
  58. package/docs/plans/2025-12-17-search-quality-testing-v2-design.md +212 -0
  59. package/docs/plans/2025-12-28-ai-agent-optimization.md +1630 -0
  60. package/eslint-rules/require-skip-comment.js +81 -0
  61. package/eslint.config.js +61 -0
  62. package/hooks/check-dependencies.sh +110 -0
  63. package/hooks/format-search-results.py +132 -0
  64. package/hooks/hooks.json +27 -0
  65. package/hooks/job-status-hook.sh +51 -0
  66. package/knip.json +43 -0
  67. package/mcp.plugin.json +12 -0
  68. package/package.json +103 -0
  69. package/python/crawl_worker.py +275 -0
  70. package/python/requirements.txt +2 -0
  71. package/scripts/readme-version-updater.cjs +18 -0
  72. package/skills/advanced-workflows/SKILL.md +273 -0
  73. package/skills/atomic-commits/SKILL.md +77 -0
  74. package/skills/knowledge-search/SKILL.md +54 -0
  75. package/skills/search-optimization/SKILL.md +396 -0
  76. package/skills/store-lifecycle/SKILL.md +470 -0
  77. package/skills/when-to-query/SKILL.md +66 -0
  78. package/src/analysis/ast-parser.test.ts +423 -0
  79. package/src/analysis/ast-parser.ts +192 -0
  80. package/src/analysis/code-graph.test.ts +698 -0
  81. package/src/analysis/code-graph.ts +245 -0
  82. package/src/analysis/dependency-usage-analyzer.test.ts +799 -0
  83. package/src/analysis/dependency-usage-analyzer.ts +405 -0
  84. package/src/analysis/go-ast-parser.test.ts +531 -0
  85. package/src/analysis/go-ast-parser.ts +478 -0
  86. package/src/analysis/parser-factory.test.ts +132 -0
  87. package/src/analysis/parser-factory.ts +44 -0
  88. package/src/analysis/python-ast-parser.test.ts +210 -0
  89. package/src/analysis/python-ast-parser.ts +34 -0
  90. package/src/analysis/repo-url-resolver.test.ts +533 -0
  91. package/src/analysis/repo-url-resolver.ts +233 -0
  92. package/src/analysis/rust-ast-parser.test.ts +568 -0
  93. package/src/analysis/rust-ast-parser.ts +477 -0
  94. package/src/analysis/tree-sitter-parser.test.ts +297 -0
  95. package/src/analysis/tree-sitter-parser.ts +223 -0
  96. package/src/cli/commands/crawl.test.ts +942 -0
  97. package/src/cli/commands/crawl.ts +141 -0
  98. package/src/cli/commands/index-cmd.test.ts +722 -0
  99. package/src/cli/commands/index-cmd.ts +105 -0
  100. package/src/cli/commands/mcp.test.ts +218 -0
  101. package/src/cli/commands/mcp.ts +18 -0
  102. package/src/cli/commands/plugin-api.test.ts +313 -0
  103. package/src/cli/commands/plugin-api.ts +45 -0
  104. package/src/cli/commands/search.test.ts +911 -0
  105. package/src/cli/commands/search.ts +113 -0
  106. package/src/cli/commands/serve.test.ts +329 -0
  107. package/src/cli/commands/serve.ts +28 -0
  108. package/src/cli/commands/setup.test.ts +820 -0
  109. package/src/cli/commands/setup.ts +153 -0
  110. package/src/cli/commands/store.test.ts +1003 -0
  111. package/src/cli/commands/store.ts +167 -0
  112. package/src/cli/index.ts +7 -0
  113. package/src/cli/program.ts +59 -0
  114. package/src/crawl/article-converter.test.ts +604 -0
  115. package/src/crawl/article-converter.ts +98 -0
  116. package/src/crawl/bridge.test.ts +674 -0
  117. package/src/crawl/bridge.ts +236 -0
  118. package/src/crawl/claude-client.test.ts +663 -0
  119. package/src/crawl/claude-client.ts +234 -0
  120. package/src/crawl/intelligent-crawler.test.ts +931 -0
  121. package/src/crawl/intelligent-crawler.ts +428 -0
  122. package/src/crawl/markdown-utils.test.ts +703 -0
  123. package/src/crawl/markdown-utils.ts +228 -0
  124. package/src/crawl/schemas.ts +114 -0
  125. package/src/db/embeddings.test.ts +63 -0
  126. package/src/db/embeddings.ts +69 -0
  127. package/src/db/index.ts +2 -0
  128. package/src/db/lance.test.ts +390 -0
  129. package/src/db/lance.ts +164 -0
  130. package/src/defaults/repos.ts +67 -0
  131. package/src/index.ts +107 -0
  132. package/src/mcp/cache.test.ts +202 -0
  133. package/src/mcp/cache.ts +103 -0
  134. package/src/mcp/commands/index.ts +20 -0
  135. package/src/mcp/commands/job.commands.ts +54 -0
  136. package/src/mcp/commands/meta.commands.ts +54 -0
  137. package/src/mcp/commands/registry.ts +183 -0
  138. package/src/mcp/commands/store.commands.ts +75 -0
  139. package/src/mcp/handlers/execute.handler.test.ts +179 -0
  140. package/src/mcp/handlers/execute.handler.ts +24 -0
  141. package/src/mcp/handlers/index.ts +43 -0
  142. package/src/mcp/handlers/job.handler.test.ts +189 -0
  143. package/src/mcp/handlers/job.handler.ts +116 -0
  144. package/src/mcp/handlers/search.handler.test.ts +334 -0
  145. package/src/mcp/handlers/search.handler.ts +209 -0
  146. package/src/mcp/handlers/store.handler.test.ts +415 -0
  147. package/src/mcp/handlers/store.handler.ts +295 -0
  148. package/src/mcp/schemas/index.test.ts +315 -0
  149. package/src/mcp/schemas/index.ts +138 -0
  150. package/src/mcp/server.test.ts +36 -0
  151. package/src/mcp/server.ts +162 -0
  152. package/src/mcp/types.ts +41 -0
  153. package/src/plugin/commands.test.ts +789 -0
  154. package/src/plugin/commands.ts +257 -0
  155. package/src/plugin/dependency-analyzer.test.ts +380 -0
  156. package/src/plugin/dependency-analyzer.ts +147 -0
  157. package/src/plugin/git-clone.test.ts +332 -0
  158. package/src/plugin/git-clone.ts +57 -0
  159. package/src/server/app.test.ts +752 -0
  160. package/src/server/app.ts +119 -0
  161. package/src/server/index.test.ts +477 -0
  162. package/src/server/index.ts +1 -0
  163. package/src/services/chunking.service.test.ts +363 -0
  164. package/src/services/chunking.service.ts +350 -0
  165. package/src/services/code-graph.service.test.ts +304 -0
  166. package/src/services/code-graph.service.ts +302 -0
  167. package/src/services/code-unit.service.test.ts +596 -0
  168. package/src/services/code-unit.service.ts +115 -0
  169. package/src/services/config.service.test.ts +127 -0
  170. package/src/services/config.service.ts +69 -0
  171. package/src/services/index.service.test.ts +1002 -0
  172. package/src/services/index.service.ts +266 -0
  173. package/src/services/index.ts +75 -0
  174. package/src/services/job.service.test.ts +418 -0
  175. package/src/services/job.service.ts +246 -0
  176. package/src/services/project-root.service.test.ts +506 -0
  177. package/src/services/project-root.service.ts +112 -0
  178. package/src/services/search.service.test.ts +1105 -0
  179. package/src/services/search.service.ts +892 -0
  180. package/src/services/services.test.ts +38 -0
  181. package/src/services/snippet.service.test.ts +205 -0
  182. package/src/services/snippet.service.ts +166 -0
  183. package/src/services/store.service.test.ts +474 -0
  184. package/src/services/store.service.ts +225 -0
  185. package/src/services/watch.service.test.ts +553 -0
  186. package/src/services/watch.service.ts +71 -0
  187. package/src/types/brands.test.ts +45 -0
  188. package/src/types/brands.ts +32 -0
  189. package/src/types/config.ts +79 -0
  190. package/src/types/document.ts +30 -0
  191. package/src/types/index.ts +66 -0
  192. package/src/types/job.ts +46 -0
  193. package/src/types/progress.ts +9 -0
  194. package/src/types/result.test.ts +44 -0
  195. package/src/types/result.ts +41 -0
  196. package/src/types/search.ts +95 -0
  197. package/src/types/store.test.ts +69 -0
  198. package/src/types/store.ts +47 -0
  199. package/src/utils/type-guards.test.ts +346 -0
  200. package/src/utils/type-guards.ts +61 -0
  201. package/src/workers/background-worker-cli.ts +105 -0
  202. package/src/workers/background-worker.test.ts +178 -0
  203. package/src/workers/background-worker.ts +294 -0
  204. package/src/workers/spawn-worker.test.ts +128 -0
  205. package/src/workers/spawn-worker.ts +49 -0
  206. package/tests/analysis/ast-parser.test.ts +98 -0
  207. package/tests/analysis/code-graph.test.ts +60 -0
  208. package/tests/fixtures/README.md +114 -0
  209. package/tests/fixtures/code-snippets/api/error-handling.ts +267 -0
  210. package/tests/fixtures/code-snippets/api/rest-controller.ts +303 -0
  211. package/tests/fixtures/code-snippets/auth/jwt-auth.ts +213 -0
  212. package/tests/fixtures/code-snippets/auth/oauth-flow.ts +245 -0
  213. package/tests/fixtures/code-snippets/database/repository-pattern.ts +272 -0
  214. package/tests/fixtures/corpus/VERSION.md +25 -0
  215. package/tests/fixtures/corpus/articles/jwt-authentication.md +97 -0
  216. package/tests/fixtures/corpus/articles/react-hooks-patterns.md +127 -0
  217. package/tests/fixtures/corpus/articles/typescript-generics.md +111 -0
  218. package/tests/fixtures/corpus/documentation/express-middleware.md +71 -0
  219. package/tests/fixtures/corpus/documentation/express-routing.md +83 -0
  220. package/tests/fixtures/corpus/documentation/node-streams.md +78 -0
  221. package/tests/fixtures/corpus/oss-repos/express/History.md +3871 -0
  222. package/tests/fixtures/corpus/oss-repos/express/LICENSE +24 -0
  223. package/tests/fixtures/corpus/oss-repos/express/Readme.md +276 -0
  224. package/tests/fixtures/corpus/oss-repos/express/SECURITY.md +56 -0
  225. package/tests/fixtures/corpus/oss-repos/express/benchmarks/Makefile +17 -0
  226. package/tests/fixtures/corpus/oss-repos/express/benchmarks/README.md +34 -0
  227. package/tests/fixtures/corpus/oss-repos/express/benchmarks/middleware.js +20 -0
  228. package/tests/fixtures/corpus/oss-repos/express/benchmarks/run +18 -0
  229. package/tests/fixtures/corpus/oss-repos/express/examples/README.md +29 -0
  230. package/tests/fixtures/corpus/oss-repos/express/examples/auth/index.js +134 -0
  231. package/tests/fixtures/corpus/oss-repos/express/examples/auth/views/foot.ejs +2 -0
  232. package/tests/fixtures/corpus/oss-repos/express/examples/auth/views/head.ejs +20 -0
  233. package/tests/fixtures/corpus/oss-repos/express/examples/auth/views/login.ejs +21 -0
  234. package/tests/fixtures/corpus/oss-repos/express/examples/content-negotiation/db.js +9 -0
  235. package/tests/fixtures/corpus/oss-repos/express/examples/content-negotiation/index.js +46 -0
  236. package/tests/fixtures/corpus/oss-repos/express/examples/content-negotiation/users.js +19 -0
  237. package/tests/fixtures/corpus/oss-repos/express/examples/cookie-sessions/index.js +25 -0
  238. package/tests/fixtures/corpus/oss-repos/express/examples/cookies/index.js +53 -0
  239. package/tests/fixtures/corpus/oss-repos/express/examples/downloads/files/CCTV/345/244/247/350/265/233/344/270/212/346/265/267/345/210/206/350/265/233/345/214/272.txt +2 -0
  240. package/tests/fixtures/corpus/oss-repos/express/examples/downloads/files/amazing.txt +1 -0
  241. package/tests/fixtures/corpus/oss-repos/express/examples/downloads/files/notes/groceries.txt +3 -0
  242. package/tests/fixtures/corpus/oss-repos/express/examples/downloads/index.js +40 -0
  243. package/tests/fixtures/corpus/oss-repos/express/examples/ejs/index.js +57 -0
  244. package/tests/fixtures/corpus/oss-repos/express/examples/ejs/public/stylesheets/style.css +4 -0
  245. package/tests/fixtures/corpus/oss-repos/express/examples/ejs/views/footer.html +2 -0
  246. package/tests/fixtures/corpus/oss-repos/express/examples/ejs/views/header.html +9 -0
  247. package/tests/fixtures/corpus/oss-repos/express/examples/ejs/views/users.html +10 -0
  248. package/tests/fixtures/corpus/oss-repos/express/examples/error/index.js +53 -0
  249. package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/index.js +103 -0
  250. package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/404.ejs +3 -0
  251. package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/500.ejs +8 -0
  252. package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/error_header.ejs +10 -0
  253. package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/footer.ejs +2 -0
  254. package/tests/fixtures/corpus/oss-repos/express/examples/error-pages/views/index.ejs +20 -0
  255. package/tests/fixtures/corpus/oss-repos/express/examples/hello-world/index.js +15 -0
  256. package/tests/fixtures/corpus/oss-repos/express/examples/markdown/index.js +44 -0
  257. package/tests/fixtures/corpus/oss-repos/express/examples/markdown/views/index.md +4 -0
  258. package/tests/fixtures/corpus/oss-repos/express/examples/multi-router/controllers/api_v1.js +15 -0
  259. package/tests/fixtures/corpus/oss-repos/express/examples/multi-router/controllers/api_v2.js +15 -0
  260. package/tests/fixtures/corpus/oss-repos/express/examples/multi-router/index.js +18 -0
  261. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/main/index.js +5 -0
  262. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/pet/index.js +31 -0
  263. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/pet/views/edit.ejs +17 -0
  264. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/pet/views/show.ejs +15 -0
  265. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/index.js +41 -0
  266. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/views/edit.hbs +27 -0
  267. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/views/list.hbs +18 -0
  268. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user/views/show.hbs +31 -0
  269. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/controllers/user-pet/index.js +22 -0
  270. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/db.js +16 -0
  271. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/index.js +95 -0
  272. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/lib/boot.js +83 -0
  273. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/public/style.css +14 -0
  274. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/views/404.ejs +13 -0
  275. package/tests/fixtures/corpus/oss-repos/express/examples/mvc/views/5xx.ejs +13 -0
  276. package/tests/fixtures/corpus/oss-repos/express/examples/online/index.js +61 -0
  277. package/tests/fixtures/corpus/oss-repos/express/examples/params/index.js +74 -0
  278. package/tests/fixtures/corpus/oss-repos/express/examples/resource/index.js +95 -0
  279. package/tests/fixtures/corpus/oss-repos/express/examples/route-map/index.js +75 -0
  280. package/tests/fixtures/corpus/oss-repos/express/examples/route-middleware/index.js +90 -0
  281. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/index.js +55 -0
  282. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/post.js +13 -0
  283. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/public/style.css +24 -0
  284. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/site.js +5 -0
  285. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/user.js +47 -0
  286. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/footer.ejs +2 -0
  287. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/header.ejs +9 -0
  288. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/index.ejs +10 -0
  289. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/posts/index.ejs +12 -0
  290. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/users/edit.ejs +23 -0
  291. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/users/index.ejs +14 -0
  292. package/tests/fixtures/corpus/oss-repos/express/examples/route-separation/views/users/view.ejs +9 -0
  293. package/tests/fixtures/corpus/oss-repos/express/examples/search/index.js +61 -0
  294. package/tests/fixtures/corpus/oss-repos/express/examples/search/public/client.js +15 -0
  295. package/tests/fixtures/corpus/oss-repos/express/examples/search/public/index.html +21 -0
  296. package/tests/fixtures/corpus/oss-repos/express/examples/session/index.js +37 -0
  297. package/tests/fixtures/corpus/oss-repos/express/examples/session/redis.js +39 -0
  298. package/tests/fixtures/corpus/oss-repos/express/examples/static-files/index.js +43 -0
  299. package/tests/fixtures/corpus/oss-repos/express/examples/static-files/public/css/style.css +3 -0
  300. package/tests/fixtures/corpus/oss-repos/express/examples/static-files/public/hello.txt +1 -0
  301. package/tests/fixtures/corpus/oss-repos/express/examples/static-files/public/js/app.js +1 -0
  302. package/tests/fixtures/corpus/oss-repos/express/examples/vhost/index.js +53 -0
  303. package/tests/fixtures/corpus/oss-repos/express/examples/view-constructor/github-view.js +53 -0
  304. package/tests/fixtures/corpus/oss-repos/express/examples/view-constructor/index.js +48 -0
  305. package/tests/fixtures/corpus/oss-repos/express/examples/view-locals/index.js +155 -0
  306. package/tests/fixtures/corpus/oss-repos/express/examples/view-locals/user.js +36 -0
  307. package/tests/fixtures/corpus/oss-repos/express/examples/view-locals/views/index.ejs +20 -0
  308. package/tests/fixtures/corpus/oss-repos/express/examples/web-service/index.js +117 -0
  309. package/tests/fixtures/corpus/oss-repos/express/index.js +11 -0
  310. package/tests/fixtures/corpus/oss-repos/express/lib/application.js +631 -0
  311. package/tests/fixtures/corpus/oss-repos/express/lib/express.js +81 -0
  312. package/tests/fixtures/corpus/oss-repos/express/lib/request.js +514 -0
  313. package/tests/fixtures/corpus/oss-repos/express/lib/response.js +1053 -0
  314. package/tests/fixtures/corpus/oss-repos/express/lib/utils.js +271 -0
  315. package/tests/fixtures/corpus/oss-repos/express/lib/view.js +205 -0
  316. package/tests/fixtures/corpus/oss-repos/express/package.json +99 -0
  317. package/tests/fixtures/corpus/oss-repos/express/test/Route.js +274 -0
  318. package/tests/fixtures/corpus/oss-repos/express/test/Router.js +636 -0
  319. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/auth.js +117 -0
  320. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/content-negotiation.js +49 -0
  321. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/cookie-sessions.js +38 -0
  322. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/cookies.js +71 -0
  323. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/downloads.js +47 -0
  324. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/ejs.js +17 -0
  325. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/error-pages.js +99 -0
  326. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/error.js +29 -0
  327. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/hello-world.js +21 -0
  328. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/markdown.js +21 -0
  329. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/multi-router.js +44 -0
  330. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/mvc.js +132 -0
  331. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/params.js +44 -0
  332. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/resource.js +68 -0
  333. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/route-map.js +45 -0
  334. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/route-separation.js +97 -0
  335. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/vhost.js +46 -0
  336. package/tests/fixtures/corpus/oss-repos/express/test/acceptance/web-service.js +105 -0
  337. package/tests/fixtures/corpus/oss-repos/express/test/app.all.js +38 -0
  338. package/tests/fixtures/corpus/oss-repos/express/test/app.engine.js +83 -0
  339. package/tests/fixtures/corpus/oss-repos/express/test/app.head.js +66 -0
  340. package/tests/fixtures/corpus/oss-repos/express/test/app.js +120 -0
  341. package/tests/fixtures/corpus/oss-repos/express/test/app.listen.js +55 -0
  342. package/tests/fixtures/corpus/oss-repos/express/test/app.locals.js +26 -0
  343. package/tests/fixtures/corpus/oss-repos/express/test/app.options.js +116 -0
  344. package/tests/fixtures/corpus/oss-repos/express/test/app.param.js +323 -0
  345. package/tests/fixtures/corpus/oss-repos/express/test/app.render.js +374 -0
  346. package/tests/fixtures/corpus/oss-repos/express/test/app.request.js +143 -0
  347. package/tests/fixtures/corpus/oss-repos/express/test/app.response.js +143 -0
  348. package/tests/fixtures/corpus/oss-repos/express/test/app.route.js +197 -0
  349. package/tests/fixtures/corpus/oss-repos/express/test/app.router.js +1217 -0
  350. package/tests/fixtures/corpus/oss-repos/express/test/app.routes.error.js +62 -0
  351. package/tests/fixtures/corpus/oss-repos/express/test/app.use.js +542 -0
  352. package/tests/fixtures/corpus/oss-repos/express/test/config.js +207 -0
  353. package/tests/fixtures/corpus/oss-repos/express/test/exports.js +82 -0
  354. package/tests/fixtures/corpus/oss-repos/express/test/express.json.js +755 -0
  355. package/tests/fixtures/corpus/oss-repos/express/test/express.raw.js +513 -0
  356. package/tests/fixtures/corpus/oss-repos/express/test/express.static.js +815 -0
  357. package/tests/fixtures/corpus/oss-repos/express/test/express.text.js +566 -0
  358. package/tests/fixtures/corpus/oss-repos/express/test/express.urlencoded.js +828 -0
  359. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/% of dogs.txt +1 -0
  360. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/.name +1 -0
  361. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/blog/index.html +1 -0
  362. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/blog/post/index.tmpl +1 -0
  363. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/broken.send +0 -0
  364. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/default_layout/name.tmpl +1 -0
  365. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/default_layout/user.tmpl +1 -0
  366. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/email.tmpl +1 -0
  367. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/empty.txt +0 -0
  368. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/local_layout/user.tmpl +1 -0
  369. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/name.tmpl +1 -0
  370. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/name.txt +1 -0
  371. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/nums.txt +1 -0
  372. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/pets/names.txt +1 -0
  373. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/snow /342/230/203/.gitkeep +0 -0
  374. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/todo.html +1 -0
  375. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/todo.txt +1 -0
  376. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/user.html +1 -0
  377. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/user.tmpl +1 -0
  378. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/users/index.html +1 -0
  379. package/tests/fixtures/corpus/oss-repos/express/test/fixtures/users/tobi.txt +1 -0
  380. package/tests/fixtures/corpus/oss-repos/express/test/middleware.basic.js +42 -0
  381. package/tests/fixtures/corpus/oss-repos/express/test/regression.js +20 -0
  382. package/tests/fixtures/corpus/oss-repos/express/test/req.accepts.js +125 -0
  383. package/tests/fixtures/corpus/oss-repos/express/test/req.acceptsCharsets.js +50 -0
  384. package/tests/fixtures/corpus/oss-repos/express/test/req.acceptsEncodings.js +39 -0
  385. package/tests/fixtures/corpus/oss-repos/express/test/req.acceptsLanguages.js +57 -0
  386. package/tests/fixtures/corpus/oss-repos/express/test/req.baseUrl.js +88 -0
  387. package/tests/fixtures/corpus/oss-repos/express/test/req.fresh.js +70 -0
  388. package/tests/fixtures/corpus/oss-repos/express/test/req.get.js +60 -0
  389. package/tests/fixtures/corpus/oss-repos/express/test/req.host.js +156 -0
  390. package/tests/fixtures/corpus/oss-repos/express/test/req.hostname.js +188 -0
  391. package/tests/fixtures/corpus/oss-repos/express/test/req.ip.js +113 -0
  392. package/tests/fixtures/corpus/oss-repos/express/test/req.ips.js +71 -0
  393. package/tests/fixtures/corpus/oss-repos/express/test/req.is.js +169 -0
  394. package/tests/fixtures/corpus/oss-repos/express/test/req.path.js +20 -0
  395. package/tests/fixtures/corpus/oss-repos/express/test/req.protocol.js +113 -0
  396. package/tests/fixtures/corpus/oss-repos/express/test/req.query.js +106 -0
  397. package/tests/fixtures/corpus/oss-repos/express/test/req.range.js +104 -0
  398. package/tests/fixtures/corpus/oss-repos/express/test/req.route.js +28 -0
  399. package/tests/fixtures/corpus/oss-repos/express/test/req.secure.js +101 -0
  400. package/tests/fixtures/corpus/oss-repos/express/test/req.signedCookies.js +37 -0
  401. package/tests/fixtures/corpus/oss-repos/express/test/req.stale.js +50 -0
  402. package/tests/fixtures/corpus/oss-repos/express/test/req.subdomains.js +173 -0
  403. package/tests/fixtures/corpus/oss-repos/express/test/req.xhr.js +42 -0
  404. package/tests/fixtures/corpus/oss-repos/express/test/res.append.js +116 -0
  405. package/tests/fixtures/corpus/oss-repos/express/test/res.attachment.js +79 -0
  406. package/tests/fixtures/corpus/oss-repos/express/test/res.clearCookie.js +62 -0
  407. package/tests/fixtures/corpus/oss-repos/express/test/res.cookie.js +295 -0
  408. package/tests/fixtures/corpus/oss-repos/express/test/res.download.js +487 -0
  409. package/tests/fixtures/corpus/oss-repos/express/test/res.format.js +248 -0
  410. package/tests/fixtures/corpus/oss-repos/express/test/res.get.js +21 -0
  411. package/tests/fixtures/corpus/oss-repos/express/test/res.json.js +186 -0
  412. package/tests/fixtures/corpus/oss-repos/express/test/res.jsonp.js +344 -0
  413. package/tests/fixtures/corpus/oss-repos/express/test/res.links.js +65 -0
  414. package/tests/fixtures/corpus/oss-repos/express/test/res.locals.js +40 -0
  415. package/tests/fixtures/corpus/oss-repos/express/test/res.location.js +316 -0
  416. package/tests/fixtures/corpus/oss-repos/express/test/res.redirect.js +214 -0
  417. package/tests/fixtures/corpus/oss-repos/express/test/res.render.js +367 -0
  418. package/tests/fixtures/corpus/oss-repos/express/test/res.send.js +569 -0
  419. package/tests/fixtures/corpus/oss-repos/express/test/res.sendFile.js +913 -0
  420. package/tests/fixtures/corpus/oss-repos/express/test/res.sendStatus.js +44 -0
  421. package/tests/fixtures/corpus/oss-repos/express/test/res.set.js +124 -0
  422. package/tests/fixtures/corpus/oss-repos/express/test/res.status.js +206 -0
  423. package/tests/fixtures/corpus/oss-repos/express/test/res.type.js +46 -0
  424. package/tests/fixtures/corpus/oss-repos/express/test/res.vary.js +90 -0
  425. package/tests/fixtures/corpus/oss-repos/express/test/support/env.js +3 -0
  426. package/tests/fixtures/corpus/oss-repos/express/test/support/tmpl.js +36 -0
  427. package/tests/fixtures/corpus/oss-repos/express/test/support/utils.js +86 -0
  428. package/tests/fixtures/corpus/oss-repos/express/test/utils.js +83 -0
  429. package/tests/fixtures/corpus/oss-repos/hono/.devcontainer/Dockerfile +11 -0
  430. package/tests/fixtures/corpus/oss-repos/hono/.devcontainer/devcontainer.json +21 -0
  431. package/tests/fixtures/corpus/oss-repos/hono/.devcontainer/docker-compose.yml +18 -0
  432. package/tests/fixtures/corpus/oss-repos/hono/.eslintignore +1 -0
  433. package/tests/fixtures/corpus/oss-repos/hono/.eslintrc.cjs +9 -0
  434. package/tests/fixtures/corpus/oss-repos/hono/.gitpod.yml +9 -0
  435. package/tests/fixtures/corpus/oss-repos/hono/.prettierrc +9 -0
  436. package/tests/fixtures/corpus/oss-repos/hono/.vitest.config/jsx-runtime-default.ts +15 -0
  437. package/tests/fixtures/corpus/oss-repos/hono/.vitest.config/jsx-runtime-dom.ts +15 -0
  438. package/tests/fixtures/corpus/oss-repos/hono/.vitest.config/setup-vitest.ts +47 -0
  439. package/tests/fixtures/corpus/oss-repos/hono/LICENSE +21 -0
  440. package/tests/fixtures/corpus/oss-repos/hono/README.md +91 -0
  441. package/tests/fixtures/corpus/oss-repos/hono/build.ts +80 -0
  442. package/tests/fixtures/corpus/oss-repos/hono/bun.lockb +0 -0
  443. package/tests/fixtures/corpus/oss-repos/hono/bunfig.toml +7 -0
  444. package/tests/fixtures/corpus/oss-repos/hono/codecov.yml +13 -0
  445. package/tests/fixtures/corpus/oss-repos/hono/docs/CODE_OF_CONDUCT.md +128 -0
  446. package/tests/fixtures/corpus/oss-repos/hono/docs/CONTRIBUTING.md +62 -0
  447. package/tests/fixtures/corpus/oss-repos/hono/docs/MIGRATION.md +295 -0
  448. package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-logo.png +0 -0
  449. package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-logo.pxm +0 -0
  450. package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-logo.svg +6 -0
  451. package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-title.png +0 -0
  452. package/tests/fixtures/corpus/oss-repos/hono/docs/images/hono-title.pxm +0 -0
  453. package/tests/fixtures/corpus/oss-repos/hono/jsr.json +119 -0
  454. package/tests/fixtures/corpus/oss-repos/hono/package.cjs.json +3 -0
  455. package/tests/fixtures/corpus/oss-repos/hono/package.json +650 -0
  456. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/handler.ts +492 -0
  457. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/index.ts +13 -0
  458. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/types.ts +144 -0
  459. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/conninfo.ts +28 -0
  460. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/index.ts +9 -0
  461. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/serve-static.ts +35 -0
  462. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/server.ts +30 -0
  463. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/ssg.ts +27 -0
  464. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/bun/websocket.ts +110 -0
  465. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/handler.ts +120 -0
  466. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/index.ts +7 -0
  467. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/conninfo.ts +7 -0
  468. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/index.ts +8 -0
  469. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static-module.ts +12 -0
  470. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static.ts +39 -0
  471. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/utils.ts +50 -0
  472. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/websocket.ts +50 -0
  473. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/conninfo.ts +17 -0
  474. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/deno.d.ts +28 -0
  475. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/index.ts +9 -0
  476. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/serve-static.ts +40 -0
  477. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/ssg.ts +27 -0
  478. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/deno/websocket.ts +51 -0
  479. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/lambda-edge/conninfo.ts +15 -0
  480. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/lambda-edge/handler.ts +189 -0
  481. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/lambda-edge/index.ts +14 -0
  482. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/netlify/handler.ts +10 -0
  483. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/netlify/index.ts +6 -0
  484. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/netlify/mod.ts +1 -0
  485. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/service-worker/handler.ts +34 -0
  486. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/service-worker/index.ts +5 -0
  487. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/service-worker/types.ts +14 -0
  488. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/vercel/conninfo.ts +8 -0
  489. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/vercel/handler.ts +9 -0
  490. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/vercel/index.ts +7 -0
  491. package/tests/fixtures/corpus/oss-repos/hono/src/client/client.ts +214 -0
  492. package/tests/fixtures/corpus/oss-repos/hono/src/client/index.ts +14 -0
  493. package/tests/fixtures/corpus/oss-repos/hono/src/client/types.ts +180 -0
  494. package/tests/fixtures/corpus/oss-repos/hono/src/client/utils.ts +54 -0
  495. package/tests/fixtures/corpus/oss-repos/hono/src/compose.ts +94 -0
  496. package/tests/fixtures/corpus/oss-repos/hono/src/context.ts +914 -0
  497. package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/accepts.ts +81 -0
  498. package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/index.ts +6 -0
  499. package/tests/fixtures/corpus/oss-repos/hono/src/helper/adapter/index.ts +85 -0
  500. package/tests/fixtures/corpus/oss-repos/hono/src/helper/conninfo/index.ts +6 -0
  501. package/tests/fixtures/corpus/oss-repos/hono/src/helper/conninfo/types.ts +45 -0
  502. package/tests/fixtures/corpus/oss-repos/hono/src/helper/cookie/index.ts +130 -0
  503. package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/common.ts +243 -0
  504. package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/index.ts +220 -0
  505. package/tests/fixtures/corpus/oss-repos/hono/src/helper/dev/index.ts +79 -0
  506. package/tests/fixtures/corpus/oss-repos/hono/src/helper/factory/index.ts +246 -0
  507. package/tests/fixtures/corpus/oss-repos/hono/src/helper/html/index.ts +56 -0
  508. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/index.ts +13 -0
  509. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/middleware.ts +79 -0
  510. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/ssg.ts +388 -0
  511. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/utils.ts +71 -0
  512. package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/index.ts +9 -0
  513. package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/sse.ts +89 -0
  514. package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/stream.ts +36 -0
  515. package/tests/fixtures/corpus/oss-repos/hono/src/helper/streaming/text.ts +15 -0
  516. package/tests/fixtures/corpus/oss-repos/hono/src/helper/testing/index.ts +26 -0
  517. package/tests/fixtures/corpus/oss-repos/hono/src/helper/websocket/index.ts +57 -0
  518. package/tests/fixtures/corpus/oss-repos/hono/src/hono-base.ts +523 -0
  519. package/tests/fixtures/corpus/oss-repos/hono/src/hono.ts +34 -0
  520. package/tests/fixtures/corpus/oss-repos/hono/src/http-exception.ts +78 -0
  521. package/tests/fixtures/corpus/oss-repos/hono/src/index.ts +51 -0
  522. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/base.ts +419 -0
  523. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/children.ts +20 -0
  524. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/components.ts +195 -0
  525. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/constants.ts +5 -0
  526. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/context.ts +50 -0
  527. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/client.ts +89 -0
  528. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/components.ts +39 -0
  529. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/context.ts +52 -0
  530. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/css.ts +246 -0
  531. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/hooks/index.ts +91 -0
  532. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/index.ts +159 -0
  533. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/intrinsic-element/components.ts +398 -0
  534. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/jsx-dev-runtime.ts +22 -0
  535. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/jsx-runtime.ts +7 -0
  536. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/render.ts +772 -0
  537. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/server.ts +70 -0
  538. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/utils.ts +7 -0
  539. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/hooks/index.ts +426 -0
  540. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/index.ts +114 -0
  541. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/common.ts +11 -0
  542. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/components.ts +196 -0
  543. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-elements.ts +924 -0
  544. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/jsx-dev-runtime.ts +26 -0
  545. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/jsx-runtime.ts +18 -0
  546. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/streaming.ts +184 -0
  547. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/types.ts +41 -0
  548. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/utils.ts +36 -0
  549. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/basic-auth/index.ts +128 -0
  550. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/bearer-auth/index.ts +159 -0
  551. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/body-limit/index.ts +115 -0
  552. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/cache/index.ts +127 -0
  553. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/combine/index.ts +153 -0
  554. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/compress/index.ts +79 -0
  555. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/context-storage/index.ts +55 -0
  556. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/cors/index.ts +141 -0
  557. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/csrf/index.ts +90 -0
  558. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/etag/index.ts +88 -0
  559. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/ip-restriction/index.ts +178 -0
  560. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jsx-renderer/index.ts +158 -0
  561. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jwt/index.ts +8 -0
  562. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jwt/jwt.ts +159 -0
  563. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/logger/index.ts +93 -0
  564. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/method-override/index.ts +146 -0
  565. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/powered-by/index.ts +13 -0
  566. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/pretty-json/index.ts +50 -0
  567. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/request-id/index.ts +8 -0
  568. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/request-id/request-id.ts +59 -0
  569. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/secure-headers/index.ts +8 -0
  570. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/secure-headers/permissions-policy.ts +86 -0
  571. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/secure-headers/secure-headers.ts +319 -0
  572. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/serve-static/index.ts +140 -0
  573. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/timeout/index.ts +58 -0
  574. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/timing/index.ts +7 -0
  575. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/timing/timing.ts +225 -0
  576. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/trailing-slash/index.ts +71 -0
  577. package/tests/fixtures/corpus/oss-repos/hono/src/preset/quick.ts +24 -0
  578. package/tests/fixtures/corpus/oss-repos/hono/src/preset/tiny.ts +20 -0
  579. package/tests/fixtures/corpus/oss-repos/hono/src/request.ts +403 -0
  580. package/tests/fixtures/corpus/oss-repos/hono/src/router/linear-router/index.ts +6 -0
  581. package/tests/fixtures/corpus/oss-repos/hono/src/router/linear-router/router.ts +132 -0
  582. package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/index.ts +6 -0
  583. package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/router.ts +54 -0
  584. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/index.ts +6 -0
  585. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/node.ts +159 -0
  586. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/router.ts +274 -0
  587. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/trie.ts +74 -0
  588. package/tests/fixtures/corpus/oss-repos/hono/src/router/smart-router/index.ts +6 -0
  589. package/tests/fixtures/corpus/oss-repos/hono/src/router/smart-router/router.ts +69 -0
  590. package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/index.ts +6 -0
  591. package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/node.ts +205 -0
  592. package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/router.ts +28 -0
  593. package/tests/fixtures/corpus/oss-repos/hono/src/router.ts +103 -0
  594. package/tests/fixtures/corpus/oss-repos/hono/src/types.ts +2009 -0
  595. package/tests/fixtures/corpus/oss-repos/hono/src/utils/basic-auth.ts +26 -0
  596. package/tests/fixtures/corpus/oss-repos/hono/src/utils/body.ts +225 -0
  597. package/tests/fixtures/corpus/oss-repos/hono/src/utils/buffer.ts +65 -0
  598. package/tests/fixtures/corpus/oss-repos/hono/src/utils/color.ts +26 -0
  599. package/tests/fixtures/corpus/oss-repos/hono/src/utils/concurrent.ts +55 -0
  600. package/tests/fixtures/corpus/oss-repos/hono/src/utils/cookie.ts +230 -0
  601. package/tests/fixtures/corpus/oss-repos/hono/src/utils/crypto.ts +65 -0
  602. package/tests/fixtures/corpus/oss-repos/hono/src/utils/encode.ts +34 -0
  603. package/tests/fixtures/corpus/oss-repos/hono/src/utils/filepath.ts +56 -0
  604. package/tests/fixtures/corpus/oss-repos/hono/src/utils/handler.ts +15 -0
  605. package/tests/fixtures/corpus/oss-repos/hono/src/utils/html.ts +182 -0
  606. package/tests/fixtures/corpus/oss-repos/hono/src/utils/http-status.ts +69 -0
  607. package/tests/fixtures/corpus/oss-repos/hono/src/utils/ipaddr.ts +113 -0
  608. package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/index.ts +7 -0
  609. package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/jwa.ts +23 -0
  610. package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/jws.ts +226 -0
  611. package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/jwt.ts +114 -0
  612. package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/types.ts +83 -0
  613. package/tests/fixtures/corpus/oss-repos/hono/src/utils/jwt/utf8.ts +7 -0
  614. package/tests/fixtures/corpus/oss-repos/hono/src/utils/mime.ts +142 -0
  615. package/tests/fixtures/corpus/oss-repos/hono/src/utils/stream.ts +96 -0
  616. package/tests/fixtures/corpus/oss-repos/hono/src/utils/types.ts +105 -0
  617. package/tests/fixtures/corpus/oss-repos/hono/src/utils/url.ts +310 -0
  618. package/tests/fixtures/corpus/oss-repos/hono/src/validator/index.ts +7 -0
  619. package/tests/fixtures/corpus/oss-repos/hono/src/validator/validator.ts +151 -0
  620. package/tests/fixtures/corpus/oss-repos/hono/tsconfig.build.json +23 -0
  621. package/tests/fixtures/corpus/oss-repos/hono/tsconfig.json +28 -0
  622. package/tests/fixtures/corpus/oss-repos/hono/vitest.config.ts +34 -0
  623. package/tests/fixtures/corpus/oss-repos/hono/yarn.lock +6232 -0
  624. package/tests/fixtures/documentation/api-reference.md +412 -0
  625. package/tests/fixtures/documentation/architecture.md +214 -0
  626. package/tests/fixtures/documentation/deployment-guide.md +420 -0
  627. package/tests/fixtures/github-readmes/express.md +133 -0
  628. package/tests/fixtures/github-readmes/nextjs.md +106 -0
  629. package/tests/fixtures/github-readmes/react.md +74 -0
  630. package/tests/fixtures/github-readmes/typescript.md +93 -0
  631. package/tests/fixtures/github-readmes/vite.md +79 -0
  632. package/tests/fixtures/queries/core.json +125 -0
  633. package/tests/fixtures/queries/extended.json +427 -0
  634. package/tests/fixtures/queries/generated/.gitkeep +0 -0
  635. package/tests/fixtures/test-server.ts +267 -0
  636. package/tests/helpers/performance-metrics.ts +387 -0
  637. package/tests/helpers/search-relevance.ts +381 -0
  638. package/tests/integration/cli-consistency.test.ts +299 -0
  639. package/tests/integration/cli.test.ts +69 -0
  640. package/tests/integration/e2e-workflow.test.ts +612 -0
  641. package/tests/integration/python-bridge.test.ts +183 -0
  642. package/tests/integration/search-quality.test.ts +718 -0
  643. package/tests/integration/stress.test.ts +326 -0
  644. package/tests/mcp/server.test.ts +15 -0
  645. package/tests/scripts/schemas/evaluation.json +44 -0
  646. package/tests/scripts/schemas/query-generation.json +21 -0
  647. package/tests/services/code-unit.service.test.ts +47 -0
  648. package/tests/services/search.progressive-context.test.ts +35 -0
  649. package/tsconfig.json +34 -0
  650. package/tsup.config.ts +15 -0
  651. package/turndown-plugin-gfm.d.ts +29 -0
  652. package/vitest.config.ts +79 -0
@@ -0,0 +1,2951 @@
1
+ # Bluera Knowledge CLI Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Build a CLI tool for managing knowledge stores with semantic search capabilities, providing full feature parity with the existing VS Code extension.
6
+
7
+ **Architecture:** TypeScript primary with Python subprocess bridge for web crawling. Unified service layer shared between CLI and HTTP API. LanceDB for vector storage, Transformers.js for embeddings.
8
+
9
+ **Tech Stack:** TypeScript, Node.js, Commander.js, LanceDB, Transformers.js, Hono, Zod, crawl4ai (Python)
10
+
11
+ ---
12
+
13
+ ## Phase 1: Project Scaffolding
14
+
15
+ ### Task 1.1: Initialize package.json
16
+
17
+ **Files:**
18
+ - Create: `package.json`
19
+
20
+ **Step 1: Create package.json**
21
+
22
+ ```json
23
+ {
24
+ "name": "bluera-knowledge",
25
+ "version": "0.1.0",
26
+ "description": "CLI tool for managing knowledge stores with semantic search",
27
+ "type": "module",
28
+ "bin": {
29
+ "bluera-knowledge": "./dist/index.js"
30
+ },
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "start": "node dist/index.js",
37
+ "test": "vitest",
38
+ "test:run": "vitest run",
39
+ "lint": "eslint src/",
40
+ "typecheck": "tsc --noEmit"
41
+ },
42
+ "keywords": ["knowledge", "semantic-search", "vector-database", "cli"],
43
+ "author": "Bluera",
44
+ "license": "MIT",
45
+ "engines": {
46
+ "node": ">=20.0.0"
47
+ }
48
+ }
49
+ ```
50
+
51
+ **Step 2: Commit**
52
+
53
+ ```bash
54
+ git add package.json
55
+ git commit -m "chore: initialize package.json"
56
+ ```
57
+
58
+ ---
59
+
60
+ ### Task 1.2: Configure TypeScript
61
+
62
+ **Files:**
63
+ - Create: `tsconfig.json`
64
+
65
+ **Step 1: Create tsconfig.json with strict settings**
66
+
67
+ ```json
68
+ {
69
+ "compilerOptions": {
70
+ "target": "ES2022",
71
+ "module": "NodeNext",
72
+ "moduleResolution": "NodeNext",
73
+ "lib": ["ES2022"],
74
+ "outDir": "dist",
75
+ "rootDir": "src",
76
+ "declaration": true,
77
+ "declarationMap": true,
78
+ "sourceMap": true,
79
+ "strict": true,
80
+ "noImplicitAny": true,
81
+ "strictNullChecks": true,
82
+ "strictFunctionTypes": true,
83
+ "strictBindCallApply": true,
84
+ "strictPropertyInitialization": true,
85
+ "noImplicitThis": true,
86
+ "useUnknownInCatchVariables": true,
87
+ "alwaysStrict": true,
88
+ "noUnusedLocals": true,
89
+ "noUnusedParameters": true,
90
+ "noImplicitReturns": true,
91
+ "noFallthroughCasesInSwitch": true,
92
+ "noUncheckedIndexedAccess": true,
93
+ "noImplicitOverride": true,
94
+ "noPropertyAccessFromIndexSignature": true,
95
+ "exactOptionalPropertyTypes": true,
96
+ "esModuleInterop": true,
97
+ "forceConsistentCasingInFileNames": true,
98
+ "skipLibCheck": true
99
+ },
100
+ "include": ["src/**/*"],
101
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
102
+ }
103
+ ```
104
+
105
+ **Step 2: Commit**
106
+
107
+ ```bash
108
+ git add tsconfig.json
109
+ git commit -m "chore: configure TypeScript with strict settings"
110
+ ```
111
+
112
+ ---
113
+
114
+ ### Task 1.3: Configure ESLint
115
+
116
+ **Files:**
117
+ - Create: `eslint.config.js`
118
+
119
+ **Step 1: Create eslint.config.js**
120
+
121
+ ```javascript
122
+ import eslint from '@eslint/js';
123
+ import tseslint from 'typescript-eslint';
124
+
125
+ export default tseslint.config(
126
+ eslint.configs.recommended,
127
+ ...tseslint.configs.strictTypeChecked,
128
+ {
129
+ languageOptions: {
130
+ parserOptions: {
131
+ projectService: true,
132
+ tsconfigRootDir: import.meta.dirname,
133
+ },
134
+ },
135
+ },
136
+ {
137
+ rules: {
138
+ '@typescript-eslint/no-explicit-any': 'error',
139
+ '@typescript-eslint/no-unsafe-assignment': 'error',
140
+ '@typescript-eslint/no-unsafe-member-access': 'error',
141
+ '@typescript-eslint/no-unsafe-call': 'error',
142
+ '@typescript-eslint/no-unsafe-return': 'error',
143
+ '@typescript-eslint/explicit-function-return-type': 'error',
144
+ '@typescript-eslint/explicit-module-boundary-types': 'error',
145
+ '@typescript-eslint/prefer-readonly': 'error',
146
+ '@typescript-eslint/strict-boolean-expressions': 'error',
147
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
148
+ },
149
+ },
150
+ {
151
+ ignores: ['dist/**', 'node_modules/**', '*.config.js'],
152
+ }
153
+ );
154
+ ```
155
+
156
+ **Step 2: Commit**
157
+
158
+ ```bash
159
+ git add eslint.config.js
160
+ git commit -m "chore: configure ESLint with strict TypeScript rules"
161
+ ```
162
+
163
+ ---
164
+
165
+ ### Task 1.4: Configure tsup build
166
+
167
+ **Files:**
168
+ - Create: `tsup.config.ts`
169
+
170
+ **Step 1: Create tsup.config.ts**
171
+
172
+ ```typescript
173
+ import { defineConfig } from 'tsup';
174
+
175
+ export default defineConfig({
176
+ entry: ['src/index.ts'],
177
+ format: ['esm'],
178
+ dts: true,
179
+ sourcemap: true,
180
+ clean: true,
181
+ target: 'node20',
182
+ shims: true,
183
+ });
184
+ ```
185
+
186
+ **Step 2: Commit**
187
+
188
+ ```bash
189
+ git add tsup.config.ts
190
+ git commit -m "chore: configure tsup build"
191
+ ```
192
+
193
+ ---
194
+
195
+ ### Task 1.5: Configure Vitest
196
+
197
+ **Files:**
198
+ - Create: `vitest.config.ts`
199
+
200
+ **Step 1: Create vitest.config.ts**
201
+
202
+ ```typescript
203
+ import { defineConfig } from 'vitest/config';
204
+
205
+ export default defineConfig({
206
+ test: {
207
+ globals: true,
208
+ environment: 'node',
209
+ include: ['src/**/*.test.ts', 'tests/**/*.test.ts'],
210
+ coverage: {
211
+ provider: 'v8',
212
+ reporter: ['text', 'json', 'html'],
213
+ include: ['src/**/*.ts'],
214
+ exclude: ['src/**/*.test.ts', 'src/index.ts'],
215
+ },
216
+ },
217
+ });
218
+ ```
219
+
220
+ **Step 2: Commit**
221
+
222
+ ```bash
223
+ git add vitest.config.ts
224
+ git commit -m "chore: configure Vitest"
225
+ ```
226
+
227
+ ---
228
+
229
+ ### Task 1.6: Update .gitignore
230
+
231
+ **Files:**
232
+ - Modify: `.gitignore`
233
+
234
+ **Step 1: Update .gitignore**
235
+
236
+ ```
237
+ node_modules/
238
+ dist/
239
+ .env
240
+ .env.local
241
+ *.log
242
+ .DS_Store
243
+ coverage/
244
+ .vscode/
245
+ *.local.md
246
+ ```
247
+
248
+ **Step 2: Commit**
249
+
250
+ ```bash
251
+ git add .gitignore
252
+ git commit -m "chore: update .gitignore"
253
+ ```
254
+
255
+ ---
256
+
257
+ ### Task 1.7: Install dependencies
258
+
259
+ **Files:**
260
+ - Modify: `package.json` (lockfile created)
261
+
262
+ **Step 1: Install production dependencies**
263
+
264
+ ```bash
265
+ npm install commander @lancedb/lancedb @huggingface/transformers chokidar hono @hono/node-server cli-table3 ora chalk zod
266
+ ```
267
+
268
+ **Step 2: Install dev dependencies**
269
+
270
+ ```bash
271
+ npm install -D typescript tsup vitest @types/node eslint @eslint/js typescript-eslint
272
+ ```
273
+
274
+ **Step 3: Commit**
275
+
276
+ ```bash
277
+ git add package.json package-lock.json
278
+ git commit -m "chore: install dependencies"
279
+ ```
280
+
281
+ ---
282
+
283
+ ### Task 1.8: Create directory structure
284
+
285
+ **Files:**
286
+ - Create: `src/index.ts`
287
+ - Create: `src/types/index.ts`
288
+ - Create: `src/services/index.ts`
289
+ - Create: `src/db/index.ts`
290
+ - Create: `src/cli/index.ts`
291
+ - Create: `src/server/index.ts`
292
+
293
+ **Step 1: Create src/index.ts (entry point)**
294
+
295
+ ```typescript
296
+ #!/usr/bin/env node
297
+
298
+ console.log('bkb CLI');
299
+ ```
300
+
301
+ **Step 2: Create empty module index files**
302
+
303
+ Create `src/types/index.ts`:
304
+ ```typescript
305
+ // Types module - no external dependencies
306
+ export {};
307
+ ```
308
+
309
+ Create `src/services/index.ts`:
310
+ ```typescript
311
+ // Services module
312
+ export {};
313
+ ```
314
+
315
+ Create `src/db/index.ts`:
316
+ ```typescript
317
+ // Database module
318
+ export {};
319
+ ```
320
+
321
+ Create `src/cli/index.ts`:
322
+ ```typescript
323
+ // CLI module
324
+ export {};
325
+ ```
326
+
327
+ Create `src/server/index.ts`:
328
+ ```typescript
329
+ // Server module
330
+ export {};
331
+ ```
332
+
333
+ **Step 3: Verify build works**
334
+
335
+ ```bash
336
+ npm run build
337
+ ```
338
+
339
+ Expected: Build succeeds, creates `dist/index.js`
340
+
341
+ **Step 4: Verify CLI runs**
342
+
343
+ ```bash
344
+ node dist/index.js
345
+ ```
346
+
347
+ Expected: Output `bkb CLI`
348
+
349
+ **Step 5: Commit**
350
+
351
+ ```bash
352
+ git add src/
353
+ git commit -m "chore: create directory structure"
354
+ ```
355
+
356
+ ---
357
+
358
+ ## Phase 2: Types Layer
359
+
360
+ ### Task 2.1: Create branded types
361
+
362
+ **Files:**
363
+ - Create: `src/types/brands.ts`
364
+ - Create: `src/types/brands.test.ts`
365
+
366
+ **Step 1: Write the failing test**
367
+
368
+ Create `src/types/brands.test.ts`:
369
+ ```typescript
370
+ import { describe, it, expect } from 'vitest';
371
+ import { createStoreId, createDocumentId, isStoreId, isDocumentId } from './brands.js';
372
+
373
+ describe('branded types', () => {
374
+ describe('StoreId', () => {
375
+ it('creates a valid store ID', () => {
376
+ const id = createStoreId('store-123');
377
+ expect(id).toBe('store-123');
378
+ });
379
+
380
+ it('validates store ID format', () => {
381
+ expect(isStoreId('valid-store-id')).toBe(true);
382
+ expect(isStoreId('')).toBe(false);
383
+ expect(isStoreId('has spaces')).toBe(false);
384
+ });
385
+ });
386
+
387
+ describe('DocumentId', () => {
388
+ it('creates a valid document ID', () => {
389
+ const id = createDocumentId('doc-456');
390
+ expect(id).toBe('doc-456');
391
+ });
392
+
393
+ it('validates document ID format', () => {
394
+ expect(isDocumentId('valid-doc-id')).toBe(true);
395
+ expect(isDocumentId('')).toBe(false);
396
+ });
397
+ });
398
+ });
399
+ ```
400
+
401
+ **Step 2: Run test to verify it fails**
402
+
403
+ ```bash
404
+ npm run test:run -- src/types/brands.test.ts
405
+ ```
406
+
407
+ Expected: FAIL - module not found
408
+
409
+ **Step 3: Write minimal implementation**
410
+
411
+ Create `src/types/brands.ts`:
412
+ ```typescript
413
+ // Branded type symbols
414
+ declare const StoreIdBrand: unique symbol;
415
+ declare const DocumentIdBrand: unique symbol;
416
+
417
+ // Branded types
418
+ export type StoreId = string & { readonly [StoreIdBrand]: typeof StoreIdBrand };
419
+ export type DocumentId = string & { readonly [DocumentIdBrand]: typeof DocumentIdBrand };
420
+
421
+ // Valid ID pattern: alphanumeric, hyphens, underscores
422
+ const ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
423
+
424
+ export function isStoreId(value: string): value is StoreId {
425
+ return value.length > 0 && ID_PATTERN.test(value);
426
+ }
427
+
428
+ export function isDocumentId(value: string): value is DocumentId {
429
+ return value.length > 0 && ID_PATTERN.test(value);
430
+ }
431
+
432
+ export function createStoreId(value: string): StoreId {
433
+ if (!isStoreId(value)) {
434
+ throw new Error(`Invalid store ID: ${value}`);
435
+ }
436
+ return value;
437
+ }
438
+
439
+ export function createDocumentId(value: string): DocumentId {
440
+ if (!isDocumentId(value)) {
441
+ throw new Error(`Invalid document ID: ${value}`);
442
+ }
443
+ return value;
444
+ }
445
+ ```
446
+
447
+ **Step 4: Run test to verify it passes**
448
+
449
+ ```bash
450
+ npm run test:run -- src/types/brands.test.ts
451
+ ```
452
+
453
+ Expected: PASS
454
+
455
+ **Step 5: Commit**
456
+
457
+ ```bash
458
+ git add src/types/brands.ts src/types/brands.test.ts
459
+ git commit -m "feat: add branded types for StoreId and DocumentId"
460
+ ```
461
+
462
+ ---
463
+
464
+ ### Task 2.2: Create Result type
465
+
466
+ **Files:**
467
+ - Create: `src/types/result.ts`
468
+ - Create: `src/types/result.test.ts`
469
+
470
+ **Step 1: Write the failing test**
471
+
472
+ Create `src/types/result.test.ts`:
473
+ ```typescript
474
+ import { describe, it, expect } from 'vitest';
475
+ import { ok, err, isOk, isErr, unwrap, unwrapOr } from './result.js';
476
+
477
+ describe('Result type', () => {
478
+ describe('ok', () => {
479
+ it('creates a success result', () => {
480
+ const result = ok(42);
481
+ expect(isOk(result)).toBe(true);
482
+ expect(isErr(result)).toBe(false);
483
+ });
484
+ });
485
+
486
+ describe('err', () => {
487
+ it('creates an error result', () => {
488
+ const result = err(new Error('failed'));
489
+ expect(isErr(result)).toBe(true);
490
+ expect(isOk(result)).toBe(false);
491
+ });
492
+ });
493
+
494
+ describe('unwrap', () => {
495
+ it('returns value for success', () => {
496
+ const result = ok(42);
497
+ expect(unwrap(result)).toBe(42);
498
+ });
499
+
500
+ it('throws for error', () => {
501
+ const result = err(new Error('failed'));
502
+ expect(() => unwrap(result)).toThrow('failed');
503
+ });
504
+ });
505
+
506
+ describe('unwrapOr', () => {
507
+ it('returns value for success', () => {
508
+ const result = ok(42);
509
+ expect(unwrapOr(result, 0)).toBe(42);
510
+ });
511
+
512
+ it('returns default for error', () => {
513
+ const result = err(new Error('failed'));
514
+ expect(unwrapOr(result, 0)).toBe(0);
515
+ });
516
+ });
517
+ });
518
+ ```
519
+
520
+ **Step 2: Run test to verify it fails**
521
+
522
+ ```bash
523
+ npm run test:run -- src/types/result.test.ts
524
+ ```
525
+
526
+ Expected: FAIL - module not found
527
+
528
+ **Step 3: Write minimal implementation**
529
+
530
+ Create `src/types/result.ts`:
531
+ ```typescript
532
+ export type Result<T, E = Error> =
533
+ | { readonly success: true; readonly data: T }
534
+ | { readonly success: false; readonly error: E };
535
+
536
+ export function ok<T>(data: T): Result<T, never> {
537
+ return { success: true, data };
538
+ }
539
+
540
+ export function err<E>(error: E): Result<never, E> {
541
+ return { success: false, error };
542
+ }
543
+
544
+ export function isOk<T, E>(result: Result<T, E>): result is { success: true; data: T } {
545
+ return result.success;
546
+ }
547
+
548
+ export function isErr<T, E>(result: Result<T, E>): result is { success: false; error: E } {
549
+ return !result.success;
550
+ }
551
+
552
+ export function unwrap<T, E>(result: Result<T, E>): T {
553
+ if (isOk(result)) {
554
+ return result.data;
555
+ }
556
+ if (result.error instanceof Error) {
557
+ throw result.error;
558
+ }
559
+ throw new Error(String(result.error));
560
+ }
561
+
562
+ export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
563
+ if (isOk(result)) {
564
+ return result.data;
565
+ }
566
+ return defaultValue;
567
+ }
568
+ ```
569
+
570
+ **Step 4: Run test to verify it passes**
571
+
572
+ ```bash
573
+ npm run test:run -- src/types/result.test.ts
574
+ ```
575
+
576
+ Expected: PASS
577
+
578
+ **Step 5: Commit**
579
+
580
+ ```bash
581
+ git add src/types/result.ts src/types/result.test.ts
582
+ git commit -m "feat: add Result type for error handling"
583
+ ```
584
+
585
+ ---
586
+
587
+ ### Task 2.3: Create Store types
588
+
589
+ **Files:**
590
+ - Create: `src/types/store.ts`
591
+ - Create: `src/types/store.test.ts`
592
+
593
+ **Step 1: Write the failing test**
594
+
595
+ Create `src/types/store.test.ts`:
596
+ ```typescript
597
+ import { describe, it, expect } from 'vitest';
598
+ import { isFileStore, isRepoStore, isWebStore } from './store.js';
599
+ import type { Store, FileStore, RepoStore, WebStore } from './store.js';
600
+ import { createStoreId } from './brands.js';
601
+
602
+ describe('Store types', () => {
603
+ const fileStore: FileStore = {
604
+ type: 'file',
605
+ id: createStoreId('file-store'),
606
+ name: 'My Files',
607
+ path: '/path/to/files',
608
+ description: 'Test file store',
609
+ createdAt: new Date(),
610
+ updatedAt: new Date(),
611
+ };
612
+
613
+ const repoStore: RepoStore = {
614
+ type: 'repo',
615
+ id: createStoreId('repo-store'),
616
+ name: 'My Repo',
617
+ path: '/path/to/repo',
618
+ branch: 'main',
619
+ createdAt: new Date(),
620
+ updatedAt: new Date(),
621
+ };
622
+
623
+ const webStore: WebStore = {
624
+ type: 'web',
625
+ id: createStoreId('web-store'),
626
+ name: 'Docs Site',
627
+ url: 'https://docs.example.com',
628
+ depth: 2,
629
+ createdAt: new Date(),
630
+ updatedAt: new Date(),
631
+ };
632
+
633
+ describe('isFileStore', () => {
634
+ it('returns true for file stores', () => {
635
+ expect(isFileStore(fileStore)).toBe(true);
636
+ });
637
+
638
+ it('returns false for other store types', () => {
639
+ expect(isFileStore(repoStore)).toBe(false);
640
+ expect(isFileStore(webStore)).toBe(false);
641
+ });
642
+ });
643
+
644
+ describe('isRepoStore', () => {
645
+ it('returns true for repo stores', () => {
646
+ expect(isRepoStore(repoStore)).toBe(true);
647
+ });
648
+
649
+ it('returns false for other store types', () => {
650
+ expect(isRepoStore(fileStore)).toBe(false);
651
+ expect(isRepoStore(webStore)).toBe(false);
652
+ });
653
+ });
654
+
655
+ describe('isWebStore', () => {
656
+ it('returns true for web stores', () => {
657
+ expect(isWebStore(webStore)).toBe(true);
658
+ });
659
+
660
+ it('returns false for other store types', () => {
661
+ expect(isWebStore(fileStore)).toBe(false);
662
+ expect(isWebStore(repoStore)).toBe(false);
663
+ });
664
+ });
665
+ });
666
+ ```
667
+
668
+ **Step 2: Run test to verify it fails**
669
+
670
+ ```bash
671
+ npm run test:run -- src/types/store.test.ts
672
+ ```
673
+
674
+ Expected: FAIL - module not found
675
+
676
+ **Step 3: Write minimal implementation**
677
+
678
+ Create `src/types/store.ts`:
679
+ ```typescript
680
+ import type { StoreId } from './brands.js';
681
+
682
+ export type StoreType = 'file' | 'repo' | 'web';
683
+ export type StoreStatus = 'ready' | 'indexing' | 'error';
684
+
685
+ interface BaseStore {
686
+ readonly id: StoreId;
687
+ readonly name: string;
688
+ readonly description?: string;
689
+ readonly tags?: readonly string[];
690
+ readonly status?: StoreStatus;
691
+ readonly createdAt: Date;
692
+ readonly updatedAt: Date;
693
+ }
694
+
695
+ export interface FileStore extends BaseStore {
696
+ readonly type: 'file';
697
+ readonly path: string;
698
+ }
699
+
700
+ export interface RepoStore extends BaseStore {
701
+ readonly type: 'repo';
702
+ readonly path: string;
703
+ readonly branch?: string;
704
+ }
705
+
706
+ export interface WebStore extends BaseStore {
707
+ readonly type: 'web';
708
+ readonly url: string;
709
+ readonly depth: number;
710
+ readonly maxPages?: number;
711
+ }
712
+
713
+ export type Store = FileStore | RepoStore | WebStore;
714
+
715
+ export function isFileStore(store: Store): store is FileStore {
716
+ return store.type === 'file';
717
+ }
718
+
719
+ export function isRepoStore(store: Store): store is RepoStore {
720
+ return store.type === 'repo';
721
+ }
722
+
723
+ export function isWebStore(store: Store): store is WebStore {
724
+ return store.type === 'web';
725
+ }
726
+ ```
727
+
728
+ **Step 4: Run test to verify it passes**
729
+
730
+ ```bash
731
+ npm run test:run -- src/types/store.test.ts
732
+ ```
733
+
734
+ Expected: PASS
735
+
736
+ **Step 5: Commit**
737
+
738
+ ```bash
739
+ git add src/types/store.ts src/types/store.test.ts
740
+ git commit -m "feat: add Store discriminated union types"
741
+ ```
742
+
743
+ ---
744
+
745
+ ### Task 2.4: Create Document types
746
+
747
+ **Files:**
748
+ - Create: `src/types/document.ts`
749
+
750
+ **Step 1: Create document types**
751
+
752
+ Create `src/types/document.ts`:
753
+ ```typescript
754
+ import type { DocumentId, StoreId } from './brands.js';
755
+
756
+ export type DocumentType = 'file' | 'chunk' | 'web';
757
+
758
+ export interface DocumentMetadata {
759
+ readonly path?: string;
760
+ readonly url?: string;
761
+ readonly type: DocumentType;
762
+ readonly storeId: StoreId;
763
+ readonly indexedAt: Date;
764
+ readonly fileHash?: string;
765
+ readonly chunkIndex?: number;
766
+ readonly totalChunks?: number;
767
+ readonly [key: string]: unknown;
768
+ }
769
+
770
+ export interface Document {
771
+ readonly id: DocumentId;
772
+ readonly content: string;
773
+ readonly vector: readonly number[];
774
+ readonly metadata: DocumentMetadata;
775
+ }
776
+
777
+ export interface DocumentChunk {
778
+ readonly id: DocumentId;
779
+ readonly content: string;
780
+ readonly startLine?: number;
781
+ readonly endLine?: number;
782
+ readonly metadata: DocumentMetadata;
783
+ }
784
+ ```
785
+
786
+ **Step 2: Commit**
787
+
788
+ ```bash
789
+ git add src/types/document.ts
790
+ git commit -m "feat: add Document types"
791
+ ```
792
+
793
+ ---
794
+
795
+ ### Task 2.5: Create Search types
796
+
797
+ **Files:**
798
+ - Create: `src/types/search.ts`
799
+
800
+ **Step 1: Create search types**
801
+
802
+ Create `src/types/search.ts`:
803
+ ```typescript
804
+ import type { StoreId, DocumentId } from './brands.js';
805
+ import type { DocumentMetadata } from './document.js';
806
+
807
+ export type SearchMode = 'vector' | 'fts' | 'hybrid';
808
+
809
+ export interface SearchQuery {
810
+ readonly query: string;
811
+ readonly stores?: readonly StoreId[];
812
+ readonly mode?: SearchMode;
813
+ readonly limit?: number;
814
+ readonly threshold?: number;
815
+ readonly filter?: Record<string, unknown>;
816
+ readonly includeContent?: boolean;
817
+ readonly contextLines?: number;
818
+ }
819
+
820
+ export interface SearchResult {
821
+ readonly id: DocumentId;
822
+ readonly score: number;
823
+ readonly content: string;
824
+ readonly highlight?: string;
825
+ readonly metadata: DocumentMetadata;
826
+ }
827
+
828
+ export interface SearchResponse {
829
+ readonly query: string;
830
+ readonly mode: SearchMode;
831
+ readonly stores: readonly StoreId[];
832
+ readonly results: readonly SearchResult[];
833
+ readonly totalResults: number;
834
+ readonly timeMs: number;
835
+ }
836
+ ```
837
+
838
+ **Step 2: Commit**
839
+
840
+ ```bash
841
+ git add src/types/search.ts
842
+ git commit -m "feat: add Search types"
843
+ ```
844
+
845
+ ---
846
+
847
+ ### Task 2.6: Create Config types
848
+
849
+ **Files:**
850
+ - Create: `src/types/config.ts`
851
+
852
+ **Step 1: Create config types**
853
+
854
+ Create `src/types/config.ts`:
855
+ ```typescript
856
+ export interface EmbeddingConfig {
857
+ readonly model: string;
858
+ readonly batchSize: number;
859
+ readonly dimensions: number;
860
+ }
861
+
862
+ export interface IndexingConfig {
863
+ readonly concurrency: number;
864
+ readonly chunkSize: number;
865
+ readonly chunkOverlap: number;
866
+ readonly ignorePatterns: readonly string[];
867
+ }
868
+
869
+ export interface SearchConfig {
870
+ readonly defaultMode: 'vector' | 'fts' | 'hybrid';
871
+ readonly defaultLimit: number;
872
+ readonly minScore: number;
873
+ readonly rrf: {
874
+ readonly k: number;
875
+ readonly vectorWeight: number;
876
+ readonly ftsWeight: number;
877
+ };
878
+ }
879
+
880
+ export interface CrawlConfig {
881
+ readonly userAgent: string;
882
+ readonly timeout: number;
883
+ readonly maxConcurrency: number;
884
+ }
885
+
886
+ export interface ServerConfig {
887
+ readonly port: number;
888
+ readonly host: string;
889
+ }
890
+
891
+ export interface AppConfig {
892
+ readonly version: number;
893
+ readonly dataDir: string;
894
+ readonly embedding: EmbeddingConfig;
895
+ readonly indexing: IndexingConfig;
896
+ readonly search: SearchConfig;
897
+ readonly crawl: CrawlConfig;
898
+ readonly server: ServerConfig;
899
+ }
900
+
901
+ export const DEFAULT_CONFIG: AppConfig = {
902
+ version: 1,
903
+ dataDir: '~/.bluera/knowledge-data',
904
+ embedding: {
905
+ model: 'Xenova/all-MiniLM-L6-v2',
906
+ batchSize: 32,
907
+ dimensions: 384,
908
+ },
909
+ indexing: {
910
+ concurrency: 4,
911
+ chunkSize: 512,
912
+ chunkOverlap: 50,
913
+ ignorePatterns: ['node_modules/**', '.git/**', '*.min.js', '*.map'],
914
+ },
915
+ search: {
916
+ defaultMode: 'hybrid',
917
+ defaultLimit: 10,
918
+ minScore: 0.5,
919
+ rrf: {
920
+ k: 60,
921
+ vectorWeight: 0.7,
922
+ ftsWeight: 0.3,
923
+ },
924
+ },
925
+ crawl: {
926
+ userAgent: 'BlueraKnowledge/1.0',
927
+ timeout: 30000,
928
+ maxConcurrency: 3,
929
+ },
930
+ server: {
931
+ port: 3847,
932
+ host: '127.0.0.1',
933
+ },
934
+ };
935
+ ```
936
+
937
+ **Step 2: Commit**
938
+
939
+ ```bash
940
+ git add src/types/config.ts
941
+ git commit -m "feat: add Config types with defaults"
942
+ ```
943
+
944
+ ---
945
+
946
+ ### Task 2.7: Create types barrel export
947
+
948
+ **Files:**
949
+ - Modify: `src/types/index.ts`
950
+
951
+ **Step 1: Update types index**
952
+
953
+ Update `src/types/index.ts`:
954
+ ```typescript
955
+ // Branded types
956
+ export {
957
+ type StoreId,
958
+ type DocumentId,
959
+ createStoreId,
960
+ createDocumentId,
961
+ isStoreId,
962
+ isDocumentId,
963
+ } from './brands.js';
964
+
965
+ // Result type
966
+ export {
967
+ type Result,
968
+ ok,
969
+ err,
970
+ isOk,
971
+ isErr,
972
+ unwrap,
973
+ unwrapOr,
974
+ } from './result.js';
975
+
976
+ // Store types
977
+ export {
978
+ type Store,
979
+ type FileStore,
980
+ type RepoStore,
981
+ type WebStore,
982
+ type StoreType,
983
+ type StoreStatus,
984
+ isFileStore,
985
+ isRepoStore,
986
+ isWebStore,
987
+ } from './store.js';
988
+
989
+ // Document types
990
+ export {
991
+ type Document,
992
+ type DocumentChunk,
993
+ type DocumentMetadata,
994
+ type DocumentType,
995
+ } from './document.js';
996
+
997
+ // Search types
998
+ export {
999
+ type SearchQuery,
1000
+ type SearchResult,
1001
+ type SearchResponse,
1002
+ type SearchMode,
1003
+ } from './search.js';
1004
+
1005
+ // Config types
1006
+ export {
1007
+ type AppConfig,
1008
+ type EmbeddingConfig,
1009
+ type IndexingConfig,
1010
+ type SearchConfig,
1011
+ type CrawlConfig,
1012
+ type ServerConfig,
1013
+ DEFAULT_CONFIG,
1014
+ } from './config.js';
1015
+ ```
1016
+
1017
+ **Step 2: Run all type tests**
1018
+
1019
+ ```bash
1020
+ npm run test:run -- src/types/
1021
+ ```
1022
+
1023
+ Expected: All tests pass
1024
+
1025
+ **Step 3: Commit**
1026
+
1027
+ ```bash
1028
+ git add src/types/index.ts
1029
+ git commit -m "feat: add types barrel export"
1030
+ ```
1031
+
1032
+ ---
1033
+
1034
+ ## Phase 3: Data Layer
1035
+
1036
+ ### Task 3.1: Create embedding engine
1037
+
1038
+ **Files:**
1039
+ - Create: `src/db/embeddings.ts`
1040
+ - Create: `src/db/embeddings.test.ts`
1041
+
1042
+ **Step 1: Write the failing test**
1043
+
1044
+ Create `src/db/embeddings.test.ts`:
1045
+ ```typescript
1046
+ import { describe, it, expect, beforeAll } from 'vitest';
1047
+ import { EmbeddingEngine } from './embeddings.js';
1048
+
1049
+ describe('EmbeddingEngine', () => {
1050
+ let engine: EmbeddingEngine;
1051
+
1052
+ beforeAll(async () => {
1053
+ engine = new EmbeddingEngine();
1054
+ await engine.initialize();
1055
+ }, 60000); // Allow time for model download
1056
+
1057
+ it('generates embeddings for text', async () => {
1058
+ const embedding = await engine.embed('Hello world');
1059
+ expect(embedding).toHaveLength(384);
1060
+ expect(embedding.every((n) => typeof n === 'number')).toBe(true);
1061
+ });
1062
+
1063
+ it('generates batch embeddings', async () => {
1064
+ const texts = ['Hello', 'World', 'Test'];
1065
+ const embeddings = await engine.embedBatch(texts);
1066
+ expect(embeddings).toHaveLength(3);
1067
+ expect(embeddings.every((e) => e.length === 384)).toBe(true);
1068
+ });
1069
+
1070
+ it('produces similar embeddings for similar text', async () => {
1071
+ const emb1 = await engine.embed('The cat sat on the mat');
1072
+ const emb2 = await engine.embed('A cat was sitting on a rug');
1073
+ const emb3 = await engine.embed('Quantum physics is complex');
1074
+
1075
+ const sim12 = cosineSimilarity(emb1, emb2);
1076
+ const sim13 = cosineSimilarity(emb1, emb3);
1077
+
1078
+ expect(sim12).toBeGreaterThan(sim13);
1079
+ });
1080
+ });
1081
+
1082
+ function cosineSimilarity(a: number[], b: number[]): number {
1083
+ let dotProduct = 0;
1084
+ let normA = 0;
1085
+ let normB = 0;
1086
+ for (let i = 0; i < a.length; i++) {
1087
+ dotProduct += a[i]! * b[i]!;
1088
+ normA += a[i]! * a[i]!;
1089
+ normB += b[i]! * b[i]!;
1090
+ }
1091
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
1092
+ }
1093
+ ```
1094
+
1095
+ **Step 2: Run test to verify it fails**
1096
+
1097
+ ```bash
1098
+ npm run test:run -- src/db/embeddings.test.ts
1099
+ ```
1100
+
1101
+ Expected: FAIL - module not found
1102
+
1103
+ **Step 3: Write minimal implementation**
1104
+
1105
+ Create `src/db/embeddings.ts`:
1106
+ ```typescript
1107
+ import { pipeline, type FeatureExtractionPipeline } from '@huggingface/transformers';
1108
+
1109
+ export class EmbeddingEngine {
1110
+ private extractor: FeatureExtractionPipeline | null = null;
1111
+ private readonly modelName: string;
1112
+ private readonly dimensions: number;
1113
+
1114
+ constructor(modelName = 'Xenova/all-MiniLM-L6-v2', dimensions = 384) {
1115
+ this.modelName = modelName;
1116
+ this.dimensions = dimensions;
1117
+ }
1118
+
1119
+ async initialize(): Promise<void> {
1120
+ if (this.extractor !== null) return;
1121
+ this.extractor = await pipeline('feature-extraction', this.modelName, {
1122
+ dtype: 'fp32',
1123
+ });
1124
+ }
1125
+
1126
+ async embed(text: string): Promise<number[]> {
1127
+ if (this.extractor === null) {
1128
+ await this.initialize();
1129
+ }
1130
+ const output = await this.extractor!(text, {
1131
+ pooling: 'mean',
1132
+ normalize: true,
1133
+ });
1134
+ return Array.from(output.data as Float32Array);
1135
+ }
1136
+
1137
+ async embedBatch(texts: string[]): Promise<number[][]> {
1138
+ const results: number[][] = [];
1139
+ for (const text of texts) {
1140
+ results.push(await this.embed(text));
1141
+ }
1142
+ return results;
1143
+ }
1144
+
1145
+ getDimensions(): number {
1146
+ return this.dimensions;
1147
+ }
1148
+ }
1149
+ ```
1150
+
1151
+ **Step 4: Run test to verify it passes**
1152
+
1153
+ ```bash
1154
+ npm run test:run -- src/db/embeddings.test.ts
1155
+ ```
1156
+
1157
+ Expected: PASS (may take time for model download on first run)
1158
+
1159
+ **Step 5: Commit**
1160
+
1161
+ ```bash
1162
+ git add src/db/embeddings.ts src/db/embeddings.test.ts
1163
+ git commit -m "feat: add EmbeddingEngine with Transformers.js"
1164
+ ```
1165
+
1166
+ ---
1167
+
1168
+ ### Task 3.2: Create LanceDB wrapper
1169
+
1170
+ **Files:**
1171
+ - Create: `src/db/lance.ts`
1172
+ - Create: `src/db/lance.test.ts`
1173
+
1174
+ **Step 1: Write the failing test**
1175
+
1176
+ Create `src/db/lance.test.ts`:
1177
+ ```typescript
1178
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
1179
+ import { LanceStore } from './lance.js';
1180
+ import { createStoreId, createDocumentId } from '../types/brands.js';
1181
+ import { rm, mkdtemp } from 'node:fs/promises';
1182
+ import { tmpdir } from 'node:os';
1183
+ import { join } from 'node:path';
1184
+
1185
+ describe('LanceStore', () => {
1186
+ let store: LanceStore;
1187
+ let tempDir: string;
1188
+ const storeId = createStoreId('test-store');
1189
+
1190
+ beforeAll(async () => {
1191
+ tempDir = await mkdtemp(join(tmpdir(), 'lance-test-'));
1192
+ store = new LanceStore(tempDir);
1193
+ await store.initialize(storeId);
1194
+ });
1195
+
1196
+ afterAll(async () => {
1197
+ await rm(tempDir, { recursive: true, force: true });
1198
+ });
1199
+
1200
+ it('adds and retrieves documents', async () => {
1201
+ const doc = {
1202
+ id: createDocumentId('doc-1'),
1203
+ content: 'Test content',
1204
+ vector: new Array(384).fill(0.1),
1205
+ metadata: {
1206
+ type: 'file' as const,
1207
+ storeId,
1208
+ indexedAt: new Date(),
1209
+ path: '/test/file.txt',
1210
+ },
1211
+ };
1212
+
1213
+ await store.addDocuments(storeId, [doc]);
1214
+
1215
+ const results = await store.search(storeId, new Array(384).fill(0.1), 10);
1216
+ expect(results.length).toBeGreaterThan(0);
1217
+ expect(results[0]?.id).toBe('doc-1');
1218
+ });
1219
+
1220
+ it('deletes documents', async () => {
1221
+ const docId = createDocumentId('doc-to-delete');
1222
+ const doc = {
1223
+ id: docId,
1224
+ content: 'Delete me',
1225
+ vector: new Array(384).fill(0.2),
1226
+ metadata: {
1227
+ type: 'file' as const,
1228
+ storeId,
1229
+ indexedAt: new Date(),
1230
+ },
1231
+ };
1232
+
1233
+ await store.addDocuments(storeId, [doc]);
1234
+ await store.deleteDocuments(storeId, [docId]);
1235
+
1236
+ const results = await store.search(storeId, new Array(384).fill(0.2), 10);
1237
+ const found = results.find((r) => r.id === 'doc-to-delete');
1238
+ expect(found).toBeUndefined();
1239
+ });
1240
+ });
1241
+ ```
1242
+
1243
+ **Step 2: Run test to verify it fails**
1244
+
1245
+ ```bash
1246
+ npm run test:run -- src/db/lance.test.ts
1247
+ ```
1248
+
1249
+ Expected: FAIL - module not found
1250
+
1251
+ **Step 3: Write minimal implementation**
1252
+
1253
+ Create `src/db/lance.ts`:
1254
+ ```typescript
1255
+ import * as lancedb from '@lancedb/lancedb';
1256
+ import type { Table, Connection } from '@lancedb/lancedb';
1257
+ import type { Document, DocumentMetadata } from '../types/document.js';
1258
+ import type { StoreId, DocumentId } from '../types/brands.js';
1259
+ import { createDocumentId } from '../types/brands.js';
1260
+
1261
+ interface LanceDocument {
1262
+ id: string;
1263
+ content: string;
1264
+ vector: number[];
1265
+ metadata: string; // JSON serialized
1266
+ }
1267
+
1268
+ interface SearchHit {
1269
+ id: string;
1270
+ content: string;
1271
+ metadata: string;
1272
+ _distance: number;
1273
+ }
1274
+
1275
+ export class LanceStore {
1276
+ private connection: Connection | null = null;
1277
+ private tables: Map<string, Table> = new Map();
1278
+ private readonly dataDir: string;
1279
+
1280
+ constructor(dataDir: string) {
1281
+ this.dataDir = dataDir;
1282
+ }
1283
+
1284
+ async initialize(storeId: StoreId): Promise<void> {
1285
+ if (this.connection === null) {
1286
+ this.connection = await lancedb.connect(this.dataDir);
1287
+ }
1288
+
1289
+ const tableName = this.getTableName(storeId);
1290
+ const tableNames = await this.connection.tableNames();
1291
+
1292
+ if (!tableNames.includes(tableName)) {
1293
+ // Create table with initial schema
1294
+ const table = await this.connection.createTable(tableName, [
1295
+ {
1296
+ id: '__init__',
1297
+ content: '',
1298
+ vector: new Array(384).fill(0),
1299
+ metadata: '{}',
1300
+ },
1301
+ ]);
1302
+ // Delete the init row
1303
+ await table.delete('id = "__init__"');
1304
+ this.tables.set(tableName, table);
1305
+ } else {
1306
+ const table = await this.connection.openTable(tableName);
1307
+ this.tables.set(tableName, table);
1308
+ }
1309
+ }
1310
+
1311
+ async addDocuments(storeId: StoreId, documents: Document[]): Promise<void> {
1312
+ const table = await this.getTable(storeId);
1313
+ const lanceDocuments: LanceDocument[] = documents.map((doc) => ({
1314
+ id: doc.id,
1315
+ content: doc.content,
1316
+ vector: [...doc.vector],
1317
+ metadata: JSON.stringify(doc.metadata),
1318
+ }));
1319
+ await table.add(lanceDocuments);
1320
+ }
1321
+
1322
+ async deleteDocuments(storeId: StoreId, documentIds: DocumentId[]): Promise<void> {
1323
+ const table = await this.getTable(storeId);
1324
+ const idList = documentIds.map((id) => `"${id}"`).join(', ');
1325
+ await table.delete(`id IN (${idList})`);
1326
+ }
1327
+
1328
+ async search(
1329
+ storeId: StoreId,
1330
+ vector: number[],
1331
+ limit: number,
1332
+ threshold?: number
1333
+ ): Promise<Array<{ id: DocumentId; content: string; score: number; metadata: DocumentMetadata }>> {
1334
+ const table = await this.getTable(storeId);
1335
+ let query = table.vectorSearch(vector).limit(limit);
1336
+
1337
+ if (threshold !== undefined) {
1338
+ query = query.distanceType('cosine');
1339
+ }
1340
+
1341
+ const results = (await query.toArray()) as SearchHit[];
1342
+
1343
+ return results
1344
+ .filter((r) => {
1345
+ if (threshold === undefined) return true;
1346
+ const score = 1 - r._distance;
1347
+ return score >= threshold;
1348
+ })
1349
+ .map((r) => ({
1350
+ id: createDocumentId(r.id),
1351
+ content: r.content,
1352
+ score: 1 - r._distance,
1353
+ metadata: JSON.parse(r.metadata) as DocumentMetadata,
1354
+ }));
1355
+ }
1356
+
1357
+ async deleteStore(storeId: StoreId): Promise<void> {
1358
+ const tableName = this.getTableName(storeId);
1359
+ if (this.connection !== null) {
1360
+ await this.connection.dropTable(tableName);
1361
+ this.tables.delete(tableName);
1362
+ }
1363
+ }
1364
+
1365
+ private getTableName(storeId: StoreId): string {
1366
+ return `documents_${storeId}`;
1367
+ }
1368
+
1369
+ private async getTable(storeId: StoreId): Promise<Table> {
1370
+ const tableName = this.getTableName(storeId);
1371
+ let table = this.tables.get(tableName);
1372
+ if (table === undefined) {
1373
+ await this.initialize(storeId);
1374
+ table = this.tables.get(tableName);
1375
+ }
1376
+ if (table === undefined) {
1377
+ throw new Error(`Table not found for store: ${storeId}`);
1378
+ }
1379
+ return table;
1380
+ }
1381
+ }
1382
+ ```
1383
+
1384
+ **Step 4: Run test to verify it passes**
1385
+
1386
+ ```bash
1387
+ npm run test:run -- src/db/lance.test.ts
1388
+ ```
1389
+
1390
+ Expected: PASS
1391
+
1392
+ **Step 5: Commit**
1393
+
1394
+ ```bash
1395
+ git add src/db/lance.ts src/db/lance.test.ts
1396
+ git commit -m "feat: add LanceDB wrapper for vector storage"
1397
+ ```
1398
+
1399
+ ---
1400
+
1401
+ ### Task 3.3: Update db barrel export
1402
+
1403
+ **Files:**
1404
+ - Modify: `src/db/index.ts`
1405
+
1406
+ **Step 1: Update db index**
1407
+
1408
+ Update `src/db/index.ts`:
1409
+ ```typescript
1410
+ export { EmbeddingEngine } from './embeddings.js';
1411
+ export { LanceStore } from './lance.js';
1412
+ ```
1413
+
1414
+ **Step 2: Run all db tests**
1415
+
1416
+ ```bash
1417
+ npm run test:run -- src/db/
1418
+ ```
1419
+
1420
+ Expected: All tests pass
1421
+
1422
+ **Step 3: Commit**
1423
+
1424
+ ```bash
1425
+ git add src/db/index.ts
1426
+ git commit -m "feat: add db barrel export"
1427
+ ```
1428
+
1429
+ ---
1430
+
1431
+ ## Phase 4: Services Layer
1432
+
1433
+ ### Task 4.1: Create ConfigService
1434
+
1435
+ **Files:**
1436
+ - Create: `src/services/config.service.ts`
1437
+ - Create: `src/services/config.service.test.ts`
1438
+
1439
+ **Step 1: Write the failing test**
1440
+
1441
+ Create `src/services/config.service.test.ts`:
1442
+ ```typescript
1443
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
1444
+ import { ConfigService } from './config.service.js';
1445
+ import { rm, mkdtemp } from 'node:fs/promises';
1446
+ import { tmpdir } from 'node:os';
1447
+ import { join } from 'node:path';
1448
+
1449
+ describe('ConfigService', () => {
1450
+ let configService: ConfigService;
1451
+ let tempDir: string;
1452
+ let configPath: string;
1453
+
1454
+ beforeEach(async () => {
1455
+ tempDir = await mkdtemp(join(tmpdir(), 'config-test-'));
1456
+ configPath = join(tempDir, 'config.json');
1457
+ configService = new ConfigService(configPath, tempDir);
1458
+ });
1459
+
1460
+ afterEach(async () => {
1461
+ await rm(tempDir, { recursive: true, force: true });
1462
+ });
1463
+
1464
+ it('loads default config when no file exists', async () => {
1465
+ const config = await configService.load();
1466
+ expect(config.version).toBe(1);
1467
+ expect(config.embedding.model).toBe('Xenova/all-MiniLM-L6-v2');
1468
+ });
1469
+
1470
+ it('saves and loads config', async () => {
1471
+ const config = await configService.load();
1472
+ config.search.defaultLimit = 20;
1473
+ await configService.save(config);
1474
+
1475
+ const loaded = await configService.load();
1476
+ expect(loaded.search.defaultLimit).toBe(20);
1477
+ });
1478
+
1479
+ it('resolves data directory path', async () => {
1480
+ const config = await configService.load();
1481
+ const dataDir = configService.resolveDataDir();
1482
+ expect(dataDir).toBe(tempDir);
1483
+ });
1484
+ });
1485
+ ```
1486
+
1487
+ **Step 2: Run test to verify it fails**
1488
+
1489
+ ```bash
1490
+ npm run test:run -- src/services/config.service.test.ts
1491
+ ```
1492
+
1493
+ Expected: FAIL - module not found
1494
+
1495
+ **Step 3: Write minimal implementation**
1496
+
1497
+ Create `src/services/config.service.ts`:
1498
+ ```typescript
1499
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
1500
+ import { dirname } from 'node:path';
1501
+ import { homedir } from 'node:os';
1502
+ import type { AppConfig } from '../types/config.js';
1503
+ import { DEFAULT_CONFIG } from '../types/config.js';
1504
+
1505
+ export class ConfigService {
1506
+ private readonly configPath: string;
1507
+ private readonly dataDir: string;
1508
+ private config: AppConfig | null = null;
1509
+
1510
+ constructor(
1511
+ configPath = `${homedir()}/.bluera/knowledge.json`,
1512
+ dataDir?: string
1513
+ ) {
1514
+ this.configPath = configPath;
1515
+ this.dataDir = dataDir ?? this.expandPath(DEFAULT_CONFIG.dataDir);
1516
+ }
1517
+
1518
+ async load(): Promise<AppConfig> {
1519
+ if (this.config !== null) {
1520
+ return this.config;
1521
+ }
1522
+
1523
+ try {
1524
+ const content = await readFile(this.configPath, 'utf-8');
1525
+ this.config = { ...DEFAULT_CONFIG, ...JSON.parse(content) } as AppConfig;
1526
+ } catch {
1527
+ this.config = { ...DEFAULT_CONFIG };
1528
+ }
1529
+
1530
+ return this.config;
1531
+ }
1532
+
1533
+ async save(config: AppConfig): Promise<void> {
1534
+ await mkdir(dirname(this.configPath), { recursive: true });
1535
+ await writeFile(this.configPath, JSON.stringify(config, null, 2));
1536
+ this.config = config;
1537
+ }
1538
+
1539
+ resolveDataDir(): string {
1540
+ return this.dataDir;
1541
+ }
1542
+
1543
+ private expandPath(path: string): string {
1544
+ if (path.startsWith('~')) {
1545
+ return path.replace('~', homedir());
1546
+ }
1547
+ return path;
1548
+ }
1549
+ }
1550
+ ```
1551
+
1552
+ **Step 4: Run test to verify it passes**
1553
+
1554
+ ```bash
1555
+ npm run test:run -- src/services/config.service.test.ts
1556
+ ```
1557
+
1558
+ Expected: PASS
1559
+
1560
+ **Step 5: Commit**
1561
+
1562
+ ```bash
1563
+ git add src/services/config.service.ts src/services/config.service.test.ts
1564
+ git commit -m "feat: add ConfigService"
1565
+ ```
1566
+
1567
+ ---
1568
+
1569
+ ### Task 4.2: Create StoreService
1570
+
1571
+ **Files:**
1572
+ - Create: `src/services/store.service.ts`
1573
+ - Create: `src/services/store.service.test.ts`
1574
+
1575
+ **Step 1: Write the failing test**
1576
+
1577
+ Create `src/services/store.service.test.ts`:
1578
+ ```typescript
1579
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
1580
+ import { StoreService } from './store.service.js';
1581
+ import { rm, mkdtemp } from 'node:fs/promises';
1582
+ import { tmpdir } from 'node:os';
1583
+ import { join } from 'node:path';
1584
+
1585
+ describe('StoreService', () => {
1586
+ let storeService: StoreService;
1587
+ let tempDir: string;
1588
+
1589
+ beforeEach(async () => {
1590
+ tempDir = await mkdtemp(join(tmpdir(), 'store-test-'));
1591
+ storeService = new StoreService(tempDir);
1592
+ await storeService.initialize();
1593
+ });
1594
+
1595
+ afterEach(async () => {
1596
+ await rm(tempDir, { recursive: true, force: true });
1597
+ });
1598
+
1599
+ it('creates a file store', async () => {
1600
+ const result = await storeService.create({
1601
+ name: 'My Files',
1602
+ type: 'file',
1603
+ path: '/path/to/files',
1604
+ });
1605
+
1606
+ expect(result.success).toBe(true);
1607
+ if (result.success) {
1608
+ expect(result.data.name).toBe('My Files');
1609
+ expect(result.data.type).toBe('file');
1610
+ }
1611
+ });
1612
+
1613
+ it('lists all stores', async () => {
1614
+ await storeService.create({ name: 'Store 1', type: 'file', path: '/path/1' });
1615
+ await storeService.create({ name: 'Store 2', type: 'repo', path: '/path/2' });
1616
+
1617
+ const stores = await storeService.list();
1618
+ expect(stores).toHaveLength(2);
1619
+ });
1620
+
1621
+ it('gets store by ID', async () => {
1622
+ const createResult = await storeService.create({
1623
+ name: 'Test Store',
1624
+ type: 'file',
1625
+ path: '/path/test',
1626
+ });
1627
+
1628
+ if (!createResult.success) throw new Error('Create failed');
1629
+
1630
+ const store = await storeService.get(createResult.data.id);
1631
+ expect(store?.name).toBe('Test Store');
1632
+ });
1633
+
1634
+ it('gets store by name', async () => {
1635
+ await storeService.create({
1636
+ name: 'Named Store',
1637
+ type: 'file',
1638
+ path: '/path/named',
1639
+ });
1640
+
1641
+ const store = await storeService.getByName('Named Store');
1642
+ expect(store?.name).toBe('Named Store');
1643
+ });
1644
+
1645
+ it('deletes a store', async () => {
1646
+ const createResult = await storeService.create({
1647
+ name: 'To Delete',
1648
+ type: 'file',
1649
+ path: '/path/delete',
1650
+ });
1651
+
1652
+ if (!createResult.success) throw new Error('Create failed');
1653
+
1654
+ const deleteResult = await storeService.delete(createResult.data.id);
1655
+ expect(deleteResult.success).toBe(true);
1656
+
1657
+ const stores = await storeService.list();
1658
+ expect(stores).toHaveLength(0);
1659
+ });
1660
+ });
1661
+ ```
1662
+
1663
+ **Step 2: Run test to verify it fails**
1664
+
1665
+ ```bash
1666
+ npm run test:run -- src/services/store.service.test.ts
1667
+ ```
1668
+
1669
+ Expected: FAIL - module not found
1670
+
1671
+ **Step 3: Write minimal implementation**
1672
+
1673
+ Create `src/services/store.service.ts`:
1674
+ ```typescript
1675
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
1676
+ import { join } from 'node:path';
1677
+ import { randomUUID } from 'node:crypto';
1678
+ import type { Store, FileStore, RepoStore, WebStore, StoreType } from '../types/store.js';
1679
+ import type { StoreId } from '../types/brands.js';
1680
+ import { createStoreId } from '../types/brands.js';
1681
+ import type { Result } from '../types/result.js';
1682
+ import { ok, err } from '../types/result.js';
1683
+
1684
+ interface CreateStoreInput {
1685
+ name: string;
1686
+ type: StoreType;
1687
+ path?: string;
1688
+ url?: string;
1689
+ description?: string;
1690
+ tags?: string[];
1691
+ branch?: string;
1692
+ depth?: number;
1693
+ }
1694
+
1695
+ interface StoreRegistry {
1696
+ stores: Store[];
1697
+ }
1698
+
1699
+ export class StoreService {
1700
+ private readonly dataDir: string;
1701
+ private registry: StoreRegistry = { stores: [] };
1702
+
1703
+ constructor(dataDir: string) {
1704
+ this.dataDir = dataDir;
1705
+ }
1706
+
1707
+ async initialize(): Promise<void> {
1708
+ await mkdir(this.dataDir, { recursive: true });
1709
+ await this.loadRegistry();
1710
+ }
1711
+
1712
+ async create(input: CreateStoreInput): Promise<Result<Store>> {
1713
+ const existing = await this.getByName(input.name);
1714
+ if (existing !== undefined) {
1715
+ return err(new Error(`Store with name "${input.name}" already exists`));
1716
+ }
1717
+
1718
+ const id = createStoreId(randomUUID());
1719
+ const now = new Date();
1720
+
1721
+ let store: Store;
1722
+
1723
+ switch (input.type) {
1724
+ case 'file':
1725
+ if (input.path === undefined) {
1726
+ return err(new Error('Path is required for file stores'));
1727
+ }
1728
+ store = {
1729
+ type: 'file',
1730
+ id,
1731
+ name: input.name,
1732
+ path: input.path,
1733
+ description: input.description,
1734
+ tags: input.tags,
1735
+ status: 'ready',
1736
+ createdAt: now,
1737
+ updatedAt: now,
1738
+ } satisfies FileStore;
1739
+ break;
1740
+
1741
+ case 'repo':
1742
+ if (input.path === undefined) {
1743
+ return err(new Error('Path is required for repo stores'));
1744
+ }
1745
+ store = {
1746
+ type: 'repo',
1747
+ id,
1748
+ name: input.name,
1749
+ path: input.path,
1750
+ branch: input.branch,
1751
+ description: input.description,
1752
+ tags: input.tags,
1753
+ status: 'ready',
1754
+ createdAt: now,
1755
+ updatedAt: now,
1756
+ } satisfies RepoStore;
1757
+ break;
1758
+
1759
+ case 'web':
1760
+ if (input.url === undefined) {
1761
+ return err(new Error('URL is required for web stores'));
1762
+ }
1763
+ store = {
1764
+ type: 'web',
1765
+ id,
1766
+ name: input.name,
1767
+ url: input.url,
1768
+ depth: input.depth ?? 1,
1769
+ description: input.description,
1770
+ tags: input.tags,
1771
+ status: 'ready',
1772
+ createdAt: now,
1773
+ updatedAt: now,
1774
+ } satisfies WebStore;
1775
+ break;
1776
+ }
1777
+
1778
+ this.registry.stores.push(store);
1779
+ await this.saveRegistry();
1780
+
1781
+ return ok(store);
1782
+ }
1783
+
1784
+ async list(type?: StoreType): Promise<Store[]> {
1785
+ if (type !== undefined) {
1786
+ return this.registry.stores.filter((s) => s.type === type);
1787
+ }
1788
+ return [...this.registry.stores];
1789
+ }
1790
+
1791
+ async get(id: StoreId): Promise<Store | undefined> {
1792
+ return this.registry.stores.find((s) => s.id === id);
1793
+ }
1794
+
1795
+ async getByName(name: string): Promise<Store | undefined> {
1796
+ return this.registry.stores.find((s) => s.name === name);
1797
+ }
1798
+
1799
+ async getByIdOrName(idOrName: string): Promise<Store | undefined> {
1800
+ return this.registry.stores.find((s) => s.id === idOrName || s.name === idOrName);
1801
+ }
1802
+
1803
+ async update(id: StoreId, updates: Partial<Pick<Store, 'name' | 'description' | 'tags'>>): Promise<Result<Store>> {
1804
+ const index = this.registry.stores.findIndex((s) => s.id === id);
1805
+ if (index === -1) {
1806
+ return err(new Error(`Store not found: ${id}`));
1807
+ }
1808
+
1809
+ const store = this.registry.stores[index]!;
1810
+ const updated = {
1811
+ ...store,
1812
+ ...updates,
1813
+ updatedAt: new Date(),
1814
+ } as Store;
1815
+
1816
+ this.registry.stores[index] = updated;
1817
+ await this.saveRegistry();
1818
+
1819
+ return ok(updated);
1820
+ }
1821
+
1822
+ async delete(id: StoreId): Promise<Result<void>> {
1823
+ const index = this.registry.stores.findIndex((s) => s.id === id);
1824
+ if (index === -1) {
1825
+ return err(new Error(`Store not found: ${id}`));
1826
+ }
1827
+
1828
+ this.registry.stores.splice(index, 1);
1829
+ await this.saveRegistry();
1830
+
1831
+ return ok(undefined);
1832
+ }
1833
+
1834
+ private async loadRegistry(): Promise<void> {
1835
+ const registryPath = join(this.dataDir, 'stores.json');
1836
+ try {
1837
+ const content = await readFile(registryPath, 'utf-8');
1838
+ const data = JSON.parse(content) as { stores: Store[] };
1839
+ this.registry = {
1840
+ stores: data.stores.map((s) => ({
1841
+ ...s,
1842
+ id: createStoreId(s.id),
1843
+ createdAt: new Date(s.createdAt),
1844
+ updatedAt: new Date(s.updatedAt),
1845
+ })),
1846
+ };
1847
+ } catch {
1848
+ this.registry = { stores: [] };
1849
+ }
1850
+ }
1851
+
1852
+ private async saveRegistry(): Promise<void> {
1853
+ const registryPath = join(this.dataDir, 'stores.json');
1854
+ await writeFile(registryPath, JSON.stringify(this.registry, null, 2));
1855
+ }
1856
+ }
1857
+ ```
1858
+
1859
+ **Step 4: Run test to verify it passes**
1860
+
1861
+ ```bash
1862
+ npm run test:run -- src/services/store.service.test.ts
1863
+ ```
1864
+
1865
+ Expected: PASS
1866
+
1867
+ **Step 5: Commit**
1868
+
1869
+ ```bash
1870
+ git add src/services/store.service.ts src/services/store.service.test.ts
1871
+ git commit -m "feat: add StoreService for store CRUD operations"
1872
+ ```
1873
+
1874
+ ---
1875
+
1876
+ ### Task 4.3: Create SearchService
1877
+
1878
+ **Files:**
1879
+ - Create: `src/services/search.service.ts`
1880
+ - Create: `src/services/search.service.test.ts`
1881
+
1882
+ **Step 1: Write the failing test**
1883
+
1884
+ Create `src/services/search.service.test.ts`:
1885
+ ```typescript
1886
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
1887
+ import { SearchService } from './search.service.js';
1888
+ import { LanceStore } from '../db/lance.js';
1889
+ import { EmbeddingEngine } from '../db/embeddings.js';
1890
+ import { createStoreId, createDocumentId } from '../types/brands.js';
1891
+ import { rm, mkdtemp } from 'node:fs/promises';
1892
+ import { tmpdir } from 'node:os';
1893
+ import { join } from 'node:path';
1894
+
1895
+ describe('SearchService', () => {
1896
+ let searchService: SearchService;
1897
+ let lanceStore: LanceStore;
1898
+ let embeddingEngine: EmbeddingEngine;
1899
+ let tempDir: string;
1900
+ const storeId = createStoreId('test-store');
1901
+
1902
+ beforeAll(async () => {
1903
+ tempDir = await mkdtemp(join(tmpdir(), 'search-test-'));
1904
+ lanceStore = new LanceStore(tempDir);
1905
+ embeddingEngine = new EmbeddingEngine();
1906
+
1907
+ await embeddingEngine.initialize();
1908
+ await lanceStore.initialize(storeId);
1909
+
1910
+ // Add test documents
1911
+ const texts = [
1912
+ 'TypeScript is a typed superset of JavaScript',
1913
+ 'Python is great for machine learning',
1914
+ 'React is a JavaScript library for building user interfaces',
1915
+ ];
1916
+
1917
+ for (let i = 0; i < texts.length; i++) {
1918
+ const text = texts[i]!;
1919
+ const vector = await embeddingEngine.embed(text);
1920
+ await lanceStore.addDocuments(storeId, [
1921
+ {
1922
+ id: createDocumentId(`doc-${i}`),
1923
+ content: text,
1924
+ vector,
1925
+ metadata: {
1926
+ type: 'file',
1927
+ storeId,
1928
+ indexedAt: new Date(),
1929
+ },
1930
+ },
1931
+ ]);
1932
+ }
1933
+
1934
+ searchService = new SearchService(lanceStore, embeddingEngine);
1935
+ }, 120000);
1936
+
1937
+ afterAll(async () => {
1938
+ await rm(tempDir, { recursive: true, force: true });
1939
+ });
1940
+
1941
+ it('searches with vector mode', async () => {
1942
+ const results = await searchService.search({
1943
+ query: 'JavaScript programming',
1944
+ stores: [storeId],
1945
+ mode: 'vector',
1946
+ limit: 10,
1947
+ });
1948
+
1949
+ expect(results.results.length).toBeGreaterThan(0);
1950
+ expect(results.mode).toBe('vector');
1951
+ });
1952
+
1953
+ it('returns results with scores', async () => {
1954
+ const results = await searchService.search({
1955
+ query: 'machine learning Python',
1956
+ stores: [storeId],
1957
+ mode: 'vector',
1958
+ limit: 10,
1959
+ });
1960
+
1961
+ expect(results.results[0]?.score).toBeGreaterThan(0);
1962
+ expect(results.results[0]?.content).toContain('Python');
1963
+ });
1964
+ });
1965
+ ```
1966
+
1967
+ **Step 2: Run test to verify it fails**
1968
+
1969
+ ```bash
1970
+ npm run test:run -- src/services/search.service.test.ts
1971
+ ```
1972
+
1973
+ Expected: FAIL - module not found
1974
+
1975
+ **Step 3: Write minimal implementation**
1976
+
1977
+ Create `src/services/search.service.ts`:
1978
+ ```typescript
1979
+ import type { LanceStore } from '../db/lance.js';
1980
+ import type { EmbeddingEngine } from '../db/embeddings.js';
1981
+ import type { SearchQuery, SearchResponse, SearchResult } from '../types/search.js';
1982
+ import type { StoreId } from '../types/brands.js';
1983
+
1984
+ export class SearchService {
1985
+ private readonly lanceStore: LanceStore;
1986
+ private readonly embeddingEngine: EmbeddingEngine;
1987
+
1988
+ constructor(lanceStore: LanceStore, embeddingEngine: EmbeddingEngine) {
1989
+ this.lanceStore = lanceStore;
1990
+ this.embeddingEngine = embeddingEngine;
1991
+ }
1992
+
1993
+ async search(query: SearchQuery): Promise<SearchResponse> {
1994
+ const startTime = Date.now();
1995
+ const mode = query.mode ?? 'hybrid';
1996
+ const limit = query.limit ?? 10;
1997
+ const stores = query.stores ?? [];
1998
+
1999
+ let allResults: SearchResult[] = [];
2000
+
2001
+ if (mode === 'vector' || mode === 'hybrid') {
2002
+ const queryVector = await this.embeddingEngine.embed(query.query);
2003
+
2004
+ for (const storeId of stores) {
2005
+ const results = await this.lanceStore.search(
2006
+ storeId,
2007
+ queryVector,
2008
+ limit,
2009
+ query.threshold
2010
+ );
2011
+
2012
+ allResults.push(
2013
+ ...results.map((r) => ({
2014
+ id: r.id,
2015
+ score: r.score,
2016
+ content: r.content,
2017
+ metadata: r.metadata,
2018
+ }))
2019
+ );
2020
+ }
2021
+ }
2022
+
2023
+ // Sort by score descending
2024
+ allResults.sort((a, b) => b.score - a.score);
2025
+
2026
+ // Limit results
2027
+ allResults = allResults.slice(0, limit);
2028
+
2029
+ return {
2030
+ query: query.query,
2031
+ mode,
2032
+ stores,
2033
+ results: allResults,
2034
+ totalResults: allResults.length,
2035
+ timeMs: Date.now() - startTime,
2036
+ };
2037
+ }
2038
+
2039
+ async searchAllStores(
2040
+ query: SearchQuery,
2041
+ storeIds: StoreId[]
2042
+ ): Promise<SearchResponse> {
2043
+ return this.search({
2044
+ ...query,
2045
+ stores: storeIds,
2046
+ });
2047
+ }
2048
+ }
2049
+ ```
2050
+
2051
+ **Step 4: Run test to verify it passes**
2052
+
2053
+ ```bash
2054
+ npm run test:run -- src/services/search.service.test.ts
2055
+ ```
2056
+
2057
+ Expected: PASS
2058
+
2059
+ **Step 5: Commit**
2060
+
2061
+ ```bash
2062
+ git add src/services/search.service.ts src/services/search.service.test.ts
2063
+ git commit -m "feat: add SearchService for vector search"
2064
+ ```
2065
+
2066
+ ---
2067
+
2068
+ ### Task 4.4: Create IndexService (simplified)
2069
+
2070
+ **Files:**
2071
+ - Create: `src/services/index.service.ts`
2072
+ - Create: `src/services/index.service.test.ts`
2073
+
2074
+ **Step 1: Write the failing test**
2075
+
2076
+ Create `src/services/index.service.test.ts`:
2077
+ ```typescript
2078
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2079
+ import { IndexService } from './index.service.js';
2080
+ import { LanceStore } from '../db/lance.js';
2081
+ import { EmbeddingEngine } from '../db/embeddings.js';
2082
+ import { createStoreId } from '../types/brands.js';
2083
+ import { rm, mkdtemp, writeFile, mkdir } from 'node:fs/promises';
2084
+ import { tmpdir } from 'node:os';
2085
+ import { join } from 'node:path';
2086
+ import type { FileStore } from '../types/store.js';
2087
+
2088
+ describe('IndexService', () => {
2089
+ let indexService: IndexService;
2090
+ let lanceStore: LanceStore;
2091
+ let embeddingEngine: EmbeddingEngine;
2092
+ let tempDir: string;
2093
+ let testFilesDir: string;
2094
+ const storeId = createStoreId('test-store');
2095
+
2096
+ beforeAll(async () => {
2097
+ tempDir = await mkdtemp(join(tmpdir(), 'index-test-'));
2098
+ testFilesDir = join(tempDir, 'files');
2099
+ await mkdir(testFilesDir, { recursive: true });
2100
+
2101
+ // Create test files
2102
+ await writeFile(join(testFilesDir, 'test1.txt'), 'Hello world, this is a test file.');
2103
+ await writeFile(join(testFilesDir, 'test2.md'), '# Heading\n\nSome markdown content here.');
2104
+
2105
+ lanceStore = new LanceStore(tempDir);
2106
+ embeddingEngine = new EmbeddingEngine();
2107
+
2108
+ await embeddingEngine.initialize();
2109
+ await lanceStore.initialize(storeId);
2110
+
2111
+ indexService = new IndexService(lanceStore, embeddingEngine);
2112
+ }, 120000);
2113
+
2114
+ afterAll(async () => {
2115
+ await rm(tempDir, { recursive: true, force: true });
2116
+ });
2117
+
2118
+ it('indexes a file store', async () => {
2119
+ const store: FileStore = {
2120
+ type: 'file',
2121
+ id: storeId,
2122
+ name: 'Test Store',
2123
+ path: testFilesDir,
2124
+ createdAt: new Date(),
2125
+ updatedAt: new Date(),
2126
+ };
2127
+
2128
+ const result = await indexService.indexStore(store);
2129
+
2130
+ expect(result.success).toBe(true);
2131
+ if (result.success) {
2132
+ expect(result.data.documentsIndexed).toBeGreaterThan(0);
2133
+ }
2134
+ });
2135
+ });
2136
+ ```
2137
+
2138
+ **Step 2: Run test to verify it fails**
2139
+
2140
+ ```bash
2141
+ npm run test:run -- src/services/index.service.test.ts
2142
+ ```
2143
+
2144
+ Expected: FAIL - module not found
2145
+
2146
+ **Step 3: Write minimal implementation**
2147
+
2148
+ Create `src/services/index.service.ts`:
2149
+ ```typescript
2150
+ import { readFile, readdir, stat } from 'node:fs/promises';
2151
+ import { join, extname } from 'node:path';
2152
+ import { createHash } from 'node:crypto';
2153
+ import type { LanceStore } from '../db/lance.js';
2154
+ import type { EmbeddingEngine } from '../db/embeddings.js';
2155
+ import type { Store, FileStore, RepoStore } from '../types/store.js';
2156
+ import type { Document } from '../types/document.js';
2157
+ import { createDocumentId } from '../types/brands.js';
2158
+ import type { Result } from '../types/result.js';
2159
+ import { ok, err } from '../types/result.js';
2160
+
2161
+ interface IndexResult {
2162
+ documentsIndexed: number;
2163
+ chunksCreated: number;
2164
+ timeMs: number;
2165
+ }
2166
+
2167
+ const TEXT_EXTENSIONS = new Set([
2168
+ '.txt', '.md', '.js', '.ts', '.jsx', '.tsx', '.json', '.yaml', '.yml',
2169
+ '.html', '.css', '.scss', '.less', '.py', '.rb', '.go', '.rs', '.java',
2170
+ '.c', '.cpp', '.h', '.hpp', '.sh', '.bash', '.zsh', '.sql', '.xml',
2171
+ ]);
2172
+
2173
+ export class IndexService {
2174
+ private readonly lanceStore: LanceStore;
2175
+ private readonly embeddingEngine: EmbeddingEngine;
2176
+
2177
+ constructor(lanceStore: LanceStore, embeddingEngine: EmbeddingEngine) {
2178
+ this.lanceStore = lanceStore;
2179
+ this.embeddingEngine = embeddingEngine;
2180
+ }
2181
+
2182
+ async indexStore(store: Store): Promise<Result<IndexResult>> {
2183
+ const startTime = Date.now();
2184
+
2185
+ try {
2186
+ if (store.type === 'file' || store.type === 'repo') {
2187
+ return await this.indexFileStore(store);
2188
+ }
2189
+
2190
+ return err(new Error(`Indexing not supported for store type: ${store.type}`));
2191
+ } catch (error) {
2192
+ return err(error instanceof Error ? error : new Error(String(error)));
2193
+ }
2194
+ }
2195
+
2196
+ private async indexFileStore(store: FileStore | RepoStore): Promise<Result<IndexResult>> {
2197
+ const startTime = Date.now();
2198
+ const files = await this.scanDirectory(store.path);
2199
+ const documents: Document[] = [];
2200
+
2201
+ for (const filePath of files) {
2202
+ const content = await readFile(filePath, 'utf-8');
2203
+ const vector = await this.embeddingEngine.embed(content);
2204
+ const fileHash = createHash('md5').update(content).digest('hex');
2205
+
2206
+ const doc: Document = {
2207
+ id: createDocumentId(`${store.id}-${fileHash}`),
2208
+ content,
2209
+ vector,
2210
+ metadata: {
2211
+ type: 'file',
2212
+ storeId: store.id,
2213
+ path: filePath,
2214
+ indexedAt: new Date(),
2215
+ fileHash,
2216
+ },
2217
+ };
2218
+
2219
+ documents.push(doc);
2220
+ }
2221
+
2222
+ if (documents.length > 0) {
2223
+ await this.lanceStore.addDocuments(store.id, documents);
2224
+ }
2225
+
2226
+ return ok({
2227
+ documentsIndexed: documents.length,
2228
+ chunksCreated: documents.length, // Simplified - no chunking yet
2229
+ timeMs: Date.now() - startTime,
2230
+ });
2231
+ }
2232
+
2233
+ private async scanDirectory(dir: string): Promise<string[]> {
2234
+ const files: string[] = [];
2235
+ const entries = await readdir(dir, { withFileTypes: true });
2236
+
2237
+ for (const entry of entries) {
2238
+ const fullPath = join(dir, entry.name);
2239
+
2240
+ if (entry.isDirectory()) {
2241
+ // Skip common ignored directories
2242
+ if (!['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
2243
+ files.push(...(await this.scanDirectory(fullPath)));
2244
+ }
2245
+ } else if (entry.isFile()) {
2246
+ const ext = extname(entry.name).toLowerCase();
2247
+ if (TEXT_EXTENSIONS.has(ext)) {
2248
+ files.push(fullPath);
2249
+ }
2250
+ }
2251
+ }
2252
+
2253
+ return files;
2254
+ }
2255
+ }
2256
+ ```
2257
+
2258
+ **Step 4: Run test to verify it passes**
2259
+
2260
+ ```bash
2261
+ npm run test:run -- src/services/index.service.test.ts
2262
+ ```
2263
+
2264
+ Expected: PASS
2265
+
2266
+ **Step 5: Commit**
2267
+
2268
+ ```bash
2269
+ git add src/services/index.service.ts src/services/index.service.test.ts
2270
+ git commit -m "feat: add IndexService for file indexing"
2271
+ ```
2272
+
2273
+ ---
2274
+
2275
+ ### Task 4.5: Create ServiceContainer
2276
+
2277
+ **Files:**
2278
+ - Modify: `src/services/index.ts`
2279
+
2280
+ **Step 1: Create ServiceContainer**
2281
+
2282
+ Update `src/services/index.ts`:
2283
+ ```typescript
2284
+ import { ConfigService } from './config.service.js';
2285
+ import { StoreService } from './store.service.js';
2286
+ import { SearchService } from './search.service.js';
2287
+ import { IndexService } from './index.service.js';
2288
+ import { LanceStore } from '../db/lance.js';
2289
+ import { EmbeddingEngine } from '../db/embeddings.js';
2290
+
2291
+ export { ConfigService } from './config.service.js';
2292
+ export { StoreService } from './store.service.js';
2293
+ export { SearchService } from './search.service.js';
2294
+ export { IndexService } from './index.service.js';
2295
+
2296
+ export interface ServiceContainer {
2297
+ config: ConfigService;
2298
+ store: StoreService;
2299
+ search: SearchService;
2300
+ index: IndexService;
2301
+ lance: LanceStore;
2302
+ embeddings: EmbeddingEngine;
2303
+ }
2304
+
2305
+ export async function createServices(
2306
+ configPath?: string,
2307
+ dataDir?: string
2308
+ ): Promise<ServiceContainer> {
2309
+ const config = new ConfigService(configPath, dataDir);
2310
+ const appConfig = await config.load();
2311
+ const resolvedDataDir = config.resolveDataDir();
2312
+
2313
+ const lance = new LanceStore(resolvedDataDir);
2314
+ const embeddings = new EmbeddingEngine(
2315
+ appConfig.embedding.model,
2316
+ appConfig.embedding.dimensions
2317
+ );
2318
+
2319
+ await embeddings.initialize();
2320
+
2321
+ const store = new StoreService(resolvedDataDir);
2322
+ await store.initialize();
2323
+
2324
+ const search = new SearchService(lance, embeddings);
2325
+ const index = new IndexService(lance, embeddings);
2326
+
2327
+ return {
2328
+ config,
2329
+ store,
2330
+ search,
2331
+ index,
2332
+ lance,
2333
+ embeddings,
2334
+ };
2335
+ }
2336
+ ```
2337
+
2338
+ **Step 2: Commit**
2339
+
2340
+ ```bash
2341
+ git add src/services/index.ts
2342
+ git commit -m "feat: add ServiceContainer for dependency injection"
2343
+ ```
2344
+
2345
+ ---
2346
+
2347
+ ## Phase 5: CLI Layer
2348
+
2349
+ ### Task 5.1: Create CLI framework
2350
+
2351
+ **Files:**
2352
+ - Create: `src/cli/program.ts`
2353
+ - Modify: `src/index.ts`
2354
+
2355
+ **Step 1: Create CLI program**
2356
+
2357
+ Create `src/cli/program.ts`:
2358
+ ```typescript
2359
+ import { Command } from 'commander';
2360
+
2361
+ export function createProgram(): Command {
2362
+ const program = new Command();
2363
+
2364
+ program
2365
+ .name('bluera-knowledge')
2366
+ .description('CLI tool for managing knowledge stores with semantic search')
2367
+ .version('0.1.0');
2368
+
2369
+ program
2370
+ .option('-c, --config <path>', 'Path to config file')
2371
+ .option('-d, --data-dir <path>', 'Data directory')
2372
+ .option('-f, --format <format>', 'Output format: json | table | plain', 'table')
2373
+ .option('-q, --quiet', 'Suppress non-essential output')
2374
+ .option('-v, --verbose', 'Enable verbose logging');
2375
+
2376
+ return program;
2377
+ }
2378
+
2379
+ export interface GlobalOptions {
2380
+ config?: string;
2381
+ dataDir?: string;
2382
+ format: 'json' | 'table' | 'plain';
2383
+ quiet?: boolean;
2384
+ verbose?: boolean;
2385
+ }
2386
+
2387
+ export function getGlobalOptions(program: Command): GlobalOptions {
2388
+ const opts = program.opts<GlobalOptions>();
2389
+ return {
2390
+ config: opts.config,
2391
+ dataDir: opts.dataDir,
2392
+ format: opts.format ?? 'table',
2393
+ quiet: opts.quiet,
2394
+ verbose: opts.verbose,
2395
+ };
2396
+ }
2397
+ ```
2398
+
2399
+ **Step 2: Update entry point**
2400
+
2401
+ Update `src/index.ts`:
2402
+ ```typescript
2403
+ #!/usr/bin/env node
2404
+
2405
+ import { createProgram } from './cli/program.js';
2406
+
2407
+ const program = createProgram();
2408
+
2409
+ program.parse();
2410
+ ```
2411
+
2412
+ **Step 3: Verify CLI works**
2413
+
2414
+ ```bash
2415
+ npm run build && node dist/index.js --help
2416
+ ```
2417
+
2418
+ Expected: Help output with description and options
2419
+
2420
+ **Step 4: Commit**
2421
+
2422
+ ```bash
2423
+ git add src/cli/program.ts src/index.ts
2424
+ git commit -m "feat: add CLI framework with Commander.js"
2425
+ ```
2426
+
2427
+ ---
2428
+
2429
+ ### Task 5.2: Add store commands
2430
+
2431
+ **Files:**
2432
+ - Create: `src/cli/commands/store.ts`
2433
+ - Modify: `src/index.ts`
2434
+
2435
+ **Step 1: Create store commands**
2436
+
2437
+ Create `src/cli/commands/store.ts`:
2438
+ ```typescript
2439
+ import { Command } from 'commander';
2440
+ import { createServices } from '../../services/index.js';
2441
+ import type { GlobalOptions } from '../program.js';
2442
+ import type { StoreType } from '../../types/store.js';
2443
+
2444
+ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
2445
+ const store = new Command('store').description('Manage knowledge stores');
2446
+
2447
+ store
2448
+ .command('list')
2449
+ .description('List all stores')
2450
+ .option('-t, --type <type>', 'Filter by store type (file, repo, web)')
2451
+ .action(async (options: { type?: StoreType }) => {
2452
+ const globalOpts = getOptions();
2453
+ const services = await createServices(globalOpts.config, globalOpts.dataDir);
2454
+ const stores = await services.store.list(options.type);
2455
+
2456
+ if (globalOpts.format === 'json') {
2457
+ console.log(JSON.stringify(stores, null, 2));
2458
+ } else {
2459
+ if (stores.length === 0) {
2460
+ console.log('No stores found.');
2461
+ } else {
2462
+ console.log('\nStores:\n');
2463
+ for (const s of stores) {
2464
+ console.log(` ${s.name} (${s.type}) - ${s.id}`);
2465
+ }
2466
+ console.log('');
2467
+ }
2468
+ }
2469
+ });
2470
+
2471
+ store
2472
+ .command('create <name>')
2473
+ .description('Create a new store')
2474
+ .requiredOption('-t, --type <type>', 'Store type (file, repo, web)')
2475
+ .requiredOption('-s, --source <path>', 'Source path or URL')
2476
+ .option('-d, --description <desc>', 'Store description')
2477
+ .option('--tags <tags>', 'Comma-separated tags')
2478
+ .action(async (name: string, options: {
2479
+ type: StoreType;
2480
+ source: string;
2481
+ description?: string;
2482
+ tags?: string;
2483
+ }) => {
2484
+ const globalOpts = getOptions();
2485
+ const services = await createServices(globalOpts.config, globalOpts.dataDir);
2486
+
2487
+ const result = await services.store.create({
2488
+ name,
2489
+ type: options.type,
2490
+ path: options.type !== 'web' ? options.source : undefined,
2491
+ url: options.type === 'web' ? options.source : undefined,
2492
+ description: options.description,
2493
+ tags: options.tags?.split(',').map((t) => t.trim()),
2494
+ });
2495
+
2496
+ if (result.success) {
2497
+ if (globalOpts.format === 'json') {
2498
+ console.log(JSON.stringify(result.data, null, 2));
2499
+ } else {
2500
+ console.log(`\nCreated store: ${result.data.name} (${result.data.id})\n`);
2501
+ }
2502
+ } else {
2503
+ console.error(`Error: ${result.error.message}`);
2504
+ process.exit(1);
2505
+ }
2506
+ });
2507
+
2508
+ store
2509
+ .command('info <store>')
2510
+ .description('Show store details')
2511
+ .action(async (storeIdOrName: string) => {
2512
+ const globalOpts = getOptions();
2513
+ const services = await createServices(globalOpts.config, globalOpts.dataDir);
2514
+ const s = await services.store.getByIdOrName(storeIdOrName);
2515
+
2516
+ if (s === undefined) {
2517
+ console.error(`Store not found: ${storeIdOrName}`);
2518
+ process.exit(3);
2519
+ }
2520
+
2521
+ if (globalOpts.format === 'json') {
2522
+ console.log(JSON.stringify(s, null, 2));
2523
+ } else {
2524
+ console.log(`\nStore: ${s.name}`);
2525
+ console.log(` ID: ${s.id}`);
2526
+ console.log(` Type: ${s.type}`);
2527
+ if ('path' in s) console.log(` Path: ${s.path}`);
2528
+ if ('url' in s) console.log(` URL: ${s.url}`);
2529
+ if (s.description !== undefined) console.log(` Description: ${s.description}`);
2530
+ console.log(` Created: ${s.createdAt.toISOString()}`);
2531
+ console.log(` Updated: ${s.updatedAt.toISOString()}`);
2532
+ console.log('');
2533
+ }
2534
+ });
2535
+
2536
+ store
2537
+ .command('delete <store>')
2538
+ .description('Delete a store')
2539
+ .option('-f, --force', 'Skip confirmation')
2540
+ .action(async (storeIdOrName: string, options: { force?: boolean }) => {
2541
+ const globalOpts = getOptions();
2542
+ const services = await createServices(globalOpts.config, globalOpts.dataDir);
2543
+ const s = await services.store.getByIdOrName(storeIdOrName);
2544
+
2545
+ if (s === undefined) {
2546
+ console.error(`Store not found: ${storeIdOrName}`);
2547
+ process.exit(3);
2548
+ }
2549
+
2550
+ const result = await services.store.delete(s.id);
2551
+
2552
+ if (result.success) {
2553
+ console.log(`Deleted store: ${s.name}`);
2554
+ } else {
2555
+ console.error(`Error: ${result.error.message}`);
2556
+ process.exit(1);
2557
+ }
2558
+ });
2559
+
2560
+ return store;
2561
+ }
2562
+ ```
2563
+
2564
+ **Step 2: Register store command**
2565
+
2566
+ Update `src/index.ts`:
2567
+ ```typescript
2568
+ #!/usr/bin/env node
2569
+
2570
+ import { createProgram, getGlobalOptions } from './cli/program.js';
2571
+ import { createStoreCommand } from './cli/commands/store.js';
2572
+
2573
+ const program = createProgram();
2574
+
2575
+ program.addCommand(createStoreCommand(() => getGlobalOptions(program)));
2576
+
2577
+ program.parse();
2578
+ ```
2579
+
2580
+ **Step 3: Verify store commands work**
2581
+
2582
+ ```bash
2583
+ npm run build && node dist/index.js store --help
2584
+ ```
2585
+
2586
+ Expected: Help output for store commands
2587
+
2588
+ **Step 4: Commit**
2589
+
2590
+ ```bash
2591
+ git add src/cli/commands/store.ts src/index.ts
2592
+ git commit -m "feat: add store CLI commands"
2593
+ ```
2594
+
2595
+ ---
2596
+
2597
+ ### Task 5.3: Add search command
2598
+
2599
+ **Files:**
2600
+ - Create: `src/cli/commands/search.ts`
2601
+ - Modify: `src/index.ts`
2602
+
2603
+ **Step 1: Create search command**
2604
+
2605
+ Create `src/cli/commands/search.ts`:
2606
+ ```typescript
2607
+ import { Command } from 'commander';
2608
+ import { createServices } from '../../services/index.js';
2609
+ import type { GlobalOptions } from '../program.js';
2610
+ import type { SearchMode } from '../../types/search.js';
2611
+ import { createStoreId } from '../../types/brands.js';
2612
+
2613
+ export function createSearchCommand(getOptions: () => GlobalOptions): Command {
2614
+ const search = new Command('search')
2615
+ .description('Search across knowledge stores')
2616
+ .argument('<query>', 'Search query')
2617
+ .option('-s, --stores <stores>', 'Comma-separated store IDs/names')
2618
+ .option('-m, --mode <mode>', 'Search mode: vector | fts | hybrid', 'hybrid')
2619
+ .option('-n, --limit <number>', 'Max results', '10')
2620
+ .option('-t, --threshold <number>', 'Minimum relevance score 0-1')
2621
+ .option('--include-content', 'Include full content in results')
2622
+ .action(async (query: string, options: {
2623
+ stores?: string;
2624
+ mode?: SearchMode;
2625
+ limit?: string;
2626
+ threshold?: string;
2627
+ includeContent?: boolean;
2628
+ }) => {
2629
+ const globalOpts = getOptions();
2630
+ const services = await createServices(globalOpts.config, globalOpts.dataDir);
2631
+
2632
+ // Get store IDs
2633
+ let storeIds = (await services.store.list()).map((s) => s.id);
2634
+
2635
+ if (options.stores !== undefined) {
2636
+ const requestedStores = options.stores.split(',').map((s) => s.trim());
2637
+ const resolvedStores = [];
2638
+
2639
+ for (const requested of requestedStores) {
2640
+ const store = await services.store.getByIdOrName(requested);
2641
+ if (store !== undefined) {
2642
+ resolvedStores.push(store.id);
2643
+ } else {
2644
+ console.error(`Store not found: ${requested}`);
2645
+ process.exit(3);
2646
+ }
2647
+ }
2648
+
2649
+ storeIds = resolvedStores;
2650
+ }
2651
+
2652
+ if (storeIds.length === 0) {
2653
+ console.error('No stores to search. Create a store first.');
2654
+ process.exit(1);
2655
+ }
2656
+
2657
+ // Initialize LanceDB for each store
2658
+ for (const storeId of storeIds) {
2659
+ await services.lance.initialize(storeId);
2660
+ }
2661
+
2662
+ const results = await services.search.search({
2663
+ query,
2664
+ stores: storeIds,
2665
+ mode: options.mode ?? 'hybrid',
2666
+ limit: parseInt(options.limit ?? '10', 10),
2667
+ threshold: options.threshold !== undefined ? parseFloat(options.threshold) : undefined,
2668
+ includeContent: options.includeContent,
2669
+ });
2670
+
2671
+ if (globalOpts.format === 'json') {
2672
+ console.log(JSON.stringify(results, null, 2));
2673
+ } else {
2674
+ console.log(`\nSearch: "${query}"`);
2675
+ console.log(`Mode: ${results.mode} | Stores: ${results.stores.length} | Results: ${results.totalResults} | Time: ${results.timeMs}ms\n`);
2676
+
2677
+ if (results.results.length === 0) {
2678
+ console.log('No results found.\n');
2679
+ } else {
2680
+ for (let i = 0; i < results.results.length; i++) {
2681
+ const r = results.results[i]!;
2682
+ const path = r.metadata.path ?? r.metadata.url ?? 'unknown';
2683
+ console.log(`${i + 1}. [${r.score.toFixed(2)}] ${path}`);
2684
+ const preview = r.content.slice(0, 150).replace(/\n/g, ' ');
2685
+ console.log(` ${preview}${r.content.length > 150 ? '...' : ''}\n`);
2686
+ }
2687
+ }
2688
+ }
2689
+ });
2690
+
2691
+ return search;
2692
+ }
2693
+ ```
2694
+
2695
+ **Step 2: Register search command**
2696
+
2697
+ Update `src/index.ts`:
2698
+ ```typescript
2699
+ #!/usr/bin/env node
2700
+
2701
+ import { createProgram, getGlobalOptions } from './cli/program.js';
2702
+ import { createStoreCommand } from './cli/commands/store.js';
2703
+ import { createSearchCommand } from './cli/commands/search.js';
2704
+
2705
+ const program = createProgram();
2706
+
2707
+ program.addCommand(createStoreCommand(() => getGlobalOptions(program)));
2708
+ program.addCommand(createSearchCommand(() => getGlobalOptions(program)));
2709
+
2710
+ program.parse();
2711
+ ```
2712
+
2713
+ **Step 3: Commit**
2714
+
2715
+ ```bash
2716
+ git add src/cli/commands/search.ts src/index.ts
2717
+ git commit -m "feat: add search CLI command"
2718
+ ```
2719
+
2720
+ ---
2721
+
2722
+ ### Task 5.4: Add index command
2723
+
2724
+ **Files:**
2725
+ - Create: `src/cli/commands/index-cmd.ts`
2726
+ - Modify: `src/index.ts`
2727
+
2728
+ **Step 1: Create index command**
2729
+
2730
+ Create `src/cli/commands/index-cmd.ts`:
2731
+ ```typescript
2732
+ import { Command } from 'commander';
2733
+ import { createServices } from '../../services/index.js';
2734
+ import type { GlobalOptions } from '../program.js';
2735
+
2736
+ export function createIndexCommand(getOptions: () => GlobalOptions): Command {
2737
+ const index = new Command('index')
2738
+ .description('Index a knowledge store')
2739
+ .argument('<store>', 'Store ID or name')
2740
+ .option('--force', 'Force reindex all files')
2741
+ .action(async (storeIdOrName: string, options: { force?: boolean }) => {
2742
+ const globalOpts = getOptions();
2743
+ const services = await createServices(globalOpts.config, globalOpts.dataDir);
2744
+
2745
+ const store = await services.store.getByIdOrName(storeIdOrName);
2746
+
2747
+ if (store === undefined) {
2748
+ console.error(`Store not found: ${storeIdOrName}`);
2749
+ process.exit(3);
2750
+ }
2751
+
2752
+ console.log(`\nIndexing store: ${store.name}...\n`);
2753
+
2754
+ await services.lance.initialize(store.id);
2755
+
2756
+ const result = await services.index.indexStore(store);
2757
+
2758
+ if (result.success) {
2759
+ if (globalOpts.format === 'json') {
2760
+ console.log(JSON.stringify(result.data, null, 2));
2761
+ } else {
2762
+ console.log(`Indexed ${result.data.documentsIndexed} documents`);
2763
+ console.log(`Created ${result.data.chunksCreated} chunks`);
2764
+ console.log(`Time: ${result.data.timeMs}ms\n`);
2765
+ }
2766
+ } else {
2767
+ console.error(`Error: ${result.error.message}`);
2768
+ process.exit(4);
2769
+ }
2770
+ });
2771
+
2772
+ return index;
2773
+ }
2774
+ ```
2775
+
2776
+ **Step 2: Register index command**
2777
+
2778
+ Update `src/index.ts`:
2779
+ ```typescript
2780
+ #!/usr/bin/env node
2781
+
2782
+ import { createProgram, getGlobalOptions } from './cli/program.js';
2783
+ import { createStoreCommand } from './cli/commands/store.js';
2784
+ import { createSearchCommand } from './cli/commands/search.js';
2785
+ import { createIndexCommand } from './cli/commands/index-cmd.js';
2786
+
2787
+ const program = createProgram();
2788
+
2789
+ program.addCommand(createStoreCommand(() => getGlobalOptions(program)));
2790
+ program.addCommand(createSearchCommand(() => getGlobalOptions(program)));
2791
+ program.addCommand(createIndexCommand(() => getGlobalOptions(program)));
2792
+
2793
+ program.parse();
2794
+ ```
2795
+
2796
+ **Step 3: Commit**
2797
+
2798
+ ```bash
2799
+ git add src/cli/commands/index-cmd.ts src/index.ts
2800
+ git commit -m "feat: add index CLI command"
2801
+ ```
2802
+
2803
+ ---
2804
+
2805
+ ### Task 5.5: Update CLI barrel export
2806
+
2807
+ **Files:**
2808
+ - Modify: `src/cli/index.ts`
2809
+
2810
+ **Step 1: Update CLI index**
2811
+
2812
+ Update `src/cli/index.ts`:
2813
+ ```typescript
2814
+ export { createProgram, getGlobalOptions, type GlobalOptions } from './program.js';
2815
+ export { createStoreCommand } from './commands/store.js';
2816
+ export { createSearchCommand } from './commands/search.js';
2817
+ export { createIndexCommand } from './commands/index-cmd.js';
2818
+ ```
2819
+
2820
+ **Step 2: Run full build and test**
2821
+
2822
+ ```bash
2823
+ npm run build && npm run test:run
2824
+ ```
2825
+
2826
+ Expected: All tests pass, build succeeds
2827
+
2828
+ **Step 3: Commit**
2829
+
2830
+ ```bash
2831
+ git add src/cli/index.ts
2832
+ git commit -m "feat: add CLI barrel export"
2833
+ ```
2834
+
2835
+ ---
2836
+
2837
+ ## Phase 6: Integration Testing
2838
+
2839
+ ### Task 6.1: Create end-to-end test
2840
+
2841
+ **Files:**
2842
+ - Create: `tests/integration/cli.test.ts`
2843
+
2844
+ **Step 1: Write integration test**
2845
+
2846
+ Create `tests/integration/cli.test.ts`:
2847
+ ```typescript
2848
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2849
+ import { execSync } from 'node:child_process';
2850
+ import { rm, mkdtemp, writeFile, mkdir } from 'node:fs/promises';
2851
+ import { tmpdir } from 'node:os';
2852
+ import { join } from 'node:path';
2853
+
2854
+ describe('CLI Integration', () => {
2855
+ let tempDir: string;
2856
+ let testFilesDir: string;
2857
+
2858
+ beforeAll(async () => {
2859
+ tempDir = await mkdtemp(join(tmpdir(), 'cli-test-'));
2860
+ testFilesDir = join(tempDir, 'files');
2861
+ await mkdir(testFilesDir, { recursive: true });
2862
+ await writeFile(
2863
+ join(testFilesDir, 'test.md'),
2864
+ '# Test Document\n\nThis is content about TypeScript and JavaScript programming.'
2865
+ );
2866
+ }, 30000);
2867
+
2868
+ afterAll(async () => {
2869
+ await rm(tempDir, { recursive: true, force: true });
2870
+ });
2871
+
2872
+ const cli = (args: string): string => {
2873
+ return execSync(`node dist/index.js ${args} --data-dir "${tempDir}"`, {
2874
+ encoding: 'utf-8',
2875
+ timeout: 120000,
2876
+ });
2877
+ };
2878
+
2879
+ it('shows help', () => {
2880
+ const output = cli('--help');
2881
+ expect(output).toContain('bkb');
2882
+ expect(output).toContain('CLI tool for managing knowledge stores');
2883
+ });
2884
+
2885
+ it('creates and lists stores', () => {
2886
+ cli(`store create test-store --type file --source "${testFilesDir}"`);
2887
+ const listOutput = cli('store list');
2888
+ expect(listOutput).toContain('test-store');
2889
+ });
2890
+
2891
+ it('shows store info', () => {
2892
+ const output = cli('store info test-store');
2893
+ expect(output).toContain('test-store');
2894
+ expect(output).toContain('file');
2895
+ });
2896
+
2897
+ it('indexes a store', () => {
2898
+ const output = cli('index test-store');
2899
+ expect(output).toContain('Indexed');
2900
+ expect(output).toContain('documents');
2901
+ }, 120000);
2902
+
2903
+ it('searches indexed content', () => {
2904
+ const output = cli('search "TypeScript programming"');
2905
+ expect(output).toContain('Search:');
2906
+ expect(output).toContain('Results:');
2907
+ }, 60000);
2908
+ });
2909
+ ```
2910
+
2911
+ **Step 2: Run integration tests**
2912
+
2913
+ ```bash
2914
+ npm run build && npm run test:run -- tests/integration/
2915
+ ```
2916
+
2917
+ Expected: All integration tests pass
2918
+
2919
+ **Step 3: Commit**
2920
+
2921
+ ```bash
2922
+ git add tests/integration/cli.test.ts
2923
+ git commit -m "test: add CLI integration tests"
2924
+ ```
2925
+
2926
+ ---
2927
+
2928
+ ## Summary
2929
+
2930
+ This plan covers the core implementation of the Bluera Knowledge CLI:
2931
+
2932
+ **Phase 1:** Project scaffolding (8 tasks)
2933
+ **Phase 2:** Types layer (7 tasks)
2934
+ **Phase 3:** Data layer (3 tasks)
2935
+ **Phase 4:** Services layer (5 tasks)
2936
+ **Phase 5:** CLI layer (5 tasks)
2937
+ **Phase 6:** Integration testing (1 task)
2938
+
2939
+ **Total: 29 bite-sized tasks**
2940
+
2941
+ **Not included in this initial plan (future phases):**
2942
+ - HTTP server mode (Hono)
2943
+ - Python bridge for web crawling
2944
+ - Document chunking
2945
+ - Full-text search
2946
+ - Hybrid search with RRF
2947
+ - Progress bars and interactive mode
2948
+ - File watching
2949
+ - Export/import functionality
2950
+
2951
+ These can be added in subsequent plans once the core is working.