mdcontext 0.0.1 → 0.2.0

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 (337) hide show
  1. package/.changeset/README.md +28 -0
  2. package/.changeset/config.json +11 -0
  3. package/.claude/settings.local.json +25 -0
  4. package/.github/workflows/ci.yml +83 -0
  5. package/.github/workflows/claude-code-review.yml +44 -0
  6. package/.github/workflows/claude.yml +85 -0
  7. package/.github/workflows/release.yml +113 -0
  8. package/.tldrignore +112 -0
  9. package/BACKLOG.md +338 -0
  10. package/CONTRIBUTING.md +186 -0
  11. package/NOTES/NOTES +44 -0
  12. package/README.md +434 -11
  13. package/biome.json +36 -0
  14. package/cspell.config.yaml +14 -0
  15. package/dist/chunk-23UPXDNL.js +3044 -0
  16. package/dist/chunk-2W7MO2DL.js +1366 -0
  17. package/dist/chunk-3NUAZGMA.js +1689 -0
  18. package/dist/chunk-7TOWB2XB.js +366 -0
  19. package/dist/chunk-7XOTOADQ.js +3065 -0
  20. package/dist/chunk-AH2PDM2K.js +3042 -0
  21. package/dist/chunk-BNXWSZ63.js +3742 -0
  22. package/dist/chunk-BTL5DJVU.js +3222 -0
  23. package/dist/chunk-HDHYG7E4.js +104 -0
  24. package/dist/chunk-HLR4KZBP.js +3234 -0
  25. package/dist/chunk-IP3FRFEB.js +1045 -0
  26. package/dist/chunk-KHU56VDO.js +3042 -0
  27. package/dist/chunk-KRYIFLQR.js +88 -0
  28. package/dist/chunk-LBSDNLEM.js +287 -0
  29. package/dist/chunk-MNTQ7HCP.js +2643 -0
  30. package/dist/chunk-MUJELQQ6.js +1387 -0
  31. package/dist/chunk-MXJGMSLV.js +2199 -0
  32. package/dist/chunk-N6QJGC3Z.js +2636 -0
  33. package/dist/chunk-OBELGBPM.js +1713 -0
  34. package/dist/chunk-OT7R5XTA.js +3192 -0
  35. package/dist/chunk-P7X4RA2T.js +106 -0
  36. package/dist/chunk-PIDUQNC2.js +3185 -0
  37. package/dist/chunk-POGCDIH4.js +3187 -0
  38. package/dist/chunk-PSIEOQGZ.js +3043 -0
  39. package/dist/chunk-PVRT3IHA.js +3238 -0
  40. package/dist/chunk-QNN4TT23.js +1430 -0
  41. package/dist/chunk-RE3R45RJ.js +3042 -0
  42. package/dist/chunk-S7E6TFX6.js +803 -0
  43. package/dist/chunk-SG6GLU4U.js +1378 -0
  44. package/dist/chunk-SJCDV2ST.js +274 -0
  45. package/dist/chunk-SYE5XLF3.js +104 -0
  46. package/dist/chunk-T5VLYBZD.js +103 -0
  47. package/dist/chunk-TOQB7VWU.js +3238 -0
  48. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  49. package/dist/chunk-VVTGZNBT.js +1629 -0
  50. package/dist/chunk-W7Q4RFEV.js +104 -0
  51. package/dist/chunk-XTYYVRLO.js +3190 -0
  52. package/dist/chunk-Y6MDYVJD.js +3063 -0
  53. package/dist/cli/main.d.ts +1 -0
  54. package/dist/cli/main.js +5458 -0
  55. package/dist/index.d.ts +653 -0
  56. package/dist/index.js +79 -0
  57. package/dist/mcp/server.d.ts +1 -0
  58. package/dist/mcp/server.js +472 -0
  59. package/dist/schema-BAWSG7KY.js +22 -0
  60. package/dist/schema-E3QUPL26.js +20 -0
  61. package/dist/schema-EHL7WUT6.js +20 -0
  62. package/docs/019-USAGE.md +625 -0
  63. package/docs/020-current-implementation.md +364 -0
  64. package/docs/021-DOGFOODING-FINDINGS.md +175 -0
  65. package/docs/BACKLOG.md +80 -0
  66. package/docs/CONFIG.md +1123 -0
  67. package/docs/DESIGN.md +439 -0
  68. package/docs/ERRORS.md +383 -0
  69. package/docs/PROJECT.md +88 -0
  70. package/docs/ROADMAP.md +407 -0
  71. package/docs/summarization.md +320 -0
  72. package/docs/test-links.md +9 -0
  73. package/justfile +40 -0
  74. package/package.json +74 -9
  75. package/pnpm-workspace.yaml +5 -0
  76. package/research/INDEX.md +315 -0
  77. package/research/code-review/README.md +90 -0
  78. package/research/code-review/cli-error-handling-review.md +979 -0
  79. package/research/code-review/code-review-validation-report.md +464 -0
  80. package/research/code-review/main-ts-review.md +1128 -0
  81. package/research/config-analysis/01-current-implementation.md +470 -0
  82. package/research/config-analysis/02-strategy-recommendation.md +428 -0
  83. package/research/config-analysis/03-task-candidates.md +715 -0
  84. package/research/config-analysis/033-research-configuration-management.md +828 -0
  85. package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
  86. package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
  87. package/research/config-docs/SUMMARY.md +357 -0
  88. package/research/config-docs/TEST-RESULTS.md +776 -0
  89. package/research/config-docs/TODO.md +542 -0
  90. package/research/config-docs/analysis.md +744 -0
  91. package/research/config-docs/fix-validation.md +502 -0
  92. package/research/config-docs/help-audit.md +264 -0
  93. package/research/config-docs/help-system-analysis.md +890 -0
  94. package/research/dogfood/consolidated-tool-evaluation.md +373 -0
  95. package/research/dogfood/strategy-a/a-synthesis.md +184 -0
  96. package/research/dogfood/strategy-a/a1-docs.md +226 -0
  97. package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
  98. package/research/dogfood/strategy-a/a3-llm.md +164 -0
  99. package/research/dogfood/strategy-b/b-synthesis.md +228 -0
  100. package/research/dogfood/strategy-b/b1-architecture.md +207 -0
  101. package/research/dogfood/strategy-b/b2-gaps.md +258 -0
  102. package/research/dogfood/strategy-b/b3-workflows.md +250 -0
  103. package/research/dogfood/strategy-c/c-synthesis.md +451 -0
  104. package/research/dogfood/strategy-c/c1-explorer.md +192 -0
  105. package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
  106. package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
  107. package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
  108. package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
  109. package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
  110. package/research/effect-cli-error-handling.md +845 -0
  111. package/research/effect-errors-as-values.md +943 -0
  112. package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
  113. package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
  114. package/research/errors-task-analysis/embeddings-analysis.md +709 -0
  115. package/research/errors-task-analysis/index-search-analysis.md +812 -0
  116. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  117. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  118. package/research/issue-review.md +603 -0
  119. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  120. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  121. package/research/llm-summarization/anthropic-2026.md +367 -0
  122. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  123. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  124. package/research/llm-summarization/openai-2026.md +473 -0
  125. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  126. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  127. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  128. package/research/llm-summarization/prototype-results.md +56 -0
  129. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  130. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  131. package/research/mdcontext-error-analysis.md +521 -0
  132. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  133. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  134. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  135. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  136. package/research/mdcontext-pudding/02-search.md +970 -0
  137. package/research/mdcontext-pudding/03-context.md +779 -0
  138. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  139. package/research/mdcontext-pudding/04-tree.md +704 -0
  140. package/research/mdcontext-pudding/05-config.md +1038 -0
  141. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  142. package/research/mdcontext-pudding/06-links.md +679 -0
  143. package/research/mdcontext-pudding/07-stats.md +693 -0
  144. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  145. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  146. package/research/mdcontext-pudding/README.md +168 -0
  147. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  148. package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
  149. package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
  150. package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
  151. package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
  152. package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
  153. package/research/research-quality-review.md +834 -0
  154. package/research/semantic-search/002-research-embedding-models.md +490 -0
  155. package/research/semantic-search/003-research-rag-alternatives.md +523 -0
  156. package/research/semantic-search/004-research-vector-search.md +841 -0
  157. package/research/semantic-search/032-research-semantic-search.md +427 -0
  158. package/research/semantic-search/embedding-text-analysis.md +156 -0
  159. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  160. package/research/semantic-search/query-processing-analysis.md +207 -0
  161. package/research/semantic-search/root-cause-and-solution.md +114 -0
  162. package/research/semantic-search/threshold-validation-report.md +69 -0
  163. package/research/semantic-search/vector-search-analysis.md +63 -0
  164. package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
  165. package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
  166. package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
  167. package/research/task-management-2026/03-lightweight-file-based.md +567 -0
  168. package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
  169. package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
  170. package/research/task-management-2026/linear/02-api-integrations.md +930 -0
  171. package/research/task-management-2026/linear/03-ai-features.md +368 -0
  172. package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
  173. package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
  174. package/research/test-path-issues.md +276 -0
  175. package/review/ALP-76/1-error-type-design.md +962 -0
  176. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  177. package/review/ALP-76/3-error-presentation.md +624 -0
  178. package/review/ALP-76/4-test-coverage.md +625 -0
  179. package/review/ALP-76/5-migration-completeness.md +440 -0
  180. package/review/ALP-76/6-effect-best-practices.md +755 -0
  181. package/scripts/apply-branch-protection.sh +47 -0
  182. package/scripts/branch-protection-templates.json +79 -0
  183. package/scripts/prototype-summarization.ts +346 -0
  184. package/scripts/rebuild-hnswlib.js +58 -0
  185. package/scripts/setup-branch-protection.sh +64 -0
  186. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  187. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  188. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  189. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  190. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  191. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  192. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  193. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  194. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  195. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  196. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  197. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  198. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  199. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  200. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  201. package/src/cli/argv-preprocessor.test.ts +210 -0
  202. package/src/cli/argv-preprocessor.ts +202 -0
  203. package/src/cli/cli.test.ts +627 -0
  204. package/src/cli/commands/backlinks.ts +54 -0
  205. package/src/cli/commands/config-cmd.ts +642 -0
  206. package/src/cli/commands/context.ts +285 -0
  207. package/src/cli/commands/duplicates.ts +122 -0
  208. package/src/cli/commands/embeddings.ts +529 -0
  209. package/src/cli/commands/index-cmd.ts +480 -0
  210. package/src/cli/commands/index.ts +16 -0
  211. package/src/cli/commands/links.ts +52 -0
  212. package/src/cli/commands/search.ts +1281 -0
  213. package/src/cli/commands/stats.ts +149 -0
  214. package/src/cli/commands/tree.ts +128 -0
  215. package/src/cli/config-layer.ts +176 -0
  216. package/src/cli/error-handler.test.ts +235 -0
  217. package/src/cli/error-handler.ts +655 -0
  218. package/src/cli/flag-schemas.ts +341 -0
  219. package/src/cli/help.ts +588 -0
  220. package/src/cli/index.ts +9 -0
  221. package/src/cli/main.ts +435 -0
  222. package/src/cli/options.ts +41 -0
  223. package/src/cli/shared-error-handling.ts +199 -0
  224. package/src/cli/typo-suggester.test.ts +105 -0
  225. package/src/cli/typo-suggester.ts +130 -0
  226. package/src/cli/utils.ts +259 -0
  227. package/src/config/file-provider.test.ts +320 -0
  228. package/src/config/file-provider.ts +273 -0
  229. package/src/config/index.ts +72 -0
  230. package/src/config/integration.test.ts +667 -0
  231. package/src/config/precedence.test.ts +277 -0
  232. package/src/config/precedence.ts +451 -0
  233. package/src/config/schema.test.ts +414 -0
  234. package/src/config/schema.ts +603 -0
  235. package/src/config/service.test.ts +320 -0
  236. package/src/config/service.ts +243 -0
  237. package/src/config/testing.test.ts +264 -0
  238. package/src/config/testing.ts +110 -0
  239. package/src/core/index.ts +1 -0
  240. package/src/core/types.ts +113 -0
  241. package/src/duplicates/detector.test.ts +183 -0
  242. package/src/duplicates/detector.ts +414 -0
  243. package/src/duplicates/index.ts +18 -0
  244. package/src/embeddings/embedding-namespace.test.ts +300 -0
  245. package/src/embeddings/embedding-namespace.ts +947 -0
  246. package/src/embeddings/heading-boost.test.ts +222 -0
  247. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  248. package/src/embeddings/hyde.test.ts +272 -0
  249. package/src/embeddings/hyde.ts +264 -0
  250. package/src/embeddings/index.ts +10 -0
  251. package/src/embeddings/openai-provider.ts +414 -0
  252. package/src/embeddings/pricing.json +22 -0
  253. package/src/embeddings/provider-constants.ts +204 -0
  254. package/src/embeddings/provider-errors.test.ts +967 -0
  255. package/src/embeddings/provider-errors.ts +565 -0
  256. package/src/embeddings/provider-factory.test.ts +240 -0
  257. package/src/embeddings/provider-factory.ts +225 -0
  258. package/src/embeddings/provider-integration.test.ts +788 -0
  259. package/src/embeddings/query-preprocessing.test.ts +187 -0
  260. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  261. package/src/embeddings/semantic-search.ts +1270 -0
  262. package/src/embeddings/types.ts +359 -0
  263. package/src/embeddings/vector-store.ts +708 -0
  264. package/src/embeddings/voyage-provider.ts +313 -0
  265. package/src/errors/errors.test.ts +845 -0
  266. package/src/errors/index.ts +533 -0
  267. package/src/index/ignore-patterns.test.ts +354 -0
  268. package/src/index/ignore-patterns.ts +305 -0
  269. package/src/index/index.ts +4 -0
  270. package/src/index/indexer.ts +684 -0
  271. package/src/index/storage.ts +260 -0
  272. package/src/index/types.ts +147 -0
  273. package/src/index/watcher.ts +189 -0
  274. package/src/index.ts +30 -0
  275. package/src/integration/search-keyword.test.ts +678 -0
  276. package/src/mcp/server.ts +612 -0
  277. package/src/parser/index.ts +1 -0
  278. package/src/parser/parser.test.ts +291 -0
  279. package/src/parser/parser.ts +394 -0
  280. package/src/parser/section-filter.test.ts +277 -0
  281. package/src/parser/section-filter.ts +392 -0
  282. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  283. package/src/search/bm25-store.ts +366 -0
  284. package/src/search/cross-encoder.test.ts +253 -0
  285. package/src/search/cross-encoder.ts +406 -0
  286. package/src/search/fuzzy-search.test.ts +419 -0
  287. package/src/search/fuzzy-search.ts +273 -0
  288. package/src/search/hybrid-search.ts +448 -0
  289. package/src/search/path-matcher.test.ts +276 -0
  290. package/src/search/path-matcher.ts +33 -0
  291. package/src/search/query-parser.test.ts +260 -0
  292. package/src/search/query-parser.ts +319 -0
  293. package/src/search/searcher.test.ts +280 -0
  294. package/src/search/searcher.ts +724 -0
  295. package/src/search/wink-bm25.d.ts +30 -0
  296. package/src/summarization/cli-providers/claude.ts +202 -0
  297. package/src/summarization/cli-providers/detection.test.ts +273 -0
  298. package/src/summarization/cli-providers/detection.ts +118 -0
  299. package/src/summarization/cli-providers/index.ts +8 -0
  300. package/src/summarization/cost.test.ts +139 -0
  301. package/src/summarization/cost.ts +102 -0
  302. package/src/summarization/error-handler.test.ts +127 -0
  303. package/src/summarization/error-handler.ts +111 -0
  304. package/src/summarization/index.ts +102 -0
  305. package/src/summarization/pipeline.test.ts +498 -0
  306. package/src/summarization/pipeline.ts +231 -0
  307. package/src/summarization/prompts.test.ts +269 -0
  308. package/src/summarization/prompts.ts +133 -0
  309. package/src/summarization/provider-factory.test.ts +396 -0
  310. package/src/summarization/provider-factory.ts +178 -0
  311. package/src/summarization/types.ts +184 -0
  312. package/src/summarize/budget-bugs.test.ts +620 -0
  313. package/src/summarize/formatters.ts +419 -0
  314. package/src/summarize/index.ts +20 -0
  315. package/src/summarize/summarizer.test.ts +275 -0
  316. package/src/summarize/summarizer.ts +597 -0
  317. package/src/summarize/verify-bugs.test.ts +238 -0
  318. package/src/types/huggingface-transformers.d.ts +66 -0
  319. package/src/utils/index.ts +1 -0
  320. package/src/utils/tokens.test.ts +142 -0
  321. package/src/utils/tokens.ts +186 -0
  322. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  323. package/tests/fixtures/cli/.mdcontext/config.json +8 -0
  324. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  325. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  326. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
  327. package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
  328. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
  329. package/tests/fixtures/cli/README.md +9 -0
  330. package/tests/fixtures/cli/api-reference.md +11 -0
  331. package/tests/fixtures/cli/getting-started.md +11 -0
  332. package/tests/integration/embed-index.test.ts +712 -0
  333. package/tests/integration/search-context.test.ts +469 -0
  334. package/tests/integration/search-semantic.test.ts +522 -0
  335. package/tsconfig.json +26 -0
  336. package/vitest.config.ts +16 -0
  337. package/vitest.setup.ts +12 -0
@@ -0,0 +1,678 @@
1
+ /**
2
+ * Integration tests for keyword search functionality
3
+ *
4
+ * Tests the complete keyword search pipeline including:
5
+ * - Basic keyword matching
6
+ * - Boolean operators (AND, OR, NOT)
7
+ * - Phrase search
8
+ * - Case sensitivity
9
+ * - Context lines
10
+ * - Result format verification
11
+ */
12
+
13
+ import * as fs from 'node:fs/promises'
14
+ import * as path from 'node:path'
15
+ import { Effect } from 'effect'
16
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
17
+ import { buildIndex } from '../index/indexer.js'
18
+ import { searchContent } from '../search/searcher.js'
19
+
20
+ const TEST_DIR = path.join(process.cwd(), 'tests', 'fixtures', 'keyword-search')
21
+
22
+ const runEffect = <A, E>(effect: Effect.Effect<A, E>) =>
23
+ Effect.runPromise(effect)
24
+
25
+ describe('Keyword Search Integration', () => {
26
+ beforeAll(async () => {
27
+ await fs.mkdir(TEST_DIR, { recursive: true })
28
+
29
+ await fs.writeFile(
30
+ path.join(TEST_DIR, 'authentication.md'),
31
+ `# Authentication System
32
+
33
+ ## Overview
34
+
35
+ The authentication system handles user login and session management.
36
+ It supports multiple authentication providers including OAuth and SAML.
37
+
38
+ ## Security Features
39
+
40
+ - Password hashing with bcrypt
41
+ - Token-based authentication
42
+ - Multi-factor authentication support
43
+ - Session timeout and renewal
44
+
45
+ ## Implementation Details
46
+
47
+ The auth module provides a secure way to authenticate users.
48
+ Authentication tokens expire after 24 hours for security.
49
+ Failed authentication attempts are logged and monitored.
50
+
51
+ ## Error Handling
52
+
53
+ When authentication fails, the system returns appropriate error codes.
54
+ Common authentication errors include invalid credentials and expired tokens.
55
+ `,
56
+ )
57
+
58
+ await fs.writeFile(
59
+ path.join(TEST_DIR, 'database.md'),
60
+ `# Database Layer
61
+
62
+ ## Connection Management
63
+
64
+ The database connection pool manages active connections efficiently.
65
+ Connection pooling improves performance and reduces overhead.
66
+
67
+ ## Query Builder
68
+
69
+ Our query builder provides a fluent API for database operations.
70
+ Complex queries can be constructed programmatically without raw SQL.
71
+
72
+ ## Transactions
73
+
74
+ Database transactions ensure data consistency across operations.
75
+ Transactions are automatically rolled back on error.
76
+
77
+ ## Performance
78
+
79
+ Indexing strategies significantly improve query performance.
80
+ Query optimization is handled automatically by the database engine.
81
+ `,
82
+ )
83
+
84
+ await fs.writeFile(
85
+ path.join(TEST_DIR, 'api.md'),
86
+ `# API Documentation
87
+
88
+ ## REST Endpoints
89
+
90
+ The REST API provides access to all system functionality.
91
+ Endpoints follow RESTful conventions for consistency.
92
+
93
+ ## GraphQL Support
94
+
95
+ GraphQL endpoints offer flexible query capabilities.
96
+ Clients can request exactly the data they need.
97
+
98
+ ## Rate Limiting
99
+
100
+ API rate limiting prevents abuse and ensures fair usage.
101
+ Rate limits are enforced per API key and endpoint.
102
+
103
+ ## Versioning
104
+
105
+ API versioning ensures backward compatibility.
106
+ Deprecated endpoints remain available for one major version.
107
+ `,
108
+ )
109
+
110
+ await runEffect(buildIndex(TEST_DIR, { force: true }))
111
+ })
112
+
113
+ afterAll(async () => {
114
+ await fs.rm(TEST_DIR, { recursive: true, force: true })
115
+ })
116
+
117
+ describe('Basic Keyword Search', () => {
118
+ it('should find sections containing a single keyword', async () => {
119
+ const results = await runEffect(
120
+ searchContent(TEST_DIR, {
121
+ content: 'authentication',
122
+ }),
123
+ )
124
+
125
+ expect(results.length).toBeGreaterThan(0)
126
+
127
+ const authDoc = results.filter((r) =>
128
+ r.section.documentPath.includes('authentication'),
129
+ )
130
+ expect(authDoc.length).toBeGreaterThan(0)
131
+ })
132
+
133
+ it('should return results with match details', async () => {
134
+ const results = await runEffect(
135
+ searchContent(TEST_DIR, {
136
+ content: 'authentication',
137
+ pathPattern: 'authentication*',
138
+ }),
139
+ )
140
+
141
+ expect(results.length).toBeGreaterThan(0)
142
+
143
+ const firstResult = results[0]
144
+ expect(firstResult).toBeDefined()
145
+ expect(firstResult?.section).toBeDefined()
146
+ expect(firstResult?.document).toBeDefined()
147
+ expect(firstResult?.matches).toBeDefined()
148
+ expect(firstResult?.matches?.length).toBeGreaterThan(0)
149
+ })
150
+
151
+ it('should respect case-insensitive matching by default', async () => {
152
+ const lowerResults = await runEffect(
153
+ searchContent(TEST_DIR, {
154
+ content: 'authentication',
155
+ pathPattern: 'authentication*',
156
+ }),
157
+ )
158
+
159
+ const upperResults = await runEffect(
160
+ searchContent(TEST_DIR, {
161
+ content: 'AUTHENTICATION',
162
+ pathPattern: 'authentication*',
163
+ }),
164
+ )
165
+
166
+ expect(lowerResults.length).toBe(upperResults.length)
167
+ expect(lowerResults.length).toBeGreaterThan(0)
168
+ })
169
+
170
+ it('should find multiple occurrences across sections', async () => {
171
+ const results = await runEffect(
172
+ searchContent(TEST_DIR, {
173
+ content: 'database',
174
+ pathPattern: 'database*',
175
+ }),
176
+ )
177
+
178
+ expect(results.length).toBeGreaterThan(0)
179
+
180
+ const totalMatches = results.reduce(
181
+ (sum, r) => sum + (r.matches?.length ?? 0),
182
+ 0,
183
+ )
184
+ expect(totalMatches).toBeGreaterThan(1)
185
+ })
186
+ })
187
+
188
+ describe('Boolean Operators', () => {
189
+ it('should support AND operator', async () => {
190
+ const results = await runEffect(
191
+ searchContent(TEST_DIR, {
192
+ content: 'authentication AND token',
193
+ }),
194
+ )
195
+
196
+ expect(results.length).toBeGreaterThan(0)
197
+
198
+ for (const result of results) {
199
+ const content = result.sectionContent?.toLowerCase() ?? ''
200
+ expect(content).toContain('authentication')
201
+ expect(content).toContain('token')
202
+ }
203
+ })
204
+
205
+ it('should support OR operator', async () => {
206
+ const results = await runEffect(
207
+ searchContent(TEST_DIR, {
208
+ content: 'OAuth OR SAML',
209
+ }),
210
+ )
211
+
212
+ expect(results.length).toBeGreaterThan(0)
213
+
214
+ const hasOAuth = results.some(
215
+ (r) =>
216
+ r.sectionContent?.toLowerCase().includes('oauth') ||
217
+ r.matches?.some((m) => m.line.toLowerCase().includes('oauth')),
218
+ )
219
+ const hasSAML = results.some(
220
+ (r) =>
221
+ r.sectionContent?.toLowerCase().includes('saml') ||
222
+ r.matches?.some((m) => m.line.toLowerCase().includes('saml')),
223
+ )
224
+
225
+ expect(hasOAuth || hasSAML).toBe(true)
226
+ })
227
+
228
+ it('should support NOT operator', async () => {
229
+ const allAuthResults = await runEffect(
230
+ searchContent(TEST_DIR, {
231
+ content: 'authentication',
232
+ }),
233
+ )
234
+
235
+ const noTokenResults = await runEffect(
236
+ searchContent(TEST_DIR, {
237
+ content: 'authentication NOT token',
238
+ }),
239
+ )
240
+
241
+ expect(noTokenResults.length).toBeLessThan(allAuthResults.length)
242
+
243
+ for (const result of noTokenResults) {
244
+ const content = result.sectionContent?.toLowerCase() ?? ''
245
+ expect(content).toContain('authentication')
246
+ expect(content).not.toContain('token')
247
+ }
248
+ })
249
+
250
+ it('should support complex boolean expressions', async () => {
251
+ const results = await runEffect(
252
+ searchContent(TEST_DIR, {
253
+ content:
254
+ '(authentication AND security) OR (database AND performance)',
255
+ }),
256
+ )
257
+
258
+ expect(results.length).toBeGreaterThan(0)
259
+
260
+ for (const result of results) {
261
+ const content = result.sectionContent?.toLowerCase() ?? ''
262
+ const hasAuthSecurity =
263
+ content.includes('authentication') && content.includes('security')
264
+ const hasDbPerformance =
265
+ content.includes('database') && content.includes('performance')
266
+
267
+ expect(hasAuthSecurity || hasDbPerformance).toBe(true)
268
+ }
269
+ })
270
+
271
+ it('should handle implicit AND between terms', async () => {
272
+ const explicitResults = await runEffect(
273
+ searchContent(TEST_DIR, {
274
+ content: 'query AND builder',
275
+ }),
276
+ )
277
+
278
+ const implicitResults = await runEffect(
279
+ searchContent(TEST_DIR, {
280
+ content: 'query builder',
281
+ }),
282
+ )
283
+
284
+ expect(implicitResults.length).toBeGreaterThan(0)
285
+ expect(implicitResults.length).toBe(explicitResults.length)
286
+ })
287
+ })
288
+
289
+ describe('Phrase Search', () => {
290
+ it('should find exact phrases with quotes', async () => {
291
+ const results = await runEffect(
292
+ searchContent(TEST_DIR, {
293
+ content: '"authentication system"',
294
+ }),
295
+ )
296
+
297
+ expect(results.length).toBeGreaterThan(0)
298
+
299
+ const hasExactPhrase = results.some((r) =>
300
+ r.matches?.some((m) =>
301
+ m.line.toLowerCase().includes('authentication system'),
302
+ ),
303
+ )
304
+ expect(hasExactPhrase).toBe(true)
305
+ })
306
+
307
+ it('should not match partial phrases', async () => {
308
+ const phraseResults = await runEffect(
309
+ searchContent(TEST_DIR, {
310
+ content: '"password hashing"',
311
+ }),
312
+ )
313
+
314
+ expect(phraseResults.length).toBeGreaterThan(0)
315
+
316
+ const wrongOrderResults = await runEffect(
317
+ searchContent(TEST_DIR, {
318
+ content: '"hashing password"',
319
+ }),
320
+ )
321
+
322
+ expect(wrongOrderResults.length).toBe(0)
323
+ })
324
+
325
+ it('should combine phrases with boolean operators', async () => {
326
+ const results = await runEffect(
327
+ searchContent(TEST_DIR, {
328
+ content: '"authentication system" OR "database connection"',
329
+ }),
330
+ )
331
+
332
+ expect(results.length).toBeGreaterThan(0)
333
+
334
+ const hasAuthPhrase = results.some((r) =>
335
+ r.matches?.some((m) =>
336
+ m.line.toLowerCase().includes('authentication system'),
337
+ ),
338
+ )
339
+ const hasDbPhrase = results.some((r) =>
340
+ r.matches?.some((m) =>
341
+ m.line.toLowerCase().includes('database connection'),
342
+ ),
343
+ )
344
+
345
+ expect(hasAuthPhrase || hasDbPhrase).toBe(true)
346
+ })
347
+
348
+ it('should handle phrases with special characters', async () => {
349
+ const results = await runEffect(
350
+ searchContent(TEST_DIR, {
351
+ content: '"24 hours"',
352
+ }),
353
+ )
354
+
355
+ expect(results.length).toBeGreaterThan(0)
356
+ })
357
+ })
358
+
359
+ describe('Context Lines', () => {
360
+ it('should include context lines before matches', async () => {
361
+ const results = await runEffect(
362
+ searchContent(TEST_DIR, {
363
+ content: 'bcrypt',
364
+ contextBefore: 2,
365
+ }),
366
+ )
367
+
368
+ expect(results.length).toBeGreaterThan(0)
369
+
370
+ const firstResult = results[0]
371
+ const firstMatch = firstResult?.matches?.[0]
372
+ expect(firstMatch?.contextLines).toBeDefined()
373
+
374
+ if (firstMatch?.contextLines) {
375
+ const beforeLines = firstMatch.contextLines.filter(
376
+ (cl) => !cl.isMatch && cl.lineNumber < firstMatch.lineNumber,
377
+ )
378
+ expect(beforeLines.length).toBeGreaterThanOrEqual(1)
379
+ }
380
+ })
381
+
382
+ it('should include context lines after matches', async () => {
383
+ const results = await runEffect(
384
+ searchContent(TEST_DIR, {
385
+ content: 'bcrypt',
386
+ contextAfter: 2,
387
+ }),
388
+ )
389
+
390
+ expect(results.length).toBeGreaterThan(0)
391
+
392
+ const firstResult = results[0]
393
+ const firstMatch = firstResult?.matches?.[0]
394
+ expect(firstMatch?.contextLines).toBeDefined()
395
+
396
+ if (firstMatch?.contextLines) {
397
+ const afterLines = firstMatch.contextLines.filter(
398
+ (cl) => !cl.isMatch && cl.lineNumber > firstMatch.lineNumber,
399
+ )
400
+ expect(afterLines.length).toBeGreaterThanOrEqual(1)
401
+ }
402
+ })
403
+
404
+ it('should include context lines both before and after', async () => {
405
+ const results = await runEffect(
406
+ searchContent(TEST_DIR, {
407
+ content: 'authentication',
408
+ contextBefore: 1,
409
+ contextAfter: 1,
410
+ pathPattern: 'authentication*',
411
+ }),
412
+ )
413
+
414
+ expect(results.length).toBeGreaterThan(0)
415
+
416
+ const firstResult = results[0]
417
+ const firstMatch = firstResult?.matches?.[0]
418
+
419
+ if (firstMatch?.contextLines) {
420
+ const matchLine = firstMatch.contextLines.find((cl) => cl.isMatch)
421
+ expect(matchLine).toBeDefined()
422
+ expect(matchLine?.lineNumber).toBe(firstMatch.lineNumber)
423
+ expect(firstMatch.contextLines.length).toBeGreaterThanOrEqual(1)
424
+ }
425
+ })
426
+
427
+ it('should generate snippet text from context', async () => {
428
+ const results = await runEffect(
429
+ searchContent(TEST_DIR, {
430
+ content: 'authentication',
431
+ contextBefore: 1,
432
+ contextAfter: 1,
433
+ pathPattern: 'authentication*',
434
+ }),
435
+ )
436
+
437
+ expect(results.length).toBeGreaterThan(0)
438
+
439
+ const firstResult = results[0]
440
+ const firstMatch = firstResult?.matches?.[0]
441
+ expect(firstMatch?.snippet).toBeDefined()
442
+ expect(firstMatch?.snippet.length).toBeGreaterThan(0)
443
+ expect(firstMatch?.snippet.toLowerCase()).toContain('authentication')
444
+ })
445
+ })
446
+
447
+ describe('Result Format', () => {
448
+ it('should include section metadata', async () => {
449
+ const results = await runEffect(
450
+ searchContent(TEST_DIR, {
451
+ content: 'authentication',
452
+ pathPattern: 'authentication*',
453
+ }),
454
+ )
455
+
456
+ expect(results.length).toBeGreaterThan(0)
457
+
458
+ const result = results[0]!
459
+ expect(result.section.heading).toBeDefined()
460
+ expect(result.section.level).toBeGreaterThan(0)
461
+ expect(result.section.documentPath).toBeDefined()
462
+ expect(typeof result.section.tokenCount).toBe('number')
463
+ })
464
+
465
+ it('should include document metadata', async () => {
466
+ const results = await runEffect(
467
+ searchContent(TEST_DIR, {
468
+ content: 'authentication',
469
+ pathPattern: 'authentication*',
470
+ }),
471
+ )
472
+
473
+ expect(results.length).toBeGreaterThan(0)
474
+
475
+ const result = results[0]!
476
+ expect(result.document.title).toBeDefined()
477
+ expect(result.document.path).toBeDefined()
478
+ expect(typeof result.document.tokenCount).toBe('number')
479
+ })
480
+
481
+ it('should include match line numbers', async () => {
482
+ const results = await runEffect(
483
+ searchContent(TEST_DIR, {
484
+ content: 'authentication',
485
+ pathPattern: 'authentication*',
486
+ }),
487
+ )
488
+
489
+ expect(results.length).toBeGreaterThan(0)
490
+
491
+ const result = results[0]!
492
+ expect(result.matches).toBeDefined()
493
+ expect(result.matches!.length).toBeGreaterThan(0)
494
+
495
+ for (const match of result.matches!) {
496
+ expect(match.lineNumber).toBeGreaterThan(0)
497
+ expect(typeof match.lineNumber).toBe('number')
498
+ }
499
+ })
500
+
501
+ it('should include matching line text', async () => {
502
+ const results = await runEffect(
503
+ searchContent(TEST_DIR, {
504
+ content: 'authentication',
505
+ pathPattern: 'authentication*',
506
+ }),
507
+ )
508
+
509
+ expect(results.length).toBeGreaterThan(0)
510
+
511
+ const result = results[0]!
512
+ const match = result.matches![0]!
513
+
514
+ expect(match.line).toBeDefined()
515
+ expect(match.line.length).toBeGreaterThan(0)
516
+ expect(match.line.toLowerCase()).toContain('authentication')
517
+ })
518
+
519
+ it('should populate section content when matches exist', async () => {
520
+ const results = await runEffect(
521
+ searchContent(TEST_DIR, {
522
+ content: 'authentication',
523
+ pathPattern: 'authentication*',
524
+ }),
525
+ )
526
+
527
+ expect(results.length).toBeGreaterThan(0)
528
+
529
+ for (const result of results) {
530
+ if (result.matches && result.matches.length > 0) {
531
+ expect(result.sectionContent).toBeDefined()
532
+ expect(result.sectionContent!.length).toBeGreaterThan(0)
533
+ }
534
+ }
535
+ })
536
+ })
537
+
538
+ describe('Edge Cases', () => {
539
+ it('should handle search with no matches', async () => {
540
+ const results = await runEffect(
541
+ searchContent(TEST_DIR, {
542
+ content: 'nonexistent_keyword_12345',
543
+ }),
544
+ )
545
+
546
+ expect(results.length).toBe(0)
547
+ })
548
+
549
+ it('should handle empty search query gracefully', async () => {
550
+ const results = await runEffect(
551
+ searchContent(TEST_DIR, {
552
+ content: '',
553
+ }),
554
+ )
555
+
556
+ expect(Array.isArray(results)).toBe(true)
557
+ })
558
+
559
+ it('should handle special regex characters', async () => {
560
+ await fs.writeFile(
561
+ path.join(TEST_DIR, 'special-chars.md'),
562
+ `# Special Characters
563
+
564
+ Test with special chars: foo.bar, test[value], foo(bar)
565
+ `,
566
+ )
567
+
568
+ await runEffect(buildIndex(TEST_DIR, { force: true }))
569
+
570
+ const results = await runEffect(
571
+ searchContent(TEST_DIR, {
572
+ content: 'foo.bar',
573
+ }),
574
+ )
575
+
576
+ expect(results.length).toBeGreaterThan(0)
577
+ })
578
+
579
+ it('should handle very long keywords', async () => {
580
+ const longKeyword = 'a'.repeat(100)
581
+
582
+ const results = await runEffect(
583
+ searchContent(TEST_DIR, {
584
+ content: longKeyword,
585
+ }),
586
+ )
587
+
588
+ expect(Array.isArray(results)).toBe(true)
589
+ })
590
+
591
+ it('should handle multiple consecutive spaces in query', async () => {
592
+ const results = await runEffect(
593
+ searchContent(TEST_DIR, {
594
+ content: 'authentication system',
595
+ }),
596
+ )
597
+
598
+ expect(Array.isArray(results)).toBe(true)
599
+ })
600
+ })
601
+
602
+ describe('Path Filtering', () => {
603
+ it('should filter results by path pattern', async () => {
604
+ const results = await runEffect(
605
+ searchContent(TEST_DIR, {
606
+ content: 'authentication',
607
+ pathPattern: 'authentication*',
608
+ }),
609
+ )
610
+
611
+ expect(results.length).toBeGreaterThan(0)
612
+
613
+ for (const result of results) {
614
+ expect(result.section.documentPath).toMatch(/authentication/)
615
+ }
616
+ })
617
+
618
+ it('should return no results when path pattern does not match', async () => {
619
+ const results = await runEffect(
620
+ searchContent(TEST_DIR, {
621
+ content: 'authentication',
622
+ pathPattern: 'nonexistent*',
623
+ }),
624
+ )
625
+
626
+ expect(results.length).toBe(0)
627
+ })
628
+
629
+ it('should combine path filtering with content search', async () => {
630
+ const dbResults = await runEffect(
631
+ searchContent(TEST_DIR, {
632
+ content: 'query',
633
+ pathPattern: 'database*',
634
+ }),
635
+ )
636
+
637
+ expect(dbResults.length).toBeGreaterThan(0)
638
+
639
+ for (const result of dbResults) {
640
+ expect(result.section.documentPath).toMatch(/database/)
641
+ expect(result.sectionContent?.toLowerCase()).toContain('query')
642
+ }
643
+ })
644
+ })
645
+
646
+ describe('Limit Results', () => {
647
+ it('should respect limit option', async () => {
648
+ const limit = 2
649
+ const results = await runEffect(
650
+ searchContent(TEST_DIR, {
651
+ content: 'the',
652
+ limit,
653
+ }),
654
+ )
655
+
656
+ expect(results.length).toBeLessThanOrEqual(limit)
657
+ })
658
+
659
+ it('should return all results when limit is not specified', async () => {
660
+ const limitedResults = await runEffect(
661
+ searchContent(TEST_DIR, {
662
+ content: 'API',
663
+ limit: 1,
664
+ }),
665
+ )
666
+
667
+ const unlimitedResults = await runEffect(
668
+ searchContent(TEST_DIR, {
669
+ content: 'API',
670
+ }),
671
+ )
672
+
673
+ expect(unlimitedResults.length).toBeGreaterThanOrEqual(
674
+ limitedResults.length,
675
+ )
676
+ })
677
+ })
678
+ })