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
+ * ConfigService Unit Tests
3
+ *
4
+ * Comprehensive tests for the Effect-based ConfigService layer and related
5
+ * utilities. This suite verifies how configuration is loaded, merged, and
6
+ * accessed across different Effect layers.
7
+ *
8
+ * Patterns under test:
9
+ * - Default configuration via `ConfigServiceDefault`
10
+ * - Live configuration via `ConfigServiceLive` combined with `ConfigProvider`
11
+ * - Creating ad-hoc layers with `makeConfigLayer` and `makeConfigLayerPartial`
12
+ * - Merging user-provided values with `defaultConfig` using `mergeWithDefaults`
13
+ * - Reading configuration through the `ConfigService` tag and helpers
14
+ * (`getConfig`, `getConfigSection`, `getConfigValue`)
15
+ *
16
+ * Key scenarios:
17
+ * - Ensuring all top-level sections (index, search, embeddings, output, paths)
18
+ * receive correct default values when no overrides are provided
19
+ * - Overriding selected values via `ConfigProvider.fromMap` while preserving
20
+ * defaults for unspecified keys
21
+ * - Providing fully-specified configs and partially-specified configs and
22
+ * verifying correct fallback/merge behavior
23
+ * - Validating lookup helpers return the expected values and handle missing
24
+ * keys using Effect/Option semantics
25
+ *
26
+ * Test organization and setup:
27
+ * - Uses Vitest (`describe`/`it`) with Effect programs composed via
28
+ * `Effect.gen` and executed through `Effect.runPromise`
29
+ * - Layers and config providers are attached on a per-test basis using
30
+ * `Effect.provide` and `Effect.withConfigProvider`, so tests are isolated
31
+ * and do not share state between runs
32
+ */
33
+
34
+ import { ConfigProvider, Effect, Option } from 'effect'
35
+ import { describe, expect, it } from 'vitest'
36
+ import { defaultConfig } from './schema.js'
37
+ import {
38
+ ConfigService,
39
+ ConfigServiceDefault,
40
+ ConfigServiceLive,
41
+ getConfig,
42
+ getConfigSection,
43
+ getConfigValue,
44
+ makeConfigLayer,
45
+ makeConfigLayerPartial,
46
+ mergeWithDefaults,
47
+ } from './service.js'
48
+
49
+ describe('ConfigService', () => {
50
+ describe('ConfigServiceDefault', () => {
51
+ it('should provide default configuration values', async () => {
52
+ const program = Effect.gen(function* () {
53
+ return yield* ConfigService
54
+ })
55
+
56
+ const result = await Effect.runPromise(
57
+ program.pipe(Effect.provide(ConfigServiceDefault)),
58
+ )
59
+
60
+ expect(result.index.maxDepth).toBe(10)
61
+ expect(result.search.defaultLimit).toBe(10)
62
+ expect(result.embeddings.provider).toBe('openai')
63
+ expect(result.output.format).toBe('text')
64
+ expect(result.paths.cacheDir).toBe('.mdcontext/cache')
65
+ })
66
+ })
67
+
68
+ describe('ConfigServiceLive', () => {
69
+ it('should load configuration from ConfigProvider', async () => {
70
+ const program = Effect.gen(function* () {
71
+ return yield* ConfigService
72
+ })
73
+
74
+ const provider = ConfigProvider.fromMap(
75
+ new Map([
76
+ ['index.maxDepth', '20'],
77
+ ['search.defaultLimit', '25'],
78
+ ]),
79
+ )
80
+
81
+ const result = await Effect.runPromise(
82
+ program.pipe(
83
+ Effect.provide(ConfigServiceLive),
84
+ Effect.withConfigProvider(provider),
85
+ ),
86
+ )
87
+
88
+ expect(result.index.maxDepth).toBe(20)
89
+ expect(result.search.defaultLimit).toBe(25)
90
+ // Other values should use defaults
91
+ expect(result.embeddings.provider).toBe('openai')
92
+ })
93
+ })
94
+
95
+ describe('makeConfigLayer', () => {
96
+ it('should create a layer with the provided config', async () => {
97
+ const customConfig = {
98
+ ...defaultConfig,
99
+ index: { ...defaultConfig.index, maxDepth: 5 },
100
+ }
101
+
102
+ const program = Effect.gen(function* () {
103
+ return yield* ConfigService
104
+ })
105
+
106
+ const result = await Effect.runPromise(
107
+ program.pipe(Effect.provide(makeConfigLayer(customConfig))),
108
+ )
109
+
110
+ expect(result.index.maxDepth).toBe(5)
111
+ expect(result.search.defaultLimit).toBe(10) // unchanged
112
+ })
113
+ })
114
+
115
+ describe('makeConfigLayerPartial', () => {
116
+ it('should merge partial config with defaults', async () => {
117
+ const program = Effect.gen(function* () {
118
+ return yield* ConfigService
119
+ })
120
+
121
+ const result = await Effect.runPromise(
122
+ program.pipe(
123
+ Effect.provide(
124
+ makeConfigLayerPartial({
125
+ index: { maxDepth: 3 },
126
+ output: { verbose: true },
127
+ }),
128
+ ),
129
+ ),
130
+ )
131
+
132
+ expect(result.index.maxDepth).toBe(3)
133
+ expect(result.index.excludePatterns).toEqual([
134
+ 'node_modules',
135
+ '.git',
136
+ 'dist',
137
+ 'build',
138
+ ])
139
+ expect(result.output.verbose).toBe(true)
140
+ expect(result.output.format).toBe('text')
141
+ })
142
+ })
143
+
144
+ describe('getConfig', () => {
145
+ it('should return the full configuration', async () => {
146
+ const program = Effect.gen(function* () {
147
+ return yield* getConfig
148
+ })
149
+
150
+ const result = await Effect.runPromise(
151
+ program.pipe(Effect.provide(ConfigServiceDefault)),
152
+ )
153
+
154
+ expect(result).toEqual(defaultConfig)
155
+ })
156
+ })
157
+
158
+ describe('getConfigSection', () => {
159
+ it('should return a specific config section', async () => {
160
+ const program = Effect.gen(function* () {
161
+ return yield* getConfigSection('index')
162
+ })
163
+
164
+ const result = await Effect.runPromise(
165
+ program.pipe(Effect.provide(ConfigServiceDefault)),
166
+ )
167
+
168
+ expect(result.maxDepth).toBe(10)
169
+ expect(result.excludePatterns).toEqual([
170
+ 'node_modules',
171
+ '.git',
172
+ 'dist',
173
+ 'build',
174
+ ])
175
+ })
176
+
177
+ it('should work for all sections', async () => {
178
+ const getAll = Effect.gen(function* () {
179
+ const index = yield* getConfigSection('index')
180
+ const search = yield* getConfigSection('search')
181
+ const embeddings = yield* getConfigSection('embeddings')
182
+ const output = yield* getConfigSection('output')
183
+ const paths = yield* getConfigSection('paths')
184
+ return { index, search, embeddings, output, paths }
185
+ })
186
+
187
+ const result = await Effect.runPromise(
188
+ getAll.pipe(Effect.provide(ConfigServiceDefault)),
189
+ )
190
+
191
+ expect(result.index.maxDepth).toBe(10)
192
+ expect(result.search.defaultLimit).toBe(10)
193
+ expect(result.embeddings.provider).toBe('openai')
194
+ expect(result.output.format).toBe('text')
195
+ expect(result.paths.cacheDir).toBe('.mdcontext/cache')
196
+ })
197
+ })
198
+
199
+ describe('getConfigValue', () => {
200
+ it('should return a specific config value', async () => {
201
+ const program = Effect.gen(function* () {
202
+ return yield* getConfigValue('index', 'maxDepth')
203
+ })
204
+
205
+ const result = await Effect.runPromise(
206
+ program.pipe(Effect.provide(ConfigServiceDefault)),
207
+ )
208
+
209
+ expect(result).toBe(10)
210
+ })
211
+
212
+ it('should work with nested values', async () => {
213
+ const program = Effect.gen(function* () {
214
+ const maxDepth = yield* getConfigValue('index', 'maxDepth')
215
+ const format = yield* getConfigValue('output', 'format')
216
+ const provider = yield* getConfigValue('embeddings', 'provider')
217
+ return { maxDepth, format, provider }
218
+ })
219
+
220
+ const result = await Effect.runPromise(
221
+ program.pipe(Effect.provide(ConfigServiceDefault)),
222
+ )
223
+
224
+ expect(result.maxDepth).toBe(10)
225
+ expect(result.format).toBe('text')
226
+ expect(result.provider).toBe('openai')
227
+ })
228
+ })
229
+
230
+ describe('mergeWithDefaults', () => {
231
+ it('should merge empty partial with defaults', () => {
232
+ const result = mergeWithDefaults({})
233
+ expect(result).toEqual(defaultConfig)
234
+ })
235
+
236
+ it('should override specific values', () => {
237
+ const result = mergeWithDefaults({
238
+ index: { maxDepth: 5 },
239
+ })
240
+
241
+ expect(result.index.maxDepth).toBe(5)
242
+ expect(result.index.excludePatterns).toEqual([
243
+ 'node_modules',
244
+ '.git',
245
+ 'dist',
246
+ 'build',
247
+ ])
248
+ })
249
+
250
+ it('should merge multiple sections', () => {
251
+ const result = mergeWithDefaults({
252
+ index: { maxDepth: 5, followSymlinks: true },
253
+ search: { defaultLimit: 20 },
254
+ output: { verbose: true, debug: true },
255
+ })
256
+
257
+ expect(result.index.maxDepth).toBe(5)
258
+ expect(result.index.followSymlinks).toBe(true)
259
+ expect(result.index.excludePatterns).toEqual([
260
+ 'node_modules',
261
+ '.git',
262
+ 'dist',
263
+ 'build',
264
+ ])
265
+ expect(result.search.defaultLimit).toBe(20)
266
+ expect(result.search.maxLimit).toBe(100)
267
+ expect(result.output.verbose).toBe(true)
268
+ expect(result.output.debug).toBe(true)
269
+ expect(result.output.format).toBe('text')
270
+ })
271
+
272
+ it('should preserve Option values from defaults', () => {
273
+ const result = mergeWithDefaults({
274
+ paths: { cacheDir: '/custom/cache' },
275
+ })
276
+
277
+ // Verify the merge preserves defaults for unspecified values
278
+ expect(Option.isNone(result.paths.root)).toBe(true)
279
+ expect(Option.isNone(result.paths.configFile)).toBe(true)
280
+ expect(result.paths.cacheDir).toBe('/custom/cache')
281
+ })
282
+ })
283
+
284
+ describe('service composition', () => {
285
+ it('should allow services to depend on ConfigService', async () => {
286
+ // Example of a service that uses config
287
+ const myService = Effect.gen(function* () {
288
+ const config = yield* ConfigService
289
+ return `Max depth is ${config.index.maxDepth}`
290
+ })
291
+
292
+ const result = await Effect.runPromise(
293
+ myService.pipe(Effect.provide(ConfigServiceDefault)),
294
+ )
295
+
296
+ expect(result).toBe('Max depth is 10')
297
+ })
298
+
299
+ it('should allow testing with different configs', async () => {
300
+ const myService = Effect.gen(function* () {
301
+ const config = yield* ConfigService
302
+ return config.index.maxDepth > 5 ? 'deep' : 'shallow'
303
+ })
304
+
305
+ // Test with default (maxDepth = 10)
306
+ const defaultResult = await Effect.runPromise(
307
+ myService.pipe(Effect.provide(ConfigServiceDefault)),
308
+ )
309
+ expect(defaultResult).toBe('deep')
310
+
311
+ // Test with custom config (maxDepth = 3)
312
+ const customResult = await Effect.runPromise(
313
+ myService.pipe(
314
+ Effect.provide(makeConfigLayerPartial({ index: { maxDepth: 3 } })),
315
+ ),
316
+ )
317
+ expect(customResult).toBe('shallow')
318
+ })
319
+ })
320
+ })
@@ -0,0 +1,243 @@
1
+ /**
2
+ * ConfigService Layer
3
+ *
4
+ * Wraps configuration in an Effect Context.Tag and Layer, enabling
5
+ * dependency injection throughout the application. Services access
6
+ * config via `yield* ConfigService` rather than direct function parameters.
7
+ *
8
+ * ## Benefits
9
+ *
10
+ * - Test isolation without mocking (just provide different Layer)
11
+ * - Consistent config access across all commands and services
12
+ * - Effect best practices for dependency management
13
+ * - Type-safe configuration access
14
+ *
15
+ * ## Usage
16
+ *
17
+ * ```typescript
18
+ * import { ConfigService, ConfigServiceLive } from './config/service.js'
19
+ * import { Effect, Layer } from 'effect'
20
+ *
21
+ * const program = Effect.gen(function* () {
22
+ * const config = yield* ConfigService
23
+ * console.log(`Max depth: ${config.index.maxDepth}`)
24
+ * })
25
+ *
26
+ * // Run with live config
27
+ * Effect.runPromise(program.pipe(Effect.provide(ConfigServiceLive)))
28
+ * ```
29
+ */
30
+
31
+ import { type ConfigError, Context, Effect, Layer } from 'effect'
32
+ import type { MdContextConfig } from './schema.js'
33
+ import {
34
+ defaultConfig,
35
+ MdContextConfig as MdContextConfigSchema,
36
+ } from './schema.js'
37
+
38
+ // ============================================================================
39
+ // Service Definition
40
+ // ============================================================================
41
+
42
+ /**
43
+ * ConfigService provides access to the application configuration.
44
+ *
45
+ * Use this service to access configuration values throughout the application.
46
+ * The service is provided via a Layer, enabling different configurations
47
+ * for production, testing, and development environments.
48
+ */
49
+ export class ConfigService extends Context.Tag('ConfigService')<
50
+ ConfigService,
51
+ MdContextConfig
52
+ >() {}
53
+
54
+ // ============================================================================
55
+ // Layer Implementations
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Live ConfigService layer that loads configuration from the Effect
60
+ * ConfigProvider (environment variables, config files, etc.)
61
+ *
62
+ * This layer reads from the current ConfigProvider in the Effect context.
63
+ * By default, this is the environment, but it can be customized using
64
+ * Effect.withConfigProvider.
65
+ *
66
+ * Note: This layer may fail with ConfigError if required configuration
67
+ * is missing or invalid. Use ConfigServiceDefault for a guaranteed-success layer.
68
+ */
69
+ export const ConfigServiceLive: Layer.Layer<
70
+ ConfigService,
71
+ ConfigError.ConfigError
72
+ > = Layer.effect(ConfigService, MdContextConfigSchema)
73
+
74
+ /**
75
+ * Create a ConfigService layer with a custom configuration object.
76
+ *
77
+ * Useful for:
78
+ * - Testing with specific config values
79
+ * - CLI flag overrides
80
+ * - Programmatic configuration
81
+ *
82
+ * @param config - The configuration object to use
83
+ * @returns A Layer that provides the ConfigService with the given config
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const testConfig = {
88
+ * ...defaultConfig,
89
+ * index: { ...defaultConfig.index, maxDepth: 5 }
90
+ * }
91
+ * const TestConfigLayer = makeConfigLayer(testConfig)
92
+ *
93
+ * const program = Effect.gen(function* () {
94
+ * const config = yield* ConfigService
95
+ * console.log(config.index.maxDepth) // 5
96
+ * })
97
+ *
98
+ * Effect.runPromise(program.pipe(Effect.provide(TestConfigLayer)))
99
+ * ```
100
+ */
101
+ export const makeConfigLayer = (
102
+ config: MdContextConfig,
103
+ ): Layer.Layer<ConfigService> => Layer.succeed(ConfigService, config)
104
+
105
+ /**
106
+ * Default ConfigService layer with all default values.
107
+ *
108
+ * Useful for:
109
+ * - Quick testing without external dependencies
110
+ * - Fallback when no config file or environment is available
111
+ */
112
+ export const ConfigServiceDefault: Layer.Layer<ConfigService> =
113
+ makeConfigLayer(defaultConfig)
114
+
115
+ // ============================================================================
116
+ // Helper Functions
117
+ // ============================================================================
118
+
119
+ /**
120
+ * Access the full configuration object.
121
+ *
122
+ * @returns An Effect that yields the full MdContextConfig
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const program = Effect.gen(function* () {
127
+ * const config = yield* getConfig
128
+ * console.log(config.index.maxDepth)
129
+ * })
130
+ * ```
131
+ */
132
+ export const getConfig: Effect.Effect<MdContextConfig, never, ConfigService> =
133
+ ConfigService
134
+
135
+ /**
136
+ * Access a specific section of the configuration.
137
+ *
138
+ * @param section - The section key to access
139
+ * @returns An Effect that yields the specified config section
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const program = Effect.gen(function* () {
144
+ * const indexConfig = yield* getConfigSection('index')
145
+ * console.log(indexConfig.maxDepth)
146
+ * })
147
+ * ```
148
+ */
149
+ export const getConfigSection = <K extends keyof MdContextConfig>(
150
+ section: K,
151
+ ): Effect.Effect<MdContextConfig[K], never, ConfigService> =>
152
+ Effect.map(ConfigService, (config) => config[section])
153
+
154
+ /**
155
+ * Access a specific value from the configuration.
156
+ *
157
+ * @param section - The section key
158
+ * @param key - The key within the section
159
+ * @returns An Effect that yields the specified config value
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const program = Effect.gen(function* () {
164
+ * const maxDepth = yield* getConfigValue('index', 'maxDepth')
165
+ * console.log(maxDepth)
166
+ * })
167
+ * ```
168
+ */
169
+ export const getConfigValue = <
170
+ K extends keyof MdContextConfig,
171
+ V extends keyof MdContextConfig[K],
172
+ >(
173
+ section: K,
174
+ key: V,
175
+ ): Effect.Effect<MdContextConfig[K][V], never, ConfigService> =>
176
+ Effect.map(ConfigService, (config) => config[section][key])
177
+
178
+ // ============================================================================
179
+ // Partial Config Utilities
180
+ // ============================================================================
181
+
182
+ /**
183
+ * Deeply partial type for MdContextConfig
184
+ */
185
+ export type PartialMdContextConfig = {
186
+ [K in keyof MdContextConfig]?: Partial<MdContextConfig[K]>
187
+ }
188
+
189
+ /**
190
+ * Merge partial configuration with defaults.
191
+ *
192
+ * Creates a complete MdContextConfig by merging user-provided values
193
+ * with the default configuration. Useful for applying config file values
194
+ * or CLI overrides.
195
+ *
196
+ * @param partial - Partial configuration to merge
197
+ * @returns Complete MdContextConfig with defaults filled in
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const userConfig = {
202
+ * index: { maxDepth: 5 },
203
+ * output: { verbose: true }
204
+ * }
205
+ * const fullConfig = mergeWithDefaults(userConfig)
206
+ * // fullConfig.index.maxDepth === 5
207
+ * // fullConfig.index.excludePatterns === defaultConfig.index.excludePatterns
208
+ * ```
209
+ */
210
+ export const mergeWithDefaults = (
211
+ partial: PartialMdContextConfig,
212
+ ): MdContextConfig => ({
213
+ index: { ...defaultConfig.index, ...partial.index },
214
+ search: { ...defaultConfig.search, ...partial.search },
215
+ embeddings: { ...defaultConfig.embeddings, ...partial.embeddings },
216
+ summarization: { ...defaultConfig.summarization, ...partial.summarization },
217
+ aiSummarization: {
218
+ ...defaultConfig.aiSummarization,
219
+ ...partial.aiSummarization,
220
+ },
221
+ output: { ...defaultConfig.output, ...partial.output },
222
+ paths: { ...defaultConfig.paths, ...partial.paths },
223
+ })
224
+
225
+ /**
226
+ * Create a ConfigService layer from partial configuration.
227
+ *
228
+ * Combines makeConfigLayer with mergeWithDefaults for convenience.
229
+ *
230
+ * @param partial - Partial configuration to use
231
+ * @returns A Layer that provides ConfigService with merged config
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * const TestLayer = makeConfigLayerPartial({
236
+ * index: { maxDepth: 5 },
237
+ * output: { debug: true }
238
+ * })
239
+ * ```
240
+ */
241
+ export const makeConfigLayerPartial = (
242
+ partial: PartialMdContextConfig,
243
+ ): Layer.Layer<ConfigService> => makeConfigLayer(mergeWithDefaults(partial))