mdcontext 0.1.0 → 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 (251) hide show
  1. package/.changeset/config.json +9 -9
  2. package/.claude/settings.local.json +25 -0
  3. package/.github/workflows/claude-code-review.yml +44 -0
  4. package/.github/workflows/claude.yml +85 -0
  5. package/CONTRIBUTING.md +186 -0
  6. package/NOTES/NOTES +44 -0
  7. package/README.md +206 -3
  8. package/biome.json +1 -1
  9. package/dist/chunk-23UPXDNL.js +3044 -0
  10. package/dist/chunk-2W7MO2DL.js +1366 -0
  11. package/dist/chunk-3NUAZGMA.js +1689 -0
  12. package/dist/chunk-7TOWB2XB.js +366 -0
  13. package/dist/chunk-7XOTOADQ.js +3065 -0
  14. package/dist/chunk-AH2PDM2K.js +3042 -0
  15. package/dist/chunk-BNXWSZ63.js +3742 -0
  16. package/dist/chunk-BTL5DJVU.js +3222 -0
  17. package/dist/chunk-HDHYG7E4.js +104 -0
  18. package/dist/chunk-HLR4KZBP.js +3234 -0
  19. package/dist/chunk-IP3FRFEB.js +1045 -0
  20. package/dist/chunk-KHU56VDO.js +3042 -0
  21. package/dist/chunk-KRYIFLQR.js +85 -89
  22. package/dist/chunk-LBSDNLEM.js +287 -0
  23. package/dist/chunk-MNTQ7HCP.js +2643 -0
  24. package/dist/chunk-MUJELQQ6.js +1387 -0
  25. package/dist/chunk-MXJGMSLV.js +2199 -0
  26. package/dist/chunk-N6QJGC3Z.js +2636 -0
  27. package/dist/chunk-OBELGBPM.js +1713 -0
  28. package/dist/chunk-OT7R5XTA.js +3192 -0
  29. package/dist/chunk-P7X4RA2T.js +106 -0
  30. package/dist/chunk-PIDUQNC2.js +3185 -0
  31. package/dist/chunk-POGCDIH4.js +3187 -0
  32. package/dist/chunk-PSIEOQGZ.js +3043 -0
  33. package/dist/chunk-PVRT3IHA.js +3238 -0
  34. package/dist/chunk-QNN4TT23.js +1430 -0
  35. package/dist/chunk-RE3R45RJ.js +3042 -0
  36. package/dist/chunk-S7E6TFX6.js +718 -657
  37. package/dist/chunk-SG6GLU4U.js +1378 -0
  38. package/dist/chunk-SJCDV2ST.js +274 -0
  39. package/dist/chunk-SYE5XLF3.js +104 -0
  40. package/dist/chunk-T5VLYBZD.js +103 -0
  41. package/dist/chunk-TOQB7VWU.js +3238 -0
  42. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  43. package/dist/chunk-VVTGZNBT.js +1533 -1423
  44. package/dist/chunk-W7Q4RFEV.js +104 -0
  45. package/dist/chunk-XTYYVRLO.js +3190 -0
  46. package/dist/chunk-Y6MDYVJD.js +3063 -0
  47. package/dist/cli/main.js +4072 -629
  48. package/dist/index.d.ts +420 -33
  49. package/dist/index.js +8 -15
  50. package/dist/mcp/server.js +103 -7
  51. package/dist/schema-BAWSG7KY.js +22 -0
  52. package/dist/schema-E3QUPL26.js +20 -0
  53. package/dist/schema-EHL7WUT6.js +20 -0
  54. package/docs/019-USAGE.md +44 -5
  55. package/docs/020-current-implementation.md +8 -8
  56. package/docs/021-DOGFOODING-FINDINGS.md +1 -1
  57. package/docs/CONFIG.md +1123 -0
  58. package/docs/ERRORS.md +383 -0
  59. package/docs/summarization.md +320 -0
  60. package/justfile +40 -0
  61. package/package.json +39 -33
  62. package/research/INDEX.md +315 -0
  63. package/research/code-review/README.md +90 -0
  64. package/research/code-review/cli-error-handling-review.md +979 -0
  65. package/research/code-review/code-review-validation-report.md +464 -0
  66. package/research/code-review/main-ts-review.md +1128 -0
  67. package/research/config-docs/SUMMARY.md +357 -0
  68. package/research/config-docs/TEST-RESULTS.md +776 -0
  69. package/research/config-docs/TODO.md +542 -0
  70. package/research/config-docs/analysis.md +744 -0
  71. package/research/config-docs/fix-validation.md +502 -0
  72. package/research/config-docs/help-audit.md +264 -0
  73. package/research/config-docs/help-system-analysis.md +890 -0
  74. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  75. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  76. package/research/issue-review.md +603 -0
  77. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  78. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  79. package/research/llm-summarization/anthropic-2026.md +367 -0
  80. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  81. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  82. package/research/llm-summarization/openai-2026.md +473 -0
  83. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  84. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  85. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  86. package/research/llm-summarization/prototype-results.md +56 -0
  87. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  88. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  89. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  90. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  91. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  92. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  93. package/research/mdcontext-pudding/02-search.md +970 -0
  94. package/research/mdcontext-pudding/03-context.md +779 -0
  95. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  96. package/research/mdcontext-pudding/04-tree.md +704 -0
  97. package/research/mdcontext-pudding/05-config.md +1038 -0
  98. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  99. package/research/mdcontext-pudding/06-links.md +679 -0
  100. package/research/mdcontext-pudding/07-stats.md +693 -0
  101. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  102. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  103. package/research/mdcontext-pudding/README.md +168 -0
  104. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  105. package/research/research-quality-review.md +834 -0
  106. package/research/semantic-search/embedding-text-analysis.md +156 -0
  107. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  108. package/research/semantic-search/query-processing-analysis.md +207 -0
  109. package/research/semantic-search/root-cause-and-solution.md +114 -0
  110. package/research/semantic-search/threshold-validation-report.md +69 -0
  111. package/research/semantic-search/vector-search-analysis.md +63 -0
  112. package/research/test-path-issues.md +276 -0
  113. package/review/ALP-76/1-error-type-design.md +962 -0
  114. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  115. package/review/ALP-76/3-error-presentation.md +624 -0
  116. package/review/ALP-76/4-test-coverage.md +625 -0
  117. package/review/ALP-76/5-migration-completeness.md +440 -0
  118. package/review/ALP-76/6-effect-best-practices.md +755 -0
  119. package/scripts/apply-branch-protection.sh +47 -0
  120. package/scripts/branch-protection-templates.json +79 -0
  121. package/scripts/prototype-summarization.ts +346 -0
  122. package/scripts/rebuild-hnswlib.js +32 -37
  123. package/scripts/setup-branch-protection.sh +64 -0
  124. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  125. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  126. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  127. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  128. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  129. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  130. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  131. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  132. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  133. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  134. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  135. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  136. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  137. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  138. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  139. package/src/cli/argv-preprocessor.test.ts +2 -2
  140. package/src/cli/cli.test.ts +230 -33
  141. package/src/cli/commands/config-cmd.ts +642 -0
  142. package/src/cli/commands/context.ts +97 -9
  143. package/src/cli/commands/duplicates.ts +122 -0
  144. package/src/cli/commands/embeddings.ts +529 -0
  145. package/src/cli/commands/index-cmd.ts +210 -30
  146. package/src/cli/commands/index.ts +3 -0
  147. package/src/cli/commands/search.ts +894 -64
  148. package/src/cli/commands/stats.ts +3 -0
  149. package/src/cli/commands/tree.ts +26 -5
  150. package/src/cli/config-layer.ts +176 -0
  151. package/src/cli/error-handler.test.ts +235 -0
  152. package/src/cli/error-handler.ts +655 -0
  153. package/src/cli/flag-schemas.ts +66 -0
  154. package/src/cli/help.ts +209 -7
  155. package/src/cli/main.ts +348 -58
  156. package/src/cli/options.ts +10 -0
  157. package/src/cli/shared-error-handling.ts +199 -0
  158. package/src/cli/utils.ts +150 -17
  159. package/src/config/file-provider.test.ts +320 -0
  160. package/src/config/file-provider.ts +273 -0
  161. package/src/config/index.ts +72 -0
  162. package/src/config/integration.test.ts +667 -0
  163. package/src/config/precedence.test.ts +277 -0
  164. package/src/config/precedence.ts +451 -0
  165. package/src/config/schema.test.ts +414 -0
  166. package/src/config/schema.ts +603 -0
  167. package/src/config/service.test.ts +320 -0
  168. package/src/config/service.ts +243 -0
  169. package/src/config/testing.test.ts +264 -0
  170. package/src/config/testing.ts +110 -0
  171. package/src/core/types.ts +6 -33
  172. package/src/duplicates/detector.test.ts +183 -0
  173. package/src/duplicates/detector.ts +414 -0
  174. package/src/duplicates/index.ts +18 -0
  175. package/src/embeddings/embedding-namespace.test.ts +300 -0
  176. package/src/embeddings/embedding-namespace.ts +947 -0
  177. package/src/embeddings/heading-boost.test.ts +222 -0
  178. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  179. package/src/embeddings/hyde.test.ts +272 -0
  180. package/src/embeddings/hyde.ts +264 -0
  181. package/src/embeddings/index.ts +2 -0
  182. package/src/embeddings/openai-provider.ts +332 -83
  183. package/src/embeddings/pricing.json +22 -0
  184. package/src/embeddings/provider-constants.ts +204 -0
  185. package/src/embeddings/provider-errors.test.ts +967 -0
  186. package/src/embeddings/provider-errors.ts +565 -0
  187. package/src/embeddings/provider-factory.test.ts +240 -0
  188. package/src/embeddings/provider-factory.ts +225 -0
  189. package/src/embeddings/provider-integration.test.ts +788 -0
  190. package/src/embeddings/query-preprocessing.test.ts +187 -0
  191. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  192. package/src/embeddings/semantic-search.ts +780 -93
  193. package/src/embeddings/types.ts +293 -16
  194. package/src/embeddings/vector-store.ts +486 -77
  195. package/src/embeddings/voyage-provider.ts +313 -0
  196. package/src/errors/errors.test.ts +845 -0
  197. package/src/errors/index.ts +533 -0
  198. package/src/index/ignore-patterns.test.ts +354 -0
  199. package/src/index/ignore-patterns.ts +305 -0
  200. package/src/index/indexer.ts +286 -48
  201. package/src/index/storage.ts +94 -30
  202. package/src/index/types.ts +40 -2
  203. package/src/index/watcher.ts +67 -9
  204. package/src/index.ts +22 -0
  205. package/src/integration/search-keyword.test.ts +678 -0
  206. package/src/mcp/server.ts +135 -6
  207. package/src/parser/parser.ts +18 -19
  208. package/src/parser/section-filter.test.ts +277 -0
  209. package/src/parser/section-filter.ts +125 -3
  210. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  211. package/src/search/bm25-store.ts +366 -0
  212. package/src/search/cross-encoder.test.ts +253 -0
  213. package/src/search/cross-encoder.ts +406 -0
  214. package/src/search/fuzzy-search.test.ts +419 -0
  215. package/src/search/fuzzy-search.ts +273 -0
  216. package/src/search/hybrid-search.ts +448 -0
  217. package/src/search/path-matcher.test.ts +276 -0
  218. package/src/search/path-matcher.ts +33 -0
  219. package/src/search/searcher.test.ts +99 -1
  220. package/src/search/searcher.ts +189 -67
  221. package/src/search/wink-bm25.d.ts +30 -0
  222. package/src/summarization/cli-providers/claude.ts +202 -0
  223. package/src/summarization/cli-providers/detection.test.ts +273 -0
  224. package/src/summarization/cli-providers/detection.ts +118 -0
  225. package/src/summarization/cli-providers/index.ts +8 -0
  226. package/src/summarization/cost.test.ts +139 -0
  227. package/src/summarization/cost.ts +102 -0
  228. package/src/summarization/error-handler.test.ts +127 -0
  229. package/src/summarization/error-handler.ts +111 -0
  230. package/src/summarization/index.ts +102 -0
  231. package/src/summarization/pipeline.test.ts +498 -0
  232. package/src/summarization/pipeline.ts +231 -0
  233. package/src/summarization/prompts.test.ts +269 -0
  234. package/src/summarization/prompts.ts +133 -0
  235. package/src/summarization/provider-factory.test.ts +396 -0
  236. package/src/summarization/provider-factory.ts +178 -0
  237. package/src/summarization/types.ts +184 -0
  238. package/src/summarize/summarizer.ts +104 -35
  239. package/src/types/huggingface-transformers.d.ts +66 -0
  240. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  241. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  242. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  243. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +4 -4
  244. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -0
  245. package/tests/integration/embed-index.test.ts +712 -0
  246. package/tests/integration/search-context.test.ts +469 -0
  247. package/tests/integration/search-semantic.test.ts +522 -0
  248. package/vitest.config.ts +1 -6
  249. package/AGENTS.md +0 -46
  250. package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
  251. package/tests/fixtures/cli/.mdcontext/vectors.meta.json +0 -1264
@@ -177,48 +177,66 @@ describe.concurrent('mdcontext CLI e2e', () => {
177
177
  expect(output).toContain('Results:')
178
178
  })
179
179
 
180
- it.skipIf(!INCLUDE_EMBED_TESTS)('handles no results gracefully', async () => {
181
- const output = await run('search "xyznonexistent123"')
182
- expect(output).toContain('Results: 0')
183
- })
180
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
181
+ 'handles no results gracefully',
182
+ async () => {
183
+ const output = await run('search "xyznonexistent123"')
184
+ expect(output).toContain('Results: 0')
185
+ },
186
+ )
184
187
 
185
188
  it('supports -k flag for explicit keyword search', async () => {
186
189
  const output = await run('search -k "API Reference"')
187
190
  expect(output).toContain('Content search')
188
191
  })
189
192
 
190
- it.skipIf(!INCLUDE_EMBED_TESTS)('supports -n flag to limit results', async () => {
191
- const output = await run('search -n 2 "the"')
192
- const lines = output
193
- .split('\n')
194
- .filter((l) => l.trim().match(/^\w+.*\.md/))
195
- expect(lines.length).toBeLessThanOrEqual(2)
196
- })
193
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
194
+ 'supports -n flag to limit results',
195
+ async () => {
196
+ const output = await run('search -n 2 "the"')
197
+ const lines = output
198
+ .split('\n')
199
+ .filter((l) => l.trim().match(/^\w+.*\.md/))
200
+ expect(lines.length).toBeLessThanOrEqual(2)
201
+ },
202
+ )
197
203
 
198
204
  it('shows mode indicator in output', async () => {
199
205
  const output = await run('search -k "getting started"')
200
206
  expect(output).toContain('[keyword]')
201
207
  })
202
208
 
203
- it.skipIf(!INCLUDE_EMBED_TESTS)('supports boolean AND operator', async () => {
204
- const output = await run('search "test AND fixture"')
205
- expect(output).toContain('Results:')
206
- })
209
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
210
+ 'supports boolean AND operator',
211
+ async () => {
212
+ const output = await run('search "test AND fixture"')
213
+ expect(output).toContain('Results:')
214
+ },
215
+ )
207
216
 
208
- it.skipIf(!INCLUDE_EMBED_TESTS)('supports boolean OR operator', async () => {
209
- const output = await run('search "installation OR endpoints"')
210
- expect(output).toContain('Results:')
211
- })
217
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
218
+ 'supports boolean OR operator',
219
+ async () => {
220
+ const output = await run('search "installation OR endpoints"')
221
+ expect(output).toContain('Results:')
222
+ },
223
+ )
212
224
 
213
- it.skipIf(!INCLUDE_EMBED_TESTS)('supports boolean NOT operator', async () => {
214
- const output = await run('search "test NOT endpoints"')
215
- expect(output).toContain('Results:')
216
- })
225
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
226
+ 'supports boolean NOT operator',
227
+ async () => {
228
+ const output = await run('search "test NOT endpoints"')
229
+ expect(output).toContain('Results:')
230
+ },
231
+ )
217
232
 
218
- it.skipIf(!INCLUDE_EMBED_TESTS)('supports quoted phrase search', async () => {
219
- const output = await run('search \'"Getting Started"\' .')
220
- expect(output).toContain('Results:')
221
- })
233
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
234
+ 'supports quoted phrase search',
235
+ async () => {
236
+ const output = await run('search \'"Getting Started"\' .')
237
+ expect(output).toContain('Results:')
238
+ },
239
+ )
222
240
 
223
241
  it('supports --mode flag', async () => {
224
242
  const output = await run('search --mode keyword "getting started"')
@@ -292,10 +310,13 @@ describe.concurrent('mdcontext CLI e2e', () => {
292
310
  expect(output).toContain('[keyword]')
293
311
  })
294
312
 
295
- it.skipIf(!INCLUDE_EMBED_TESTS)('supports -B and -A flags for asymmetric context', async () => {
296
- const output = await run('search "test" . -B 1 -A 3')
297
- expect(output).toContain('[semantic]')
298
- })
313
+ it.skipIf(!INCLUDE_EMBED_TESTS)(
314
+ 'supports -B and -A flags for asymmetric context',
315
+ async () => {
316
+ const output = await run('search "test" . -B 1 -A 3')
317
+ expect(output).toContain('[semantic]')
318
+ },
319
+ )
299
320
 
300
321
  it('includes contextLines in JSON output', async () => {
301
322
  const output = await run('search -k "test" . -C 2 --json -n 1')
@@ -349,8 +370,8 @@ describe.concurrent('mdcontext CLI e2e', () => {
349
370
 
350
371
  describe('unknown flag handling', () => {
351
372
  it('shows clear error for unknown flag', async () => {
352
- const output = await run('context -x README.md', { expectError: true })
353
- expect(output).toContain("Unknown option '-x' for 'context'")
373
+ const output = await run('context -z README.md', { expectError: true })
374
+ expect(output).toContain("Unknown option '-z' for 'context'")
354
375
  expect(output).toContain('Valid options for')
355
376
  })
356
377
 
@@ -427,4 +448,180 @@ describe.concurrent('mdcontext CLI e2e', () => {
427
448
  expect(output).toContain('Content search')
428
449
  })
429
450
  })
451
+
452
+ describe('config loading error handling', () => {
453
+ it('shows error for non-existent config file', async () => {
454
+ const output = await run('--config /nonexistent/path.json --help', {
455
+ expectError: true,
456
+ })
457
+ expect(output).toContain('Error: Config file not found')
458
+ expect(output).toContain('path.json')
459
+ })
460
+
461
+ it('shows error for invalid JSON config file', async () => {
462
+ // Create a temp file with invalid JSON
463
+ const fs = await import('node:fs')
464
+ const os = await import('node:os')
465
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
466
+ const invalidConfigPath = path.join(tempDir, 'invalid.json')
467
+ fs.writeFileSync(invalidConfigPath, 'not valid json { broken')
468
+
469
+ try {
470
+ const output = await run(`--config ${invalidConfigPath} --help`, {
471
+ expectError: true,
472
+ })
473
+ expect(output).toContain('Error: Invalid JSON in config file')
474
+ expect(output).toContain(invalidConfigPath)
475
+ } finally {
476
+ fs.rmSync(tempDir, { recursive: true, force: true })
477
+ }
478
+ })
479
+
480
+ it('shows error for JS config that exports non-object', async () => {
481
+ const fs = await import('node:fs')
482
+ const os = await import('node:os')
483
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
484
+ const badConfigPath = path.join(tempDir, 'bad.config.mjs')
485
+ fs.writeFileSync(badConfigPath, 'export default "not an object"')
486
+
487
+ try {
488
+ const output = await run(`--config ${badConfigPath} --help`, {
489
+ expectError: true,
490
+ })
491
+ expect(output).toContain(
492
+ 'Error: Config file must export a default object',
493
+ )
494
+ expect(output).toContain(badConfigPath)
495
+ } finally {
496
+ fs.rmSync(tempDir, { recursive: true, force: true })
497
+ }
498
+ })
499
+
500
+ it('shows error for JS config that exports null', async () => {
501
+ const fs = await import('node:fs')
502
+ const os = await import('node:os')
503
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
504
+ const nullConfigPath = path.join(tempDir, 'null.config.mjs')
505
+ fs.writeFileSync(nullConfigPath, 'export default null')
506
+
507
+ try {
508
+ const output = await run(`--config ${nullConfigPath} --help`, {
509
+ expectError: true,
510
+ })
511
+ expect(output).toContain(
512
+ 'Error: Config file must export a default object',
513
+ )
514
+ expect(output).toContain(nullConfigPath)
515
+ } finally {
516
+ fs.rmSync(tempDir, { recursive: true, force: true })
517
+ }
518
+ })
519
+
520
+ it('shows error for JS config that exports array', async () => {
521
+ const fs = await import('node:fs')
522
+ const os = await import('node:os')
523
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
524
+ const arrayConfigPath = path.join(tempDir, 'array.config.mjs')
525
+ fs.writeFileSync(
526
+ arrayConfigPath,
527
+ 'export default [{ index: { maxDepth: 5 } }]',
528
+ )
529
+
530
+ try {
531
+ const output = await run(`--config ${arrayConfigPath} --help`, {
532
+ expectError: true,
533
+ })
534
+ expect(output).toContain(
535
+ 'Error: Config file must export a default object',
536
+ )
537
+ expect(output).toContain(arrayConfigPath)
538
+ } finally {
539
+ fs.rmSync(tempDir, { recursive: true, force: true })
540
+ }
541
+ })
542
+
543
+ it('shows error for JS config with syntax error', async () => {
544
+ const fs = await import('node:fs')
545
+ const os = await import('node:os')
546
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
547
+ const syntaxErrorPath = path.join(tempDir, 'syntax-error.config.mjs')
548
+ fs.writeFileSync(
549
+ syntaxErrorPath,
550
+ 'export default { invalid syntax here >>>',
551
+ )
552
+
553
+ try {
554
+ const output = await run(`--config ${syntaxErrorPath} --help`, {
555
+ expectError: true,
556
+ })
557
+ expect(output).toContain('Error')
558
+ } finally {
559
+ fs.rmSync(tempDir, { recursive: true, force: true })
560
+ }
561
+ })
562
+
563
+ it('shows error for JS config with no exports', async () => {
564
+ const fs = await import('node:fs')
565
+ const os = await import('node:os')
566
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
567
+ const noExportPath = path.join(tempDir, 'no-export.config.mjs')
568
+ fs.writeFileSync(
569
+ noExportPath,
570
+ 'const config = { index: { maxDepth: 5 } }; // no export',
571
+ )
572
+
573
+ try {
574
+ const output = await run(`--config ${noExportPath} --help`, {
575
+ expectError: true,
576
+ })
577
+ expect(output).toContain(
578
+ 'Error: Config file must export a default object',
579
+ )
580
+ } finally {
581
+ fs.rmSync(tempDir, { recursive: true, force: true })
582
+ }
583
+ })
584
+
585
+ it('loads valid JSON config file successfully', async () => {
586
+ const fs = await import('node:fs')
587
+ const os = await import('node:os')
588
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
589
+ const validConfigPath = path.join(tempDir, 'valid.json')
590
+ fs.writeFileSync(
591
+ validConfigPath,
592
+ JSON.stringify({ index: { maxDepth: 5 } }),
593
+ )
594
+
595
+ try {
596
+ const output = await run(`--config ${validConfigPath} --help`)
597
+ // Should show help without errors
598
+ expect(output).toContain('index')
599
+ expect(output).toContain('search')
600
+ expect(output).not.toContain('Error')
601
+ } finally {
602
+ fs.rmSync(tempDir, { recursive: true, force: true })
603
+ }
604
+ })
605
+
606
+ it('loads valid MJS config file successfully', async () => {
607
+ const fs = await import('node:fs')
608
+ const os = await import('node:os')
609
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-test-'))
610
+ const validConfigPath = path.join(tempDir, 'valid.config.mjs')
611
+ fs.writeFileSync(
612
+ validConfigPath,
613
+ 'export default { index: { maxDepth: 5 } }',
614
+ )
615
+
616
+ try {
617
+ const output = await run(`--config ${validConfigPath} --help`)
618
+ // Should show help without errors
619
+ expect(output).toContain('index')
620
+ expect(output).toContain('search')
621
+ expect(output).not.toContain('Error')
622
+ } finally {
623
+ fs.rmSync(tempDir, { recursive: true, force: true })
624
+ }
625
+ })
626
+ })
430
627
  })