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,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests verifying the full config precedence chain works correctly:
|
|
5
|
+
* 1. CLI flags override config file
|
|
6
|
+
* 2. Environment variables override config file
|
|
7
|
+
* 3. Config file overrides defaults
|
|
8
|
+
* 4. Invalid config produces friendly errors
|
|
9
|
+
*
|
|
10
|
+
* These tests exercise the complete config loading stack, not just
|
|
11
|
+
* individual providers in isolation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from 'node:fs'
|
|
15
|
+
import * as os from 'node:os'
|
|
16
|
+
import * as path from 'node:path'
|
|
17
|
+
import { Effect } from 'effect'
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
19
|
+
import {
|
|
20
|
+
createConfigProvider,
|
|
21
|
+
createConfigProviderSync,
|
|
22
|
+
loadConfigFile,
|
|
23
|
+
loadConfigFromPath,
|
|
24
|
+
} from './index.js'
|
|
25
|
+
import { defaultConfig, MdContextConfig } from './schema.js'
|
|
26
|
+
import {
|
|
27
|
+
ConfigService,
|
|
28
|
+
makeConfigLayerPartial,
|
|
29
|
+
mergeWithDefaults,
|
|
30
|
+
} from './service.js'
|
|
31
|
+
|
|
32
|
+
describe('Config Integration Tests', () => {
|
|
33
|
+
let tempDir: string
|
|
34
|
+
const savedEnv: Record<string, string | undefined> = {}
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-integration-'))
|
|
38
|
+
// Save and clear MDCONTEXT_ env vars
|
|
39
|
+
for (const key of Object.keys(process.env)) {
|
|
40
|
+
if (key.startsWith('MDCONTEXT_')) {
|
|
41
|
+
savedEnv[key] = process.env[key]
|
|
42
|
+
delete process.env[key]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
49
|
+
// Restore env vars
|
|
50
|
+
for (const key of Object.keys(process.env)) {
|
|
51
|
+
if (key.startsWith('MDCONTEXT_')) {
|
|
52
|
+
delete process.env[key]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
56
|
+
if (value !== undefined) {
|
|
57
|
+
process.env[key] = value
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('Precedence: CLI > Env > File > Defaults', () => {
|
|
63
|
+
it('should use defaults when no other source is provided', async () => {
|
|
64
|
+
const provider = createConfigProviderSync({
|
|
65
|
+
skipEnv: true,
|
|
66
|
+
skipConfigFile: true,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const result = await Effect.runPromise(
|
|
70
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// All values should match defaults
|
|
74
|
+
expect(result.index.maxDepth).toBe(defaultConfig.index.maxDepth)
|
|
75
|
+
expect(result.index.excludePatterns).toEqual(
|
|
76
|
+
defaultConfig.index.excludePatterns,
|
|
77
|
+
)
|
|
78
|
+
expect(result.search.defaultLimit).toBe(defaultConfig.search.defaultLimit)
|
|
79
|
+
expect(result.output.format).toBe(defaultConfig.output.format)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should let config file override defaults', async () => {
|
|
83
|
+
// Create config file with custom values
|
|
84
|
+
const fileConfig = {
|
|
85
|
+
index: { maxDepth: 20 },
|
|
86
|
+
search: { defaultLimit: 50 },
|
|
87
|
+
}
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
90
|
+
JSON.stringify(fileConfig),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const provider = await Effect.runPromise(
|
|
94
|
+
createConfigProvider({
|
|
95
|
+
workingDir: tempDir,
|
|
96
|
+
skipEnv: true,
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const result = await Effect.runPromise(
|
|
101
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// File values should override defaults
|
|
105
|
+
expect(result.index.maxDepth).toBe(20)
|
|
106
|
+
expect(result.search.defaultLimit).toBe(50)
|
|
107
|
+
// Non-specified values should use defaults
|
|
108
|
+
expect(result.output.format).toBe(defaultConfig.output.format)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should let environment variables override config file', async () => {
|
|
112
|
+
// Create config file
|
|
113
|
+
const fileConfig = {
|
|
114
|
+
index: { maxDepth: 20 },
|
|
115
|
+
search: { defaultLimit: 50 },
|
|
116
|
+
}
|
|
117
|
+
fs.writeFileSync(
|
|
118
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
119
|
+
JSON.stringify(fileConfig),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Set env vars (should override file)
|
|
123
|
+
process.env.MDCONTEXT_INDEX_MAXDEPTH = '30'
|
|
124
|
+
|
|
125
|
+
const provider = await Effect.runPromise(
|
|
126
|
+
createConfigProvider({
|
|
127
|
+
workingDir: tempDir,
|
|
128
|
+
skipEnv: false,
|
|
129
|
+
}),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const result = await Effect.runPromise(
|
|
133
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// Env var should override file config
|
|
137
|
+
expect(result.index.maxDepth).toBe(30)
|
|
138
|
+
// Non-env values should still come from file
|
|
139
|
+
expect(result.search.defaultLimit).toBe(50)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should let CLI flags override environment variables', async () => {
|
|
143
|
+
// Create config file
|
|
144
|
+
const fileConfig = {
|
|
145
|
+
index: { maxDepth: 20 },
|
|
146
|
+
search: { defaultLimit: 50 },
|
|
147
|
+
}
|
|
148
|
+
fs.writeFileSync(
|
|
149
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
150
|
+
JSON.stringify(fileConfig),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
// Set env var
|
|
154
|
+
process.env.MDCONTEXT_INDEX_MAXDEPTH = '30'
|
|
155
|
+
|
|
156
|
+
const provider = await Effect.runPromise(
|
|
157
|
+
createConfigProvider({
|
|
158
|
+
workingDir: tempDir,
|
|
159
|
+
skipEnv: false,
|
|
160
|
+
cliOverrides: { index: { maxDepth: 5 } },
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const result = await Effect.runPromise(
|
|
165
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
// CLI should override everything
|
|
169
|
+
expect(result.index.maxDepth).toBe(5)
|
|
170
|
+
// Non-CLI values should use env/file chain
|
|
171
|
+
expect(result.search.defaultLimit).toBe(50)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('should handle complete precedence chain (CLI > Env > File > Default)', async () => {
|
|
175
|
+
// Create config file with all sections
|
|
176
|
+
const fileConfig = {
|
|
177
|
+
index: { maxDepth: 20, followSymlinks: true },
|
|
178
|
+
search: { defaultLimit: 50, minSimilarity: 0.7 },
|
|
179
|
+
output: { verbose: true },
|
|
180
|
+
}
|
|
181
|
+
fs.writeFileSync(
|
|
182
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
183
|
+
JSON.stringify(fileConfig),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Set env vars (should override some file values)
|
|
187
|
+
process.env.MDCONTEXT_INDEX_MAXDEPTH = '30'
|
|
188
|
+
process.env.MDCONTEXT_SEARCH_DEFAULTLIMIT = '75'
|
|
189
|
+
|
|
190
|
+
// CLI overrides (should override both env and file)
|
|
191
|
+
const cliOverrides = {
|
|
192
|
+
index: { maxDepth: 5 },
|
|
193
|
+
output: { debug: true },
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const provider = await Effect.runPromise(
|
|
197
|
+
createConfigProvider({
|
|
198
|
+
workingDir: tempDir,
|
|
199
|
+
skipEnv: false,
|
|
200
|
+
cliOverrides,
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
const result = await Effect.runPromise(
|
|
205
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
// CLI wins over all: index.maxDepth=5 (from CLI)
|
|
209
|
+
expect(result.index.maxDepth).toBe(5)
|
|
210
|
+
// output.debug=true (from CLI)
|
|
211
|
+
expect(result.output.debug).toBe(true)
|
|
212
|
+
// Env wins over file: search.defaultLimit=75 (from env)
|
|
213
|
+
expect(result.search.defaultLimit).toBe(75)
|
|
214
|
+
// File wins over defaults: index.followSymlinks=true, search.minSimilarity=0.7
|
|
215
|
+
expect(result.index.followSymlinks).toBe(true)
|
|
216
|
+
expect(result.search.minSimilarity).toBe(0.7)
|
|
217
|
+
// output.verbose=true (from file)
|
|
218
|
+
expect(result.output.verbose).toBe(true)
|
|
219
|
+
// Default for unspecified: output.format='text'
|
|
220
|
+
expect(result.output.format).toBe('text')
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
describe('Array value handling', () => {
|
|
225
|
+
it('should merge exclude patterns from config file', async () => {
|
|
226
|
+
const fileConfig = {
|
|
227
|
+
index: { excludePatterns: ['custom-ignore', '*.bak'] },
|
|
228
|
+
}
|
|
229
|
+
fs.writeFileSync(
|
|
230
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
231
|
+
JSON.stringify(fileConfig),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const provider = await Effect.runPromise(
|
|
235
|
+
createConfigProvider({
|
|
236
|
+
workingDir: tempDir,
|
|
237
|
+
skipEnv: true,
|
|
238
|
+
}),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const result = await Effect.runPromise(
|
|
242
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
expect(result.index.excludePatterns).toEqual(['custom-ignore', '*.bak'])
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should override arrays via environment variable', async () => {
|
|
249
|
+
const fileConfig = {
|
|
250
|
+
index: { excludePatterns: ['custom-ignore', '*.bak'] },
|
|
251
|
+
}
|
|
252
|
+
fs.writeFileSync(
|
|
253
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
254
|
+
JSON.stringify(fileConfig),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
// Env vars use comma-separated format
|
|
258
|
+
process.env.MDCONTEXT_INDEX_EXCLUDEPATTERNS = 'env-ignore,env-pattern'
|
|
259
|
+
|
|
260
|
+
const provider = await Effect.runPromise(
|
|
261
|
+
createConfigProvider({
|
|
262
|
+
workingDir: tempDir,
|
|
263
|
+
skipEnv: false,
|
|
264
|
+
}),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const result = await Effect.runPromise(
|
|
268
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
expect(result.index.excludePatterns).toEqual([
|
|
272
|
+
'env-ignore',
|
|
273
|
+
'env-pattern',
|
|
274
|
+
])
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('should override arrays via CLI', async () => {
|
|
278
|
+
process.env.MDCONTEXT_INDEX_EXCLUDEPATTERNS = 'env-ignore'
|
|
279
|
+
|
|
280
|
+
const provider = createConfigProviderSync({
|
|
281
|
+
skipConfigFile: true,
|
|
282
|
+
skipEnv: false,
|
|
283
|
+
cliOverrides: {
|
|
284
|
+
index: { excludePatterns: ['cli-ignore', 'cli-pattern'] },
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const result = await Effect.runPromise(
|
|
289
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
expect(result.index.excludePatterns).toEqual([
|
|
293
|
+
'cli-ignore',
|
|
294
|
+
'cli-pattern',
|
|
295
|
+
])
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
describe('ConfigService integration', () => {
|
|
300
|
+
it('should work with ConfigService layer and custom config', async () => {
|
|
301
|
+
const program = Effect.gen(function* () {
|
|
302
|
+
const config = yield* ConfigService
|
|
303
|
+
return {
|
|
304
|
+
maxDepth: config.index.maxDepth,
|
|
305
|
+
defaultLimit: config.search.defaultLimit,
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const layer = makeConfigLayerPartial({
|
|
310
|
+
index: { maxDepth: 42 },
|
|
311
|
+
search: { defaultLimit: 99 },
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
const result = await Effect.runPromise(
|
|
315
|
+
program.pipe(Effect.provide(layer)),
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
expect(result.maxDepth).toBe(42)
|
|
319
|
+
expect(result.defaultLimit).toBe(99)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('should merge partial config with defaults correctly', () => {
|
|
323
|
+
const partial = {
|
|
324
|
+
index: { maxDepth: 25 },
|
|
325
|
+
output: { debug: true },
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const merged = mergeWithDefaults(partial)
|
|
329
|
+
|
|
330
|
+
// Partial values override
|
|
331
|
+
expect(merged.index.maxDepth).toBe(25)
|
|
332
|
+
expect(merged.output.debug).toBe(true)
|
|
333
|
+
// Other values from defaults
|
|
334
|
+
expect(merged.index.excludePatterns).toEqual(
|
|
335
|
+
defaultConfig.index.excludePatterns,
|
|
336
|
+
)
|
|
337
|
+
expect(merged.search.defaultLimit).toBe(defaultConfig.search.defaultLimit)
|
|
338
|
+
expect(merged.output.format).toBe('text')
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
describe('Invalid config error handling', () => {
|
|
343
|
+
it('should produce ConfigError for invalid JSON in config file', async () => {
|
|
344
|
+
fs.writeFileSync(
|
|
345
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
346
|
+
'not valid json { broken',
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
const result = await Effect.runPromiseExit(loadConfigFile(tempDir))
|
|
350
|
+
|
|
351
|
+
expect(result._tag).toBe('Failure')
|
|
352
|
+
if (result._tag === 'Failure') {
|
|
353
|
+
const errorStr = String(result.cause)
|
|
354
|
+
expect(errorStr).toContain('ConfigError')
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should produce ConfigError when explicit config file is missing', async () => {
|
|
359
|
+
const nonexistentPath = path.join(tempDir, 'nonexistent.json')
|
|
360
|
+
|
|
361
|
+
const result = await Effect.runPromiseExit(
|
|
362
|
+
loadConfigFromPath(nonexistentPath),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
expect(result._tag).toBe('Failure')
|
|
366
|
+
if (result._tag === 'Failure') {
|
|
367
|
+
const errorStr = String(result.cause)
|
|
368
|
+
expect(errorStr).toContain('ConfigError')
|
|
369
|
+
expect(errorStr).toContain('not found')
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('should produce ConfigError with helpful details for malformed config', async () => {
|
|
374
|
+
// Write config with invalid type (maxDepth should be number)
|
|
375
|
+
fs.writeFileSync(
|
|
376
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
377
|
+
JSON.stringify({ index: { maxDepth: 'not-a-number' } }),
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
// When we try to load this into the full config schema, it should fail
|
|
381
|
+
// because maxDepth needs to be parsed as a number
|
|
382
|
+
const provider = await Effect.runPromise(
|
|
383
|
+
createConfigProvider({
|
|
384
|
+
workingDir: tempDir,
|
|
385
|
+
skipEnv: true,
|
|
386
|
+
}),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
// This should fail because maxDepth is not a valid number
|
|
390
|
+
const result = await Effect.runPromiseExit(
|
|
391
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
expect(result._tag).toBe('Failure')
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should include source file path in error for file-based errors', async () => {
|
|
398
|
+
const configPath = path.join(tempDir, 'mdcontext.config.json')
|
|
399
|
+
fs.writeFileSync(configPath, '{ invalid json }')
|
|
400
|
+
|
|
401
|
+
const result = await Effect.runPromiseExit(loadConfigFromPath(configPath))
|
|
402
|
+
|
|
403
|
+
expect(result._tag).toBe('Failure')
|
|
404
|
+
if (result._tag === 'Failure') {
|
|
405
|
+
const errorStr = String(result.cause)
|
|
406
|
+
// Error should mention the file path
|
|
407
|
+
expect(errorStr).toContain('mdcontext.config.json')
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('Config file format support', () => {
|
|
413
|
+
it('should load JSON config file', async () => {
|
|
414
|
+
const config = { index: { maxDepth: 15 } }
|
|
415
|
+
fs.writeFileSync(
|
|
416
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
417
|
+
JSON.stringify(config),
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
const result = await Effect.runPromise(loadConfigFile(tempDir))
|
|
421
|
+
expect(result.found).toBe(true)
|
|
422
|
+
if (result.found) {
|
|
423
|
+
expect(result.config.index?.maxDepth).toBe(15)
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it('should load .mdcontextrc file (JSON format)', async () => {
|
|
428
|
+
const config = { search: { defaultLimit: 25 } }
|
|
429
|
+
fs.writeFileSync(
|
|
430
|
+
path.join(tempDir, '.mdcontextrc'),
|
|
431
|
+
JSON.stringify(config),
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
const result = await Effect.runPromise(loadConfigFile(tempDir))
|
|
435
|
+
expect(result.found).toBe(true)
|
|
436
|
+
if (result.found) {
|
|
437
|
+
expect(result.config.search?.defaultLimit).toBe(25)
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should load .mjs config with default export', async () => {
|
|
442
|
+
const configPath = path.join(tempDir, 'mdcontext.config.mjs')
|
|
443
|
+
fs.writeFileSync(
|
|
444
|
+
configPath,
|
|
445
|
+
`export default { output: { verbose: true } }`,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
const result = await Effect.runPromise(loadConfigFile(tempDir))
|
|
449
|
+
expect(result.found).toBe(true)
|
|
450
|
+
if (result.found) {
|
|
451
|
+
expect(result.config.output?.verbose).toBe(true)
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('should load .mjs config with named export', async () => {
|
|
456
|
+
const configPath = path.join(tempDir, 'mdcontext.config.mjs')
|
|
457
|
+
fs.writeFileSync(
|
|
458
|
+
configPath,
|
|
459
|
+
`export const config = { embeddings: { batchSize: 50 } }`,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
const result = await Effect.runPromise(loadConfigFile(tempDir))
|
|
463
|
+
expect(result.found).toBe(true)
|
|
464
|
+
if (result.found) {
|
|
465
|
+
expect(result.config.embeddings?.batchSize).toBe(50)
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
describe('Real-world usage scenarios', () => {
|
|
471
|
+
it('should support project-specific config with team defaults', async () => {
|
|
472
|
+
// Scenario: Team has default settings in config file,
|
|
473
|
+
// but developer overrides verbosity via env var for debugging
|
|
474
|
+
const teamConfig = {
|
|
475
|
+
index: {
|
|
476
|
+
maxDepth: 5,
|
|
477
|
+
excludePatterns: ['node_modules', 'dist', '.git', 'vendor'],
|
|
478
|
+
},
|
|
479
|
+
search: { defaultLimit: 25 },
|
|
480
|
+
output: { prettyJson: true },
|
|
481
|
+
}
|
|
482
|
+
fs.writeFileSync(
|
|
483
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
484
|
+
JSON.stringify(teamConfig),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
// Developer enables debug mode
|
|
488
|
+
process.env.MDCONTEXT_OUTPUT_DEBUG = 'true'
|
|
489
|
+
process.env.MDCONTEXT_OUTPUT_VERBOSE = 'true'
|
|
490
|
+
|
|
491
|
+
const provider = await Effect.runPromise(
|
|
492
|
+
createConfigProvider({
|
|
493
|
+
workingDir: tempDir,
|
|
494
|
+
skipEnv: false,
|
|
495
|
+
}),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
const result = await Effect.runPromise(
|
|
499
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
// Team settings preserved
|
|
503
|
+
expect(result.index.maxDepth).toBe(5)
|
|
504
|
+
expect(result.index.excludePatterns).toEqual([
|
|
505
|
+
'node_modules',
|
|
506
|
+
'dist',
|
|
507
|
+
'.git',
|
|
508
|
+
'vendor',
|
|
509
|
+
])
|
|
510
|
+
expect(result.search.defaultLimit).toBe(25)
|
|
511
|
+
expect(result.output.prettyJson).toBe(true)
|
|
512
|
+
// Developer overrides active
|
|
513
|
+
expect(result.output.debug).toBe(true)
|
|
514
|
+
expect(result.output.verbose).toBe(true)
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('should support CI environment with minimal config', async () => {
|
|
518
|
+
// Scenario: CI uses env vars only, no config file
|
|
519
|
+
process.env.MDCONTEXT_OUTPUT_FORMAT = 'json'
|
|
520
|
+
process.env.MDCONTEXT_OUTPUT_COLOR = 'false'
|
|
521
|
+
process.env.MDCONTEXT_INDEX_MAXDEPTH = '100'
|
|
522
|
+
|
|
523
|
+
const provider = createConfigProviderSync({
|
|
524
|
+
skipConfigFile: true,
|
|
525
|
+
skipEnv: false,
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
const result = await Effect.runPromise(
|
|
529
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
expect(result.output.format).toBe('json')
|
|
533
|
+
expect(result.output.color).toBe(false)
|
|
534
|
+
expect(result.index.maxDepth).toBe(100)
|
|
535
|
+
// Defaults for unset values
|
|
536
|
+
expect(result.search.defaultLimit).toBe(defaultConfig.search.defaultLimit)
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it('should support CLI one-off override without modifying config', async () => {
|
|
540
|
+
// Scenario: User has config file but wants to run with different settings once
|
|
541
|
+
const fileConfig = {
|
|
542
|
+
index: { maxDepth: 10 },
|
|
543
|
+
search: { defaultLimit: 20 },
|
|
544
|
+
}
|
|
545
|
+
fs.writeFileSync(
|
|
546
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
547
|
+
JSON.stringify(fileConfig),
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
// One-off CLI override
|
|
551
|
+
const cliOverrides = {
|
|
552
|
+
search: { defaultLimit: 100 },
|
|
553
|
+
output: { format: 'json' as const },
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const provider = await Effect.runPromise(
|
|
557
|
+
createConfigProvider({
|
|
558
|
+
workingDir: tempDir,
|
|
559
|
+
skipEnv: true,
|
|
560
|
+
cliOverrides,
|
|
561
|
+
}),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
const result = await Effect.runPromise(
|
|
565
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
// CLI override active
|
|
569
|
+
expect(result.search.defaultLimit).toBe(100)
|
|
570
|
+
// File config still applies where not overridden
|
|
571
|
+
expect(result.index.maxDepth).toBe(10)
|
|
572
|
+
})
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
describe('Custom config path', () => {
|
|
576
|
+
it('should load config from explicit path', async () => {
|
|
577
|
+
const customPath = path.join(tempDir, 'custom', 'my-config.json')
|
|
578
|
+
fs.mkdirSync(path.dirname(customPath), { recursive: true })
|
|
579
|
+
fs.writeFileSync(customPath, JSON.stringify({ index: { maxDepth: 99 } }))
|
|
580
|
+
|
|
581
|
+
const result = await Effect.runPromise(loadConfigFromPath(customPath))
|
|
582
|
+
expect(result.index?.maxDepth).toBe(99)
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it('should use explicit config path over auto-detected config', async () => {
|
|
586
|
+
// Create auto-detected config
|
|
587
|
+
fs.writeFileSync(
|
|
588
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
589
|
+
JSON.stringify({ index: { maxDepth: 10 } }),
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
// Create custom config
|
|
593
|
+
const customPath = path.join(tempDir, 'custom.json')
|
|
594
|
+
fs.writeFileSync(customPath, JSON.stringify({ index: { maxDepth: 99 } }))
|
|
595
|
+
|
|
596
|
+
const provider = await Effect.runPromise(
|
|
597
|
+
createConfigProvider({
|
|
598
|
+
configPath: customPath,
|
|
599
|
+
skipEnv: true,
|
|
600
|
+
}),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
const result = await Effect.runPromise(
|
|
604
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
// Custom path should be used
|
|
608
|
+
expect(result.index.maxDepth).toBe(99)
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
it('should load TypeScript config from explicit path', async () => {
|
|
612
|
+
// Note: We can't actually test .ts files without tsx runtime,
|
|
613
|
+
// but we can test .mjs files which use the same dynamic import path
|
|
614
|
+
const customPath = path.join(tempDir, 'custom.config.mjs')
|
|
615
|
+
fs.writeFileSync(
|
|
616
|
+
customPath,
|
|
617
|
+
`export default { index: { maxDepth: 77 }, search: { defaultLimit: 33 } }`,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
const result = await Effect.runPromise(loadConfigFromPath(customPath))
|
|
621
|
+
expect(result.index?.maxDepth).toBe(77)
|
|
622
|
+
expect(result.search?.defaultLimit).toBe(33)
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
it('should load JavaScript config from explicit path with named export', async () => {
|
|
626
|
+
const customPath = path.join(tempDir, 'custom.config.mjs')
|
|
627
|
+
fs.writeFileSync(
|
|
628
|
+
customPath,
|
|
629
|
+
`export const config = { output: { verbose: true, debug: true } }`,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
const result = await Effect.runPromise(loadConfigFromPath(customPath))
|
|
633
|
+
expect(result.output?.verbose).toBe(true)
|
|
634
|
+
expect(result.output?.debug).toBe(true)
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
it('should work with createConfigProvider for TS/JS paths', async () => {
|
|
638
|
+
const customPath = path.join(tempDir, 'custom.config.mjs')
|
|
639
|
+
fs.writeFileSync(customPath, `export default { index: { maxDepth: 88 } }`)
|
|
640
|
+
|
|
641
|
+
const provider = await Effect.runPromise(
|
|
642
|
+
createConfigProvider({
|
|
643
|
+
configPath: customPath,
|
|
644
|
+
skipEnv: true,
|
|
645
|
+
}),
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
const result = await Effect.runPromise(
|
|
649
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
expect(result.index.maxDepth).toBe(88)
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
it('should provide helpful error for invalid TS/JS config', async () => {
|
|
656
|
+
const customPath = path.join(tempDir, 'broken.config.mjs')
|
|
657
|
+
fs.writeFileSync(customPath, `export default "not-an-object"`)
|
|
658
|
+
|
|
659
|
+
const result = await Effect.runPromiseExit(loadConfigFromPath(customPath))
|
|
660
|
+
expect(result._tag).toBe('Failure')
|
|
661
|
+
if (result._tag === 'Failure') {
|
|
662
|
+
const errorStr = String(result.cause)
|
|
663
|
+
expect(errorStr).toContain('ConfigError')
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
})
|