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,320 @@
1
+ /**
2
+ * File-based ConfigProvider Unit Tests
3
+ *
4
+ * Tests for loading configuration from files and creating ConfigProviders.
5
+ */
6
+
7
+ import * as fs from 'node:fs'
8
+ import * as os from 'node:os'
9
+ import * as path from 'node:path'
10
+ import { Effect } from 'effect'
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
12
+ import {
13
+ CONFIG_FILE_NAMES,
14
+ createFileConfigProvider,
15
+ findConfigFile,
16
+ loadConfigFile,
17
+ loadConfigFromPath,
18
+ loadFileConfigProvider,
19
+ } from './file-provider.js'
20
+ import { MdContextConfig } from './schema.js'
21
+
22
+ describe('File-based ConfigProvider', () => {
23
+ let tempDir: string
24
+
25
+ beforeEach(() => {
26
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
27
+ })
28
+
29
+ afterEach(() => {
30
+ fs.rmSync(tempDir, { recursive: true, force: true })
31
+ })
32
+
33
+ describe('CONFIG_FILE_NAMES', () => {
34
+ it('should have the expected file names in order of precedence', () => {
35
+ expect(CONFIG_FILE_NAMES).toEqual([
36
+ 'mdcontext.config.ts',
37
+ 'mdcontext.config.js',
38
+ 'mdcontext.config.mjs',
39
+ 'mdcontext.config.json',
40
+ '.mdcontextrc',
41
+ '.mdcontextrc.json',
42
+ ])
43
+ })
44
+ })
45
+
46
+ describe('findConfigFile', () => {
47
+ it('should return null when no config file exists', () => {
48
+ const result = findConfigFile(tempDir)
49
+ expect(result).toBeNull()
50
+ })
51
+
52
+ it('should find mdcontext.config.json', () => {
53
+ const configPath = path.join(tempDir, 'mdcontext.config.json')
54
+ fs.writeFileSync(configPath, '{}')
55
+
56
+ const result = findConfigFile(tempDir)
57
+ expect(result).not.toBeNull()
58
+ expect(result?.path).toBe(configPath)
59
+ expect(result?.format).toBe('json')
60
+ })
61
+
62
+ it('should find .mdcontextrc', () => {
63
+ const configPath = path.join(tempDir, '.mdcontextrc')
64
+ fs.writeFileSync(configPath, '{}')
65
+
66
+ const result = findConfigFile(tempDir)
67
+ expect(result).not.toBeNull()
68
+ expect(result?.path).toBe(configPath)
69
+ expect(result?.format).toBe('json')
70
+ })
71
+
72
+ it('should find config in parent directory', () => {
73
+ const subDir = path.join(tempDir, 'subdir')
74
+ fs.mkdirSync(subDir)
75
+ const configPath = path.join(tempDir, 'mdcontext.config.json')
76
+ fs.writeFileSync(configPath, '{}')
77
+
78
+ const result = findConfigFile(subDir)
79
+ expect(result).not.toBeNull()
80
+ expect(result?.path).toBe(configPath)
81
+ })
82
+
83
+ it('should prefer higher precedence files', () => {
84
+ // Create both .ts and .json files
85
+ fs.writeFileSync(
86
+ path.join(tempDir, 'mdcontext.config.ts'),
87
+ 'export default {}',
88
+ )
89
+ fs.writeFileSync(path.join(tempDir, 'mdcontext.config.json'), '{}')
90
+
91
+ const result = findConfigFile(tempDir)
92
+ expect(result).not.toBeNull()
93
+ expect(result?.path).toBe(path.join(tempDir, 'mdcontext.config.ts'))
94
+ expect(result?.format).toBe('ts')
95
+ })
96
+
97
+ it('should identify .js format correctly', () => {
98
+ const configPath = path.join(tempDir, 'mdcontext.config.js')
99
+ fs.writeFileSync(configPath, 'module.exports = {}')
100
+
101
+ const result = findConfigFile(tempDir)
102
+ expect(result?.format).toBe('js')
103
+ })
104
+
105
+ it('should identify .mjs format correctly', () => {
106
+ const configPath = path.join(tempDir, 'mdcontext.config.mjs')
107
+ fs.writeFileSync(configPath, 'export default {}')
108
+
109
+ const result = findConfigFile(tempDir)
110
+ expect(result?.format).toBe('js')
111
+ })
112
+ })
113
+
114
+ describe('loadConfigFile', () => {
115
+ it('should return found: false when no config exists', async () => {
116
+ const result = await Effect.runPromise(loadConfigFile(tempDir))
117
+ expect(result.found).toBe(false)
118
+ if (!result.found) {
119
+ expect(result.searched.length).toBeGreaterThan(0)
120
+ }
121
+ })
122
+
123
+ it('should load JSON config file', async () => {
124
+ const config = {
125
+ index: { maxDepth: 5 },
126
+ output: { verbose: true },
127
+ }
128
+ fs.writeFileSync(
129
+ path.join(tempDir, 'mdcontext.config.json'),
130
+ JSON.stringify(config),
131
+ )
132
+
133
+ const result = await Effect.runPromise(loadConfigFile(tempDir))
134
+ expect(result.found).toBe(true)
135
+ if (result.found) {
136
+ expect(result.config).toEqual(config)
137
+ expect(result.path).toContain('mdcontext.config.json')
138
+ }
139
+ })
140
+
141
+ it('should load .mdcontextrc file', async () => {
142
+ const config = { search: { defaultLimit: 20 } }
143
+ fs.writeFileSync(
144
+ path.join(tempDir, '.mdcontextrc'),
145
+ JSON.stringify(config),
146
+ )
147
+
148
+ const result = await Effect.runPromise(loadConfigFile(tempDir))
149
+ expect(result.found).toBe(true)
150
+ if (result.found) {
151
+ expect(result.config).toEqual(config)
152
+ }
153
+ })
154
+
155
+ it('should fail with ConfigError for invalid JSON', async () => {
156
+ fs.writeFileSync(
157
+ path.join(tempDir, 'mdcontext.config.json'),
158
+ 'not valid json',
159
+ )
160
+
161
+ const result = await Effect.runPromiseExit(loadConfigFile(tempDir))
162
+ expect(result._tag).toBe('Failure')
163
+ if (result._tag === 'Failure') {
164
+ const error = result.cause
165
+ expect(String(error)).toContain('ConfigError')
166
+ }
167
+ })
168
+ })
169
+
170
+ describe('loadConfigFromPath', () => {
171
+ it('should load config from explicit path', async () => {
172
+ const configPath = path.join(tempDir, 'custom.json')
173
+ const config = { index: { maxDepth: 15 } }
174
+ fs.writeFileSync(configPath, JSON.stringify(config))
175
+
176
+ const result = await Effect.runPromise(loadConfigFromPath(configPath))
177
+ expect(result).toEqual(config)
178
+ })
179
+
180
+ it('should fail with ConfigError when file does not exist', async () => {
181
+ const configPath = path.join(tempDir, 'nonexistent.json')
182
+
183
+ const result = await Effect.runPromiseExit(loadConfigFromPath(configPath))
184
+ expect(result._tag).toBe('Failure')
185
+ if (result._tag === 'Failure') {
186
+ const error = String(result.cause)
187
+ expect(error).toContain('ConfigError')
188
+ expect(error).toContain('not found')
189
+ }
190
+ })
191
+
192
+ it('should fail with ConfigError for invalid JSON', async () => {
193
+ const configPath = path.join(tempDir, 'invalid.json')
194
+ fs.writeFileSync(configPath, 'invalid json content')
195
+
196
+ const result = await Effect.runPromiseExit(loadConfigFromPath(configPath))
197
+ expect(result._tag).toBe('Failure')
198
+ if (result._tag === 'Failure') {
199
+ expect(String(result.cause)).toContain('ConfigError')
200
+ }
201
+ })
202
+ })
203
+
204
+ describe('createFileConfigProvider', () => {
205
+ it('should create a ConfigProvider from partial config', async () => {
206
+ const config = {
207
+ index: { maxDepth: 25 },
208
+ search: { minSimilarity: 0.8 },
209
+ }
210
+ const provider = createFileConfigProvider(config)
211
+
212
+ const program = Effect.gen(function* () {
213
+ return yield* MdContextConfig
214
+ })
215
+
216
+ const result = await Effect.runPromise(
217
+ Effect.withConfigProvider(program, provider),
218
+ )
219
+
220
+ expect(result.index.maxDepth).toBe(25)
221
+ expect(result.search.minSimilarity).toBe(0.8)
222
+ // Defaults should be used for unspecified values
223
+ expect(result.output.format).toBe('text')
224
+ })
225
+
226
+ it('should work with nested config structure', async () => {
227
+ const config = {
228
+ embeddings: {
229
+ model: 'text-embedding-3-large',
230
+ batchSize: 50,
231
+ },
232
+ paths: {
233
+ cacheDir: '/custom/cache',
234
+ },
235
+ }
236
+ const provider = createFileConfigProvider(config)
237
+
238
+ const program = Effect.gen(function* () {
239
+ return yield* MdContextConfig
240
+ })
241
+
242
+ const result = await Effect.runPromise(
243
+ Effect.withConfigProvider(program, provider),
244
+ )
245
+
246
+ expect(result.embeddings.model).toBe('text-embedding-3-large')
247
+ expect(result.embeddings.batchSize).toBe(50)
248
+ expect(result.paths.cacheDir).toBe('/custom/cache')
249
+ })
250
+ })
251
+
252
+ describe('loadFileConfigProvider', () => {
253
+ it('should return empty provider when no config file exists', async () => {
254
+ const provider = await Effect.runPromise(loadFileConfigProvider(tempDir))
255
+
256
+ // Provider should exist but provide no overrides
257
+ const program = Effect.gen(function* () {
258
+ return yield* MdContextConfig
259
+ })
260
+
261
+ const result = await Effect.runPromise(
262
+ Effect.withConfigProvider(program, provider),
263
+ )
264
+
265
+ // All defaults should be used
266
+ expect(result.index.maxDepth).toBe(10)
267
+ })
268
+
269
+ it('should load config and create provider in one step', async () => {
270
+ const config = {
271
+ index: { maxDepth: 30 },
272
+ output: { debug: true },
273
+ }
274
+ fs.writeFileSync(
275
+ path.join(tempDir, 'mdcontext.config.json'),
276
+ JSON.stringify(config),
277
+ )
278
+
279
+ const provider = await Effect.runPromise(loadFileConfigProvider(tempDir))
280
+
281
+ const program = Effect.gen(function* () {
282
+ return yield* MdContextConfig
283
+ })
284
+
285
+ const result = await Effect.runPromise(
286
+ Effect.withConfigProvider(program, provider),
287
+ )
288
+
289
+ expect(result.index.maxDepth).toBe(30)
290
+ expect(result.output.debug).toBe(true)
291
+ })
292
+ })
293
+
294
+ describe('JavaScript/TypeScript config loading', () => {
295
+ it('should load .mjs config with default export', async () => {
296
+ const configPath = path.join(tempDir, 'mdcontext.config.mjs')
297
+ fs.writeFileSync(configPath, `export default { index: { maxDepth: 42 } }`)
298
+
299
+ const result = await Effect.runPromise(loadConfigFile(tempDir))
300
+ expect(result.found).toBe(true)
301
+ if (result.found) {
302
+ expect(result.config.index?.maxDepth).toBe(42)
303
+ }
304
+ })
305
+
306
+ it('should load .mjs config with named export', async () => {
307
+ const configPath = path.join(tempDir, 'mdcontext.config.mjs')
308
+ fs.writeFileSync(
309
+ configPath,
310
+ `export const config = { search: { defaultLimit: 50 } }`,
311
+ )
312
+
313
+ const result = await Effect.runPromise(loadConfigFile(tempDir))
314
+ expect(result.found).toBe(true)
315
+ if (result.found) {
316
+ expect(result.config.search?.defaultLimit).toBe(50)
317
+ }
318
+ })
319
+ })
320
+ })
@@ -0,0 +1,273 @@
1
+ /**
2
+ * File-based ConfigProvider
3
+ *
4
+ * Creates a custom ConfigProvider that reads from config files:
5
+ * - mdcontext.config.ts (TypeScript - dynamic import)
6
+ * - mdcontext.config.js (JavaScript - dynamic import)
7
+ * - mdcontext.config.json (JSON - file read)
8
+ * - .mdcontextrc (JSON - file read)
9
+ * - .mdcontextrc.json (JSON - file read)
10
+ *
11
+ * ## Usage
12
+ *
13
+ * ```typescript
14
+ * import { loadConfigFile, createFileConfigProvider } from './config/file-provider.js'
15
+ * import { Effect, ConfigProvider } from 'effect'
16
+ *
17
+ * // Load config and create provider
18
+ * const result = await loadConfigFile('/path/to/project')
19
+ * if (result.found) {
20
+ * const provider = createFileConfigProvider(result.config)
21
+ * // Use with Effect.withConfigProvider
22
+ * }
23
+ * ```
24
+ */
25
+
26
+ import * as fs from 'node:fs'
27
+ import * as path from 'node:path'
28
+ import { ConfigProvider, Effect } from 'effect'
29
+ import { ConfigError } from '../errors/index.js'
30
+ import type { PartialMdContextConfig } from './service.js'
31
+
32
+ // ============================================================================
33
+ // Types
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Supported config file names in order of precedence
38
+ */
39
+ export const CONFIG_FILE_NAMES = [
40
+ 'mdcontext.config.ts',
41
+ 'mdcontext.config.js',
42
+ 'mdcontext.config.mjs',
43
+ 'mdcontext.config.json',
44
+ '.mdcontextrc',
45
+ '.mdcontextrc.json',
46
+ ] as const
47
+
48
+ export type ConfigFileName = (typeof CONFIG_FILE_NAMES)[number]
49
+
50
+ /**
51
+ * Result of loading a config file
52
+ */
53
+ export type LoadConfigResult =
54
+ | { found: true; path: string; config: PartialMdContextConfig }
55
+ | { found: false; searched: string[] }
56
+
57
+ /**
58
+ * Config file format
59
+ */
60
+ export type ConfigFileFormat = 'ts' | 'js' | 'json'
61
+
62
+ // ============================================================================
63
+ // File Detection
64
+ // ============================================================================
65
+
66
+ /**
67
+ * Find a config file starting from the given directory.
68
+ * Searches up the directory tree until a config file is found or root is reached.
69
+ *
70
+ * @param startDir - Directory to start searching from
71
+ * @returns The path to the config file if found, or null
72
+ */
73
+ export const findConfigFile = (
74
+ startDir: string,
75
+ ): { path: string; format: ConfigFileFormat } | null => {
76
+ let currentDir = path.resolve(startDir)
77
+ const root = path.parse(currentDir).root
78
+
79
+ while (currentDir !== root) {
80
+ for (const fileName of CONFIG_FILE_NAMES) {
81
+ const configPath = path.join(currentDir, fileName)
82
+ if (fs.existsSync(configPath)) {
83
+ const format = getConfigFormat(fileName)
84
+ return { path: configPath, format }
85
+ }
86
+ }
87
+ const parentDir = path.dirname(currentDir)
88
+ if (parentDir === currentDir) break
89
+ currentDir = parentDir
90
+ }
91
+
92
+ return null
93
+ }
94
+
95
+ /**
96
+ * Get the format of a config file based on its name
97
+ */
98
+ const getConfigFormat = (fileName: string): ConfigFileFormat => {
99
+ if (fileName.endsWith('.ts')) return 'ts'
100
+ if (fileName.endsWith('.js') || fileName.endsWith('.mjs')) return 'js'
101
+ return 'json'
102
+ }
103
+
104
+ // ============================================================================
105
+ // File Loading
106
+ // ============================================================================
107
+
108
+ /**
109
+ * Load configuration from a JSON file
110
+ */
111
+ const loadJsonConfig = (
112
+ filePath: string,
113
+ ): Effect.Effect<PartialMdContextConfig, ConfigError> =>
114
+ Effect.try({
115
+ try: () => {
116
+ const content = fs.readFileSync(filePath, 'utf-8')
117
+ return JSON.parse(content) as PartialMdContextConfig
118
+ },
119
+ catch: (error) =>
120
+ new ConfigError({
121
+ field: 'configFile',
122
+ message: `Failed to load config from ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
123
+ cause: error,
124
+ }),
125
+ })
126
+
127
+ /**
128
+ * Load configuration from a JavaScript/TypeScript file using dynamic import
129
+ */
130
+ const loadJsConfig = (
131
+ filePath: string,
132
+ ): Effect.Effect<PartialMdContextConfig, ConfigError> =>
133
+ Effect.tryPromise({
134
+ try: async () => {
135
+ // Convert to file URL for dynamic import
136
+ const fileUrl = `file://${filePath}`
137
+ const module = await import(fileUrl)
138
+ // Support both default export and named 'config' export
139
+ const config = module.default ?? module.config
140
+ if (!config || typeof config !== 'object') {
141
+ throw new Error(
142
+ 'Config file must export a default object or named "config" export',
143
+ )
144
+ }
145
+ return config as PartialMdContextConfig
146
+ },
147
+ catch: (error) =>
148
+ new ConfigError({
149
+ field: 'configFile',
150
+ message: `Failed to load config from ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
151
+ cause: error,
152
+ }),
153
+ })
154
+
155
+ /**
156
+ * Load configuration from a file based on its format
157
+ */
158
+ export const loadConfigFromFile = (
159
+ filePath: string,
160
+ format: ConfigFileFormat,
161
+ ): Effect.Effect<PartialMdContextConfig, ConfigError> => {
162
+ switch (format) {
163
+ case 'json':
164
+ return loadJsonConfig(filePath)
165
+ case 'ts':
166
+ case 'js':
167
+ return loadJsConfig(filePath)
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Search for and load a config file starting from the given directory.
173
+ *
174
+ * @param startDir - Directory to start searching from
175
+ * @returns LoadConfigResult indicating whether a config was found
176
+ */
177
+ export const loadConfigFile = (
178
+ startDir: string,
179
+ ): Effect.Effect<LoadConfigResult, ConfigError> =>
180
+ Effect.gen(function* () {
181
+ const found = findConfigFile(startDir)
182
+
183
+ if (!found) {
184
+ return {
185
+ found: false,
186
+ searched: CONFIG_FILE_NAMES.map((name) => path.join(startDir, name)),
187
+ } as LoadConfigResult
188
+ }
189
+
190
+ const config = yield* loadConfigFromFile(found.path, found.format)
191
+
192
+ return {
193
+ found: true,
194
+ path: found.path,
195
+ config,
196
+ } as LoadConfigResult
197
+ })
198
+
199
+ /**
200
+ * Load a config file from a specific path (not searching up directories)
201
+ *
202
+ * @param configPath - Explicit path to the config file
203
+ * @returns The loaded configuration
204
+ */
205
+ export const loadConfigFromPath = (
206
+ configPath: string,
207
+ ): Effect.Effect<PartialMdContextConfig, ConfigError> =>
208
+ Effect.gen(function* () {
209
+ const resolvedPath = path.resolve(configPath)
210
+
211
+ if (!fs.existsSync(resolvedPath)) {
212
+ return yield* Effect.fail(
213
+ new ConfigError({
214
+ field: 'configFile',
215
+ message: `Config file not found: ${resolvedPath}`,
216
+ }),
217
+ )
218
+ }
219
+
220
+ const format = getConfigFormat(path.basename(configPath))
221
+ return yield* loadConfigFromFile(resolvedPath, format)
222
+ })
223
+
224
+ // ============================================================================
225
+ // ConfigProvider Creation
226
+ // ============================================================================
227
+
228
+ /**
229
+ * Create a ConfigProvider from a partial configuration object.
230
+ *
231
+ * The provider uses ConfigProvider.fromJson to map the nested config
232
+ * structure to Effect's config namespace.
233
+ *
234
+ * @param config - Partial configuration object
235
+ * @returns A ConfigProvider that provides the config values
236
+ */
237
+ export const createFileConfigProvider = (
238
+ config: PartialMdContextConfig,
239
+ ): ConfigProvider.ConfigProvider => ConfigProvider.fromJson(config)
240
+
241
+ /**
242
+ * Load config file and create a ConfigProvider in one step.
243
+ *
244
+ * If no config file is found, returns a ConfigProvider that provides no values
245
+ * (Effect will use defaults from the Config schema).
246
+ *
247
+ * @param startDir - Directory to start searching for config files
248
+ * @returns Effect yielding a ConfigProvider
249
+ */
250
+ export const loadFileConfigProvider = (
251
+ startDir: string,
252
+ ): Effect.Effect<ConfigProvider.ConfigProvider, ConfigError> =>
253
+ Effect.gen(function* () {
254
+ const result = yield* loadConfigFile(startDir)
255
+
256
+ if (!result.found) {
257
+ // Return empty provider - defaults will be used
258
+ return ConfigProvider.fromMap(new Map())
259
+ }
260
+
261
+ return createFileConfigProvider(result.config)
262
+ })
263
+
264
+ /**
265
+ * Load config from a specific file and create a ConfigProvider.
266
+ *
267
+ * @param configPath - Path to the config file
268
+ * @returns Effect yielding a ConfigProvider
269
+ */
270
+ export const loadFileConfigProviderFromPath = (
271
+ configPath: string,
272
+ ): Effect.Effect<ConfigProvider.ConfigProvider, ConfigError> =>
273
+ Effect.map(loadConfigFromPath(configPath), createFileConfigProvider)
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Configuration Module
3
+ *
4
+ * Exports all configuration-related types, schemas, and utilities.
5
+ */
6
+
7
+ export {
8
+ // File detection
9
+ CONFIG_FILE_NAMES,
10
+ type ConfigFileFormat,
11
+ type ConfigFileName,
12
+ // Provider creation
13
+ createFileConfigProvider,
14
+ findConfigFile,
15
+ type LoadConfigResult,
16
+ // File loading
17
+ loadConfigFile,
18
+ loadConfigFromPath,
19
+ loadFileConfigProvider,
20
+ loadFileConfigProviderFromPath,
21
+ } from './file-provider.js'
22
+ export {
23
+ // Precedence chain
24
+ type ConfigProviderOptions,
25
+ createCliConfigProvider,
26
+ createConfigProvider,
27
+ createConfigProviderSync,
28
+ createEnvConfigProvider,
29
+ createTestConfigProvider,
30
+ // Utilities
31
+ flattenConfig,
32
+ readEnvConfig,
33
+ } from './precedence.js'
34
+ export {
35
+ // Default values
36
+ defaultConfig,
37
+ // Types
38
+ type EmbeddingProvider,
39
+ EmbeddingsConfig,
40
+ // Config schemas
41
+ IndexConfig,
42
+ MdContextConfig,
43
+ type OpenAIEmbeddingModel,
44
+ OutputConfig,
45
+ type OutputFormat,
46
+ PathsConfig,
47
+ SearchConfig,
48
+ SummarizationConfig,
49
+ } from './schema.js'
50
+ export {
51
+ // Service
52
+ ConfigService,
53
+ ConfigServiceDefault,
54
+ ConfigServiceLive,
55
+ // Helper functions
56
+ getConfig,
57
+ getConfigSection,
58
+ getConfigValue,
59
+ // Layer utilities
60
+ makeConfigLayer,
61
+ makeConfigLayerPartial,
62
+ // Merge utilities
63
+ mergeWithDefaults,
64
+ type PartialMdContextConfig,
65
+ } from './service.js'
66
+ export {
67
+ // Testing utilities
68
+ runWithConfig,
69
+ runWithConfigSync,
70
+ TestConfigLayer,
71
+ withTestConfig,
72
+ } from './testing.js'