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.
- package/.changeset/README.md +28 -0
- package/.changeset/config.json +11 -0
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/.github/workflows/release.yml +113 -0
- package/.tldrignore +112 -0
- package/BACKLOG.md +338 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +434 -11
- package/biome.json +36 -0
- package/cspell.config.yaml +14 -0
- package/dist/chunk-23UPXDNL.js +3044 -0
- package/dist/chunk-2W7MO2DL.js +1366 -0
- package/dist/chunk-3NUAZGMA.js +1689 -0
- package/dist/chunk-7TOWB2XB.js +366 -0
- package/dist/chunk-7XOTOADQ.js +3065 -0
- package/dist/chunk-AH2PDM2K.js +3042 -0
- package/dist/chunk-BNXWSZ63.js +3742 -0
- package/dist/chunk-BTL5DJVU.js +3222 -0
- package/dist/chunk-HDHYG7E4.js +104 -0
- package/dist/chunk-HLR4KZBP.js +3234 -0
- package/dist/chunk-IP3FRFEB.js +1045 -0
- package/dist/chunk-KHU56VDO.js +3042 -0
- package/dist/chunk-KRYIFLQR.js +88 -0
- package/dist/chunk-LBSDNLEM.js +287 -0
- package/dist/chunk-MNTQ7HCP.js +2643 -0
- package/dist/chunk-MUJELQQ6.js +1387 -0
- package/dist/chunk-MXJGMSLV.js +2199 -0
- package/dist/chunk-N6QJGC3Z.js +2636 -0
- package/dist/chunk-OBELGBPM.js +1713 -0
- package/dist/chunk-OT7R5XTA.js +3192 -0
- package/dist/chunk-P7X4RA2T.js +106 -0
- package/dist/chunk-PIDUQNC2.js +3185 -0
- package/dist/chunk-POGCDIH4.js +3187 -0
- package/dist/chunk-PSIEOQGZ.js +3043 -0
- package/dist/chunk-PVRT3IHA.js +3238 -0
- package/dist/chunk-QNN4TT23.js +1430 -0
- package/dist/chunk-RE3R45RJ.js +3042 -0
- package/dist/chunk-S7E6TFX6.js +803 -0
- package/dist/chunk-SG6GLU4U.js +1378 -0
- package/dist/chunk-SJCDV2ST.js +274 -0
- package/dist/chunk-SYE5XLF3.js +104 -0
- package/dist/chunk-T5VLYBZD.js +103 -0
- package/dist/chunk-TOQB7VWU.js +3238 -0
- package/dist/chunk-VFNMZ4ZQ.js +3228 -0
- package/dist/chunk-VVTGZNBT.js +1629 -0
- package/dist/chunk-W7Q4RFEV.js +104 -0
- package/dist/chunk-XTYYVRLO.js +3190 -0
- package/dist/chunk-Y6MDYVJD.js +3063 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +5458 -0
- package/dist/index.d.ts +653 -0
- package/dist/index.js +79 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +472 -0
- package/dist/schema-BAWSG7KY.js +22 -0
- package/dist/schema-E3QUPL26.js +20 -0
- package/dist/schema-EHL7WUT6.js +20 -0
- package/docs/019-USAGE.md +625 -0
- package/docs/020-current-implementation.md +364 -0
- package/docs/021-DOGFOODING-FINDINGS.md +175 -0
- package/docs/BACKLOG.md +80 -0
- package/docs/CONFIG.md +1123 -0
- package/docs/DESIGN.md +439 -0
- package/docs/ERRORS.md +383 -0
- package/docs/PROJECT.md +88 -0
- package/docs/ROADMAP.md +407 -0
- package/docs/summarization.md +320 -0
- package/docs/test-links.md +9 -0
- package/justfile +40 -0
- package/package.json +74 -9
- package/pnpm-workspace.yaml +5 -0
- package/research/INDEX.md +315 -0
- package/research/code-review/README.md +90 -0
- package/research/code-review/cli-error-handling-review.md +979 -0
- package/research/code-review/code-review-validation-report.md +464 -0
- package/research/code-review/main-ts-review.md +1128 -0
- package/research/config-analysis/01-current-implementation.md +470 -0
- package/research/config-analysis/02-strategy-recommendation.md +428 -0
- package/research/config-analysis/03-task-candidates.md +715 -0
- package/research/config-analysis/033-research-configuration-management.md +828 -0
- package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
- package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
- package/research/config-docs/SUMMARY.md +357 -0
- package/research/config-docs/TEST-RESULTS.md +776 -0
- package/research/config-docs/TODO.md +542 -0
- package/research/config-docs/analysis.md +744 -0
- package/research/config-docs/fix-validation.md +502 -0
- package/research/config-docs/help-audit.md +264 -0
- package/research/config-docs/help-system-analysis.md +890 -0
- package/research/dogfood/consolidated-tool-evaluation.md +373 -0
- package/research/dogfood/strategy-a/a-synthesis.md +184 -0
- package/research/dogfood/strategy-a/a1-docs.md +226 -0
- package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
- package/research/dogfood/strategy-a/a3-llm.md +164 -0
- package/research/dogfood/strategy-b/b-synthesis.md +228 -0
- package/research/dogfood/strategy-b/b1-architecture.md +207 -0
- package/research/dogfood/strategy-b/b2-gaps.md +258 -0
- package/research/dogfood/strategy-b/b3-workflows.md +250 -0
- package/research/dogfood/strategy-c/c-synthesis.md +451 -0
- package/research/dogfood/strategy-c/c1-explorer.md +192 -0
- package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
- package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
- package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
- package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
- package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
- package/research/effect-cli-error-handling.md +845 -0
- package/research/effect-errors-as-values.md +943 -0
- package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
- package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
- package/research/errors-task-analysis/embeddings-analysis.md +709 -0
- package/research/errors-task-analysis/index-search-analysis.md +812 -0
- package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
- package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
- package/research/issue-review.md +603 -0
- package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
- package/research/llm-summarization/alternative-providers-2026.md +1428 -0
- package/research/llm-summarization/anthropic-2026.md +367 -0
- package/research/llm-summarization/claude-cli-integration.md +1706 -0
- package/research/llm-summarization/cli-integration-patterns.md +3155 -0
- package/research/llm-summarization/openai-2026.md +473 -0
- package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
- package/research/llm-summarization/opencode-cli-integration.md +1552 -0
- package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
- package/research/llm-summarization/prototype-results.md +56 -0
- package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
- package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
- package/research/mdcontext-error-analysis.md +521 -0
- package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
- package/research/mdcontext-pudding/01-index-embed.md +956 -0
- package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
- package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
- package/research/mdcontext-pudding/02-search.md +970 -0
- package/research/mdcontext-pudding/03-context.md +779 -0
- package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
- package/research/mdcontext-pudding/04-tree.md +704 -0
- package/research/mdcontext-pudding/05-config.md +1038 -0
- package/research/mdcontext-pudding/06-links-summary.txt +87 -0
- package/research/mdcontext-pudding/06-links.md +679 -0
- package/research/mdcontext-pudding/07-stats.md +693 -0
- package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
- package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
- package/research/mdcontext-pudding/README.md +168 -0
- package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
- package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
- package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
- package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
- package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
- package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
- package/research/research-quality-review.md +834 -0
- package/research/semantic-search/002-research-embedding-models.md +490 -0
- package/research/semantic-search/003-research-rag-alternatives.md +523 -0
- package/research/semantic-search/004-research-vector-search.md +841 -0
- package/research/semantic-search/032-research-semantic-search.md +427 -0
- package/research/semantic-search/embedding-text-analysis.md +156 -0
- package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
- package/research/semantic-search/query-processing-analysis.md +207 -0
- package/research/semantic-search/root-cause-and-solution.md +114 -0
- package/research/semantic-search/threshold-validation-report.md +69 -0
- package/research/semantic-search/vector-search-analysis.md +63 -0
- package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
- package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
- package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
- package/research/task-management-2026/03-lightweight-file-based.md +567 -0
- package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
- package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
- package/research/task-management-2026/linear/02-api-integrations.md +930 -0
- package/research/task-management-2026/linear/03-ai-features.md +368 -0
- package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
- package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
- package/research/test-path-issues.md +276 -0
- package/review/ALP-76/1-error-type-design.md +962 -0
- package/review/ALP-76/2-error-handling-patterns.md +906 -0
- package/review/ALP-76/3-error-presentation.md +624 -0
- package/review/ALP-76/4-test-coverage.md +625 -0
- package/review/ALP-76/5-migration-completeness.md +440 -0
- package/review/ALP-76/6-effect-best-practices.md +755 -0
- package/scripts/apply-branch-protection.sh +47 -0
- package/scripts/branch-protection-templates.json +79 -0
- package/scripts/prototype-summarization.ts +346 -0
- package/scripts/rebuild-hnswlib.js +58 -0
- package/scripts/setup-branch-protection.sh +64 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
- package/src/cli/argv-preprocessor.test.ts +210 -0
- package/src/cli/argv-preprocessor.ts +202 -0
- package/src/cli/cli.test.ts +627 -0
- package/src/cli/commands/backlinks.ts +54 -0
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +285 -0
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +480 -0
- package/src/cli/commands/index.ts +16 -0
- package/src/cli/commands/links.ts +52 -0
- package/src/cli/commands/search.ts +1281 -0
- package/src/cli/commands/stats.ts +149 -0
- package/src/cli/commands/tree.ts +128 -0
- package/src/cli/config-layer.ts +176 -0
- package/src/cli/error-handler.test.ts +235 -0
- package/src/cli/error-handler.ts +655 -0
- package/src/cli/flag-schemas.ts +341 -0
- package/src/cli/help.ts +588 -0
- package/src/cli/index.ts +9 -0
- package/src/cli/main.ts +435 -0
- package/src/cli/options.ts +41 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/typo-suggester.test.ts +105 -0
- package/src/cli/typo-suggester.ts +130 -0
- package/src/cli/utils.ts +259 -0
- package/src/config/file-provider.test.ts +320 -0
- package/src/config/file-provider.ts +273 -0
- package/src/config/index.ts +72 -0
- package/src/config/integration.test.ts +667 -0
- package/src/config/precedence.test.ts +277 -0
- package/src/config/precedence.ts +451 -0
- package/src/config/schema.test.ts +414 -0
- package/src/config/schema.ts +603 -0
- package/src/config/service.test.ts +320 -0
- package/src/config/service.ts +243 -0
- package/src/config/testing.test.ts +264 -0
- package/src/config/testing.ts +110 -0
- package/src/core/index.ts +1 -0
- package/src/core/types.ts +113 -0
- package/src/duplicates/detector.test.ts +183 -0
- package/src/duplicates/detector.ts +414 -0
- package/src/duplicates/index.ts +18 -0
- package/src/embeddings/embedding-namespace.test.ts +300 -0
- package/src/embeddings/embedding-namespace.ts +947 -0
- package/src/embeddings/heading-boost.test.ts +222 -0
- package/src/embeddings/hnsw-build-options.test.ts +198 -0
- package/src/embeddings/hyde.test.ts +272 -0
- package/src/embeddings/hyde.ts +264 -0
- package/src/embeddings/index.ts +10 -0
- package/src/embeddings/openai-provider.ts +414 -0
- package/src/embeddings/pricing.json +22 -0
- package/src/embeddings/provider-constants.ts +204 -0
- package/src/embeddings/provider-errors.test.ts +967 -0
- package/src/embeddings/provider-errors.ts +565 -0
- package/src/embeddings/provider-factory.test.ts +240 -0
- package/src/embeddings/provider-factory.ts +225 -0
- package/src/embeddings/provider-integration.test.ts +788 -0
- package/src/embeddings/query-preprocessing.test.ts +187 -0
- package/src/embeddings/semantic-search-threshold.test.ts +508 -0
- package/src/embeddings/semantic-search.ts +1270 -0
- package/src/embeddings/types.ts +359 -0
- package/src/embeddings/vector-store.ts +708 -0
- package/src/embeddings/voyage-provider.ts +313 -0
- package/src/errors/errors.test.ts +845 -0
- package/src/errors/index.ts +533 -0
- package/src/index/ignore-patterns.test.ts +354 -0
- package/src/index/ignore-patterns.ts +305 -0
- package/src/index/index.ts +4 -0
- package/src/index/indexer.ts +684 -0
- package/src/index/storage.ts +260 -0
- package/src/index/types.ts +147 -0
- package/src/index/watcher.ts +189 -0
- package/src/index.ts +30 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +612 -0
- package/src/parser/index.ts +1 -0
- package/src/parser/parser.test.ts +291 -0
- package/src/parser/parser.ts +394 -0
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +392 -0
- package/src/search/__tests__/hybrid-search.test.ts +650 -0
- package/src/search/bm25-store.ts +366 -0
- package/src/search/cross-encoder.test.ts +253 -0
- package/src/search/cross-encoder.ts +406 -0
- package/src/search/fuzzy-search.test.ts +419 -0
- package/src/search/fuzzy-search.ts +273 -0
- package/src/search/hybrid-search.ts +448 -0
- package/src/search/path-matcher.test.ts +276 -0
- package/src/search/path-matcher.ts +33 -0
- package/src/search/query-parser.test.ts +260 -0
- package/src/search/query-parser.ts +319 -0
- package/src/search/searcher.test.ts +280 -0
- package/src/search/searcher.ts +724 -0
- package/src/search/wink-bm25.d.ts +30 -0
- package/src/summarization/cli-providers/claude.ts +202 -0
- package/src/summarization/cli-providers/detection.test.ts +273 -0
- package/src/summarization/cli-providers/detection.ts +118 -0
- package/src/summarization/cli-providers/index.ts +8 -0
- package/src/summarization/cost.test.ts +139 -0
- package/src/summarization/cost.ts +102 -0
- package/src/summarization/error-handler.test.ts +127 -0
- package/src/summarization/error-handler.ts +111 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/pipeline.test.ts +498 -0
- package/src/summarization/pipeline.ts +231 -0
- package/src/summarization/prompts.test.ts +269 -0
- package/src/summarization/prompts.ts +133 -0
- package/src/summarization/provider-factory.test.ts +396 -0
- package/src/summarization/provider-factory.ts +178 -0
- package/src/summarization/types.ts +184 -0
- package/src/summarize/budget-bugs.test.ts +620 -0
- package/src/summarize/formatters.ts +419 -0
- package/src/summarize/index.ts +20 -0
- package/src/summarize/summarizer.test.ts +275 -0
- package/src/summarize/summarizer.ts +597 -0
- package/src/summarize/verify-bugs.test.ts +238 -0
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tokens.test.ts +142 -0
- package/src/utils/tokens.ts +186 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
- package/tests/fixtures/cli/.mdcontext/config.json +8 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
- package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
- package/tests/fixtures/cli/README.md +9 -0
- package/tests/fixtures/cli/api-reference.md +11 -0
- package/tests/fixtures/cli/getting-started.md +11 -0
- package/tests/integration/embed-index.test.ts +712 -0
- package/tests/integration/search-context.test.ts +469 -0
- package/tests/integration/search-semantic.test.ts +522 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +16 -0
- 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
|
+
})
|