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,655 @@
1
+ /**
2
+ * Centralized CLI error handler
3
+ *
4
+ * This module provides a single point of error formatting and display for the CLI.
5
+ * It maps tagged errors to user-friendly messages with appropriate exit codes.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * program.pipe(handleCliErrors)
10
+ * ```
11
+ *
12
+ * Exit Codes:
13
+ * - 0: Success
14
+ * - 1: User error (invalid arguments, missing config, etc.)
15
+ * - 2: System error (file system, network, etc.)
16
+ * - 3: API error (authentication, rate limits)
17
+ */
18
+
19
+ import { Console, Effect, Match } from 'effect'
20
+ import type { EmbeddingProvider } from '../config/schema.js'
21
+ import {
22
+ detectProviderError,
23
+ getProviderErrorTitle,
24
+ getProviderSuggestions,
25
+ } from '../embeddings/provider-errors.js'
26
+ import type {
27
+ ApiKeyInvalidError,
28
+ ApiKeyMissingError,
29
+ CliValidationError,
30
+ ConfigError,
31
+ DirectoryCreateError,
32
+ DirectoryWalkError,
33
+ DocumentNotFoundError,
34
+ EmbeddingError,
35
+ EmbeddingsNotFoundError,
36
+ FileReadError,
37
+ FileWriteError,
38
+ IndexBuildError,
39
+ IndexCorruptedError,
40
+ IndexNotFoundError,
41
+ MdContextError,
42
+ ParseError,
43
+ VectorStoreError,
44
+ WatchError,
45
+ } from '../errors/index.js'
46
+
47
+ // ============================================================================
48
+ // Exit Codes
49
+ // ============================================================================
50
+
51
+ export const EXIT_CODE = {
52
+ SUCCESS: 0,
53
+ USER_ERROR: 1,
54
+ SYSTEM_ERROR: 2,
55
+ API_ERROR: 3,
56
+ } as const
57
+
58
+ export type ExitCode = (typeof EXIT_CODE)[keyof typeof EXIT_CODE]
59
+
60
+ // ============================================================================
61
+ // Formatted Error
62
+ // ============================================================================
63
+
64
+ export interface FormattedError {
65
+ readonly code: string
66
+ readonly message: string
67
+ readonly details?: string | undefined
68
+ readonly suggestions?: readonly string[] | undefined
69
+ readonly exitCode: ExitCode
70
+ }
71
+
72
+ // ============================================================================
73
+ // Config Error Formatter
74
+ // ============================================================================
75
+
76
+ /**
77
+ * Format a ConfigError with enhanced context information.
78
+ * Builds a detailed error message including source file, type expectations,
79
+ * and valid values when available.
80
+ */
81
+ const formatConfigError = (e: ConfigError): FormattedError => {
82
+ const message = e.field
83
+ ? `Invalid configuration: ${e.field}`
84
+ : 'Configuration error'
85
+
86
+ const detailParts: string[] = []
87
+
88
+ if (e.sourceFile) {
89
+ detailParts.push(`Source: ${e.sourceFile}`)
90
+ }
91
+
92
+ if (e.expectedType) {
93
+ detailParts.push(`Expected: ${e.expectedType}`)
94
+ }
95
+
96
+ if (e.actualValue !== undefined) {
97
+ const actualStr =
98
+ typeof e.actualValue === 'string'
99
+ ? `"${e.actualValue}"`
100
+ : String(e.actualValue)
101
+ detailParts.push(`Got: ${actualStr}`)
102
+ }
103
+
104
+ if (e.validValues && e.validValues.length > 0) {
105
+ detailParts.push(`Valid values: ${e.validValues.join(', ')}`)
106
+ }
107
+
108
+ if (e.message && detailParts.length === 0) {
109
+ detailParts.push(e.message)
110
+ } else if (e.message && !detailParts.some((p) => p.includes(e.message))) {
111
+ detailParts.unshift(e.message)
112
+ }
113
+
114
+ const suggestions: string[] = []
115
+ suggestions.push('Check your config file syntax')
116
+ suggestions.push("Run 'mdcontext config check' to validate configuration")
117
+
118
+ return {
119
+ code: e.code,
120
+ message,
121
+ details: detailParts.length > 0 ? detailParts.join('\n ') : undefined,
122
+ suggestions,
123
+ exitCode: EXIT_CODE.USER_ERROR,
124
+ }
125
+ }
126
+
127
+ // ============================================================================
128
+ // Error Formatter
129
+ // ============================================================================
130
+
131
+ /**
132
+ * Format an error for user display.
133
+ * Returns a structured object with message, suggestions, and exit code.
134
+ */
135
+ export const formatError = (error: MdContextError): FormattedError =>
136
+ Match.value(error).pipe(
137
+ // File system errors
138
+ Match.tag('FileReadError', (e) => ({
139
+ code: e.code,
140
+ message: `Cannot read file: ${e.path}`,
141
+ details: e.message,
142
+ suggestions: [
143
+ 'Check that the file exists',
144
+ 'Check file permissions',
145
+ ] as const,
146
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
147
+ })),
148
+ Match.tag('FileWriteError', (e) => ({
149
+ code: e.code,
150
+ message: `Cannot write file: ${e.path}`,
151
+ details: e.message,
152
+ suggestions: [
153
+ 'Check that the directory exists',
154
+ 'Check write permissions',
155
+ 'Check disk space',
156
+ ] as const,
157
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
158
+ })),
159
+ Match.tag('DirectoryCreateError', (e) => ({
160
+ code: e.code,
161
+ message: `Cannot create directory: ${e.path}`,
162
+ details: e.message,
163
+ suggestions: [
164
+ 'Check parent directory permissions',
165
+ 'Check disk space',
166
+ ] as const,
167
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
168
+ })),
169
+ Match.tag('DirectoryWalkError', (e) => ({
170
+ code: e.code,
171
+ message: `Cannot traverse directory: ${e.path}`,
172
+ details: e.message,
173
+ suggestions: [
174
+ 'Check directory permissions',
175
+ 'Check that the path exists',
176
+ ] as const,
177
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
178
+ })),
179
+
180
+ // Parse errors
181
+ Match.tag('ParseError', (e) => ({
182
+ code: e.code,
183
+ message: e.path
184
+ ? `Parse error in ${e.path}${e.line ? `:${e.line}` : ''}`
185
+ : 'Parse error',
186
+ details: e.message,
187
+ suggestions: ['Check the file syntax'] as const,
188
+ exitCode: EXIT_CODE.USER_ERROR,
189
+ })),
190
+
191
+ // API key errors
192
+ Match.tag('ApiKeyMissingError', (e) => ({
193
+ code: e.code,
194
+ message: `${e.envVar} not set`,
195
+ suggestions: [
196
+ `export ${e.envVar}=your-api-key`,
197
+ 'Or add to .env file in project root',
198
+ ] as const,
199
+ exitCode: EXIT_CODE.API_ERROR,
200
+ })),
201
+ Match.tag('ApiKeyInvalidError', (e) => ({
202
+ code: e.code,
203
+ message: `Invalid API key for ${e.provider}`,
204
+ details: e.details,
205
+ suggestions: [
206
+ 'Check that your API key is correct',
207
+ 'Verify your API key has not expired',
208
+ 'Check your API account status',
209
+ ] as const,
210
+ exitCode: EXIT_CODE.API_ERROR,
211
+ })),
212
+
213
+ // Embedding errors - enhanced with provider-specific detection
214
+ Match.tag('EmbeddingError', (e) => {
215
+ // Try to detect provider-specific error for better messaging
216
+ const provider = (e.provider ?? 'openai') as EmbeddingProvider
217
+ const providerError = detectProviderError(provider, e.cause)
218
+
219
+ if (providerError) {
220
+ // Use provider-specific error formatting
221
+ return {
222
+ code: e.code,
223
+ message: getProviderErrorTitle(providerError),
224
+ details: e.message,
225
+ suggestions: getProviderSuggestions(providerError),
226
+ exitCode:
227
+ providerError.type === 'daemon-not-running' ||
228
+ providerError.type === 'gui-not-running' ||
229
+ providerError.type === 'connection-refused'
230
+ ? EXIT_CODE.SYSTEM_ERROR
231
+ : EXIT_CODE.API_ERROR,
232
+ }
233
+ }
234
+
235
+ // Fall back to reason-based handling
236
+ return Match.value(e.reason).pipe(
237
+ Match.when('RateLimit', () => ({
238
+ code: e.code,
239
+ message: 'Rate limit exceeded',
240
+ details: e.message,
241
+ suggestions: [
242
+ 'Wait a few minutes and try again',
243
+ 'Consider using a smaller batch size',
244
+ ] as const,
245
+ exitCode: EXIT_CODE.API_ERROR,
246
+ })),
247
+ Match.when('QuotaExceeded', () => ({
248
+ code: e.code,
249
+ message: 'API quota exceeded',
250
+ details: e.message,
251
+ suggestions: [
252
+ 'Check your API usage limits',
253
+ 'Consider upgrading your API plan',
254
+ ] as const,
255
+ exitCode: EXIT_CODE.API_ERROR,
256
+ })),
257
+ Match.when('Network', () => {
258
+ // Check for provider-specific network errors
259
+ const networkSuggestions =
260
+ provider === 'ollama'
261
+ ? [
262
+ 'Start the Ollama daemon: ollama serve',
263
+ 'Install Ollama: https://ollama.com/download',
264
+ ]
265
+ : provider === 'lm-studio'
266
+ ? [
267
+ 'Open LM Studio application',
268
+ 'Start the local server in Developer tab',
269
+ ]
270
+ : ['Check your internet connection', 'Try again later']
271
+
272
+ return {
273
+ code: e.code,
274
+ message:
275
+ provider === 'ollama'
276
+ ? 'Cannot connect to Ollama'
277
+ : provider === 'lm-studio'
278
+ ? 'Cannot connect to LM Studio'
279
+ : 'Network error during embedding',
280
+ details: e.message,
281
+ suggestions: networkSuggestions,
282
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
283
+ }
284
+ }),
285
+ Match.when('ModelError', () => ({
286
+ code: e.code,
287
+ message:
288
+ provider === 'ollama'
289
+ ? 'Ollama model not found'
290
+ : provider === 'lm-studio'
291
+ ? 'LM Studio model not loaded'
292
+ : 'Model error',
293
+ details: e.message,
294
+ suggestions:
295
+ provider === 'ollama'
296
+ ? [
297
+ 'Download an embedding model: ollama pull nomic-embed-text',
298
+ 'List available models: ollama list',
299
+ ]
300
+ : provider === 'lm-studio'
301
+ ? [
302
+ 'Load an embedding model in LM Studio',
303
+ 'Go to Models tab and download an embedding model',
304
+ ]
305
+ : ['Check that the model name is correct'],
306
+ exitCode: EXIT_CODE.USER_ERROR,
307
+ })),
308
+ Match.orElse(() => ({
309
+ code: e.code,
310
+ message: 'Embedding generation failed',
311
+ details: e.message,
312
+ exitCode: EXIT_CODE.API_ERROR,
313
+ })),
314
+ )
315
+ }),
316
+
317
+ // Index errors
318
+ Match.tag('IndexNotFoundError', (e) => ({
319
+ code: e.code,
320
+ message: 'Index not found',
321
+ details: `No index at ${e.path}`,
322
+ suggestions: ["Run 'mdcontext index' first to build the index"] as const,
323
+ exitCode: EXIT_CODE.USER_ERROR,
324
+ })),
325
+ Match.tag('IndexCorruptedError', (e) => ({
326
+ code: e.code,
327
+ message: 'Index is corrupted',
328
+ details: e.details ?? `Corruption reason: ${e.reason}`,
329
+ suggestions: [
330
+ "Delete the .mdcontext folder and run 'mdcontext index' again",
331
+ ] as const,
332
+ exitCode: EXIT_CODE.USER_ERROR,
333
+ })),
334
+ Match.tag('IndexBuildError', (e) => ({
335
+ code: e.code,
336
+ message: `Failed to build index for: ${e.path}`,
337
+ details: e.message,
338
+ suggestions: ['Check the file is valid markdown'] as const,
339
+ exitCode: EXIT_CODE.USER_ERROR,
340
+ })),
341
+
342
+ // Search errors
343
+ Match.tag('DocumentNotFoundError', (e) => ({
344
+ code: e.code,
345
+ message: `Document not found in index: ${e.path}`,
346
+ suggestions: [
347
+ "Run 'mdcontext index' to update the index",
348
+ 'Check the file path is correct',
349
+ ] as const,
350
+ exitCode: EXIT_CODE.USER_ERROR,
351
+ })),
352
+ Match.tag('EmbeddingsNotFoundError', (e) => ({
353
+ code: e.code,
354
+ message: 'Embeddings not found',
355
+ details: `No embeddings at ${e.path}`,
356
+ suggestions: [
357
+ "Run 'mdcontext index --embed' to build embeddings for semantic search",
358
+ 'Use -k flag for keyword search instead',
359
+ ] as const,
360
+ exitCode: EXIT_CODE.USER_ERROR,
361
+ })),
362
+
363
+ // Dimension mismatch errors
364
+ Match.tag('DimensionMismatchError', (e) => ({
365
+ code: e.code,
366
+ message: 'Embedding dimension mismatch',
367
+ details: e.message,
368
+ suggestions: [
369
+ e.corpusProvider
370
+ ? `Switch back to original provider: --provider ${e.corpusProvider.split(':')[0]} --provider-model ${e.corpusProvider.split(':')[1] ?? ''}`
371
+ : 'Check your embedding provider configuration',
372
+ "Rebuild corpus with current provider: 'mdcontext index --embed --force'",
373
+ 'The corpus was created with different embedding dimensions than your current provider',
374
+ ] as const,
375
+ exitCode: EXIT_CODE.USER_ERROR,
376
+ })),
377
+
378
+ // Vector store errors
379
+ Match.tag('VectorStoreError', (e) => ({
380
+ code: e.code,
381
+ message: `Vector store error during ${e.operation}`,
382
+ details: e.message,
383
+ suggestions: [
384
+ "Delete .mdcontext/embeddings and run 'mdcontext index --embed' again",
385
+ ] as const,
386
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
387
+ })),
388
+
389
+ // Config errors
390
+ Match.tag('ConfigError', (e) => formatConfigError(e)),
391
+
392
+ // Watch errors
393
+ Match.tag('WatchError', (e) => ({
394
+ code: e.code,
395
+ message: `File watcher error: ${e.path}`,
396
+ details: e.message,
397
+ suggestions: [
398
+ 'Check directory permissions',
399
+ 'Check disk space',
400
+ 'Try restarting the watch command',
401
+ ] as const,
402
+ exitCode: EXIT_CODE.SYSTEM_ERROR,
403
+ })),
404
+
405
+ // CLI validation errors
406
+ Match.tag('CliValidationError', (e) => ({
407
+ code: e.code,
408
+ message: e.message,
409
+ details:
410
+ e.argument && e.expected
411
+ ? `Expected ${e.expected}${e.received ? `, got ${e.received}` : ''}`
412
+ : undefined,
413
+ suggestions: ["Run 'mdcontext --help' for usage information"] as const,
414
+ exitCode: EXIT_CODE.USER_ERROR,
415
+ })),
416
+
417
+ Match.exhaustive,
418
+ )
419
+
420
+ // ============================================================================
421
+ // Error Display
422
+ // ============================================================================
423
+
424
+ /**
425
+ * Display a formatted error to stderr.
426
+ * This is the only place in the CLI that should output error messages.
427
+ */
428
+ export const displayError = (
429
+ formatted: FormattedError,
430
+ ): Effect.Effect<void, never> =>
431
+ Effect.gen(function* () {
432
+ yield* Console.error('')
433
+ yield* Console.error(`Error [${formatted.code}]: ${formatted.message}`)
434
+
435
+ if (formatted.details) {
436
+ yield* Console.error(` ${formatted.details}`)
437
+ }
438
+
439
+ if (formatted.suggestions && formatted.suggestions.length > 0) {
440
+ yield* Console.error('')
441
+ for (const suggestion of formatted.suggestions) {
442
+ yield* Console.error(` ${suggestion}`)
443
+ }
444
+ }
445
+
446
+ yield* Console.error('')
447
+ })
448
+
449
+ /**
450
+ * Display error with debug information (stack trace, full context)
451
+ */
452
+ export const displayErrorDebug = (
453
+ error: MdContextError,
454
+ formatted: FormattedError,
455
+ ): Effect.Effect<void, never> =>
456
+ Effect.gen(function* () {
457
+ yield* displayError(formatted)
458
+
459
+ yield* Console.error('--- Debug Info ---')
460
+ yield* Console.error(`Code: ${formatted.code}`)
461
+ yield* Console.error(`Tag: ${error._tag}`)
462
+ yield* Console.error(`Error: ${JSON.stringify(error, null, 2)}`)
463
+
464
+ // Show cause/stack if available
465
+ if ('cause' in error && error.cause) {
466
+ yield* Console.error(`Cause: ${String(error.cause)}`)
467
+ if (error.cause instanceof Error && error.cause.stack) {
468
+ yield* Console.error(`Stack: ${error.cause.stack}`)
469
+ }
470
+ }
471
+ })
472
+
473
+ // ============================================================================
474
+ // Main Handler
475
+ // ============================================================================
476
+
477
+ /**
478
+ * Handle a typed error: format, display, and return appropriate exit code.
479
+ */
480
+ export const handleError = (
481
+ error: MdContextError,
482
+ options: { debug?: boolean } = {},
483
+ ): Effect.Effect<never, never, never> =>
484
+ Effect.gen(function* () {
485
+ const formatted = formatError(error)
486
+
487
+ if (options.debug) {
488
+ yield* displayErrorDebug(error, formatted)
489
+ } else {
490
+ yield* displayError(formatted)
491
+ }
492
+
493
+ return yield* Effect.fail(formatted.exitCode as never)
494
+ })
495
+
496
+ /**
497
+ * Create an error handler that can be piped into an Effect.
498
+ * Handles all MdContextError types with proper formatting and exit codes.
499
+ *
500
+ * Usage:
501
+ * ```typescript
502
+ * program.pipe(
503
+ * Effect.catchTags(createErrorHandler())
504
+ * )
505
+ * ```
506
+ */
507
+ export const createErrorHandler = (options: { debug?: boolean } = {}) => ({
508
+ FileReadError: (e: FileReadError) => handleError(e, options),
509
+ FileWriteError: (e: FileWriteError) => handleError(e, options),
510
+ DirectoryCreateError: (e: DirectoryCreateError) => handleError(e, options),
511
+ DirectoryWalkError: (e: DirectoryWalkError) => handleError(e, options),
512
+ ParseError: (e: ParseError) => handleError(e, options),
513
+ ApiKeyMissingError: (e: ApiKeyMissingError) => handleError(e, options),
514
+ ApiKeyInvalidError: (e: ApiKeyInvalidError) => handleError(e, options),
515
+ EmbeddingError: (e: EmbeddingError) => handleError(e, options),
516
+ IndexNotFoundError: (e: IndexNotFoundError) => handleError(e, options),
517
+ IndexCorruptedError: (e: IndexCorruptedError) => handleError(e, options),
518
+ IndexBuildError: (e: IndexBuildError) => handleError(e, options),
519
+ DocumentNotFoundError: (e: DocumentNotFoundError) => handleError(e, options),
520
+ EmbeddingsNotFoundError: (e: EmbeddingsNotFoundError) =>
521
+ handleError(e, options),
522
+ VectorStoreError: (e: VectorStoreError) => handleError(e, options),
523
+ WatchError: (e: WatchError) => handleError(e, options),
524
+ ConfigError: (e: ConfigError) => handleError(e, options),
525
+ CliValidationError: (e: CliValidationError) => handleError(e, options),
526
+ })
527
+
528
+ // ============================================================================
529
+ // Legacy Error Handling (for transition period)
530
+ // ============================================================================
531
+
532
+ /**
533
+ * Check if an error is an Effect CLI validation error.
534
+ * Used during transition to catch @effect/cli errors.
535
+ */
536
+ export const isEffectCliValidationError = (error: unknown): boolean => {
537
+ if (error && typeof error === 'object') {
538
+ const err = error as Record<string, unknown>
539
+ return (
540
+ err._tag === 'ValidationError' ||
541
+ err._tag === 'MissingValue' ||
542
+ err._tag === 'InvalidValue' ||
543
+ err._tag === 'CommandDirective'
544
+ )
545
+ }
546
+ return false
547
+ }
548
+
549
+ /**
550
+ * Extract the error message from an Effect CLI error structure.
551
+ */
552
+ const extractEffectCliMessage = (error: unknown): string | null => {
553
+ if (error && typeof error === 'object') {
554
+ const err = error as Record<string, unknown>
555
+ if (err._tag === 'Paragraph' && err.value) {
556
+ const paragraph = err.value as Record<string, unknown>
557
+ if (paragraph._tag === 'Text' && typeof paragraph.value === 'string') {
558
+ return paragraph.value
559
+ }
560
+ }
561
+ }
562
+ return null
563
+ }
564
+
565
+ /**
566
+ * Extract user's invalid input from CLI arguments.
567
+ * Looks for --mode value in process.argv.
568
+ */
569
+ const extractModeValue = (): string | null => {
570
+ const args = process.argv
571
+ for (let i = 0; i < args.length; i++) {
572
+ const arg = args[i]
573
+ if (arg === '--mode' || arg === '-m') {
574
+ return args[i + 1] ?? null
575
+ }
576
+ if (arg?.startsWith('--mode=')) {
577
+ return arg.slice('--mode='.length) || null
578
+ }
579
+ }
580
+ return null
581
+ }
582
+
583
+ /**
584
+ * Suggest a correction for mode flag using Levenshtein distance.
585
+ */
586
+ const suggestModeCorrection = (invalidMode: string): string | null => {
587
+ const validModes = ['hybrid', 'semantic', 'keyword']
588
+
589
+ const levenshtein = (a: string, b: string): number => {
590
+ const matrix: number[][] = []
591
+ for (let i = 0; i <= a.length; i++) matrix[i] = [i]
592
+ for (let j = 0; j <= b.length; j++) matrix[0]![j] = j
593
+
594
+ for (let i = 1; i <= a.length; i++) {
595
+ for (let j = 1; j <= b.length; j++) {
596
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1
597
+ matrix[i]![j] = Math.min(
598
+ matrix[i - 1]![j]! + 1,
599
+ matrix[i]![j - 1]! + 1,
600
+ matrix[i - 1]![j - 1]! + cost,
601
+ )
602
+ }
603
+ }
604
+ return matrix[a.length]![b.length]!
605
+ }
606
+
607
+ let bestMatch: string | null = null
608
+ let bestDistance = Infinity
609
+ const maxDistance = 2
610
+
611
+ for (const mode of validModes) {
612
+ const distance = levenshtein(invalidMode.toLowerCase(), mode)
613
+ if (distance <= maxDistance && distance < bestDistance) {
614
+ bestDistance = distance
615
+ bestMatch = mode
616
+ }
617
+ }
618
+
619
+ return bestMatch
620
+ }
621
+
622
+ /**
623
+ * Check if error is a mode validation error and enhance message with suggestion.
624
+ */
625
+ const enhanceModeError = (baseMessage: string): string => {
626
+ const modeValue = extractModeValue()
627
+ if (!modeValue) return baseMessage
628
+
629
+ const suggestion = suggestModeCorrection(modeValue)
630
+ if (!suggestion) return baseMessage
631
+
632
+ return `${baseMessage}\n\nDid you mean '--mode ${suggestion}'?`
633
+ }
634
+
635
+ /**
636
+ * Format an Effect CLI validation error for display.
637
+ */
638
+ export const formatEffectCliError = (error: unknown): string => {
639
+ if (error && typeof error === 'object') {
640
+ const err = error as Record<string, unknown>
641
+
642
+ if (
643
+ (err._tag === 'ValidationError' ||
644
+ err._tag === 'MissingValue' ||
645
+ err._tag === 'InvalidValue') &&
646
+ err.error
647
+ ) {
648
+ const message = extractEffectCliMessage(err.error)
649
+ if (message) {
650
+ return enhanceModeError(message)
651
+ }
652
+ }
653
+ }
654
+ return String(error)
655
+ }