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,909 @@
1
+ # CLI Commands Module: Effect Error Handling Analysis
2
+
3
+ > Analysis of mdcontext CLI commands against Effect error handling best practices
4
+
5
+ **Files analyzed:**
6
+
7
+ - `/src/cli/main.ts`
8
+ - `/src/cli/commands/search.ts`
9
+ - `/src/cli/commands/index-cmd.ts`
10
+ - `/src/cli/commands/context.ts`
11
+ - `/src/cli/commands/tree.ts`
12
+ - `/src/cli/commands/links.ts`
13
+ - `/src/cli/commands/backlinks.ts`
14
+ - `/src/cli/commands/stats.ts`
15
+ - `/src/embeddings/openai-provider.ts`
16
+ - `/src/parser/parser.ts`
17
+
18
+ **Date:** 2026-01-22
19
+
20
+ ---
21
+
22
+ ## Executive Summary
23
+
24
+ The CLI commands module has several violations of Effect error handling best practices. The most significant issues are:
25
+
26
+ 1. **Using plain `Error` objects instead of tagged errors** - Loses type safety and discriminated union benefits
27
+ 2. **Manual error type inspection in main.ts** - Fragile pattern that relies on internal error structure
28
+ 3. **Inconsistent error transformation** - Mix of `mapError`, `catchAll`, and error swallowing
29
+ 4. **Console.error inside error handling flow** - Violates separation of error data from presentation
30
+ 5. **Error class throwing in constructors** - OpenAI provider throws in constructor instead of returning Effect
31
+
32
+ ---
33
+
34
+ ## 1. Current Error Handling Issues Found
35
+
36
+ ### Issue 1.1: Plain Error Objects Instead of Tagged Errors
37
+
38
+ **Location:** Multiple command files
39
+
40
+ **Current code:**
41
+
42
+ ```typescript
43
+ // context.ts:70-74
44
+ if (fileList.length === 0) {
45
+ yield *
46
+ Effect.fail(
47
+ new Error(
48
+ "At least one file is required. Usage: mdcontext context <file> [files...]",
49
+ ),
50
+ );
51
+ }
52
+
53
+ // context.ts:85-87
54
+ const document =
55
+ yield *
56
+ parseFile(filePath).pipe(
57
+ Effect.mapError((e) => new Error(`${e._tag}: ${e.message}`)),
58
+ );
59
+
60
+ // tree.ts:35-37
61
+ const result =
62
+ yield *
63
+ parseFile(resolvedPath).pipe(
64
+ Effect.mapError((e) => new Error(`${e._tag}: ${e.message}`)),
65
+ );
66
+ ```
67
+
68
+ **Problem:** Using `new Error()` loses all type safety. The error type becomes `Error` which:
69
+
70
+ - Cannot be handled with `catchTag`
71
+ - Loses structured error information
72
+ - Makes exhaustive error handling impossible
73
+ - Type signature doesn't indicate what can fail
74
+
75
+ **Priority:** HIGH
76
+
77
+ ---
78
+
79
+ ### Issue 1.2: Manual Error Type Inspection in main.ts
80
+
81
+ **Location:** `/src/cli/main.ts:71-97`
82
+
83
+ **Current code:**
84
+
85
+ ```typescript
86
+ const formatCliError = (error: unknown): string => {
87
+ if (error && typeof error === "object") {
88
+ const err = error as Record<string, unknown>;
89
+ if (err._tag === "ValidationError" && err.error) {
90
+ const validationError = err.error as Record<string, unknown>;
91
+ // Extract the actual error message
92
+ if (validationError._tag === "Paragraph" && validationError.value) {
93
+ const paragraph = validationError.value as Record<string, unknown>;
94
+ if (paragraph._tag === "Text" && typeof paragraph.value === "string") {
95
+ return paragraph.value;
96
+ }
97
+ }
98
+ }
99
+ // ... more manual inspection
100
+ }
101
+ return String(error);
102
+ };
103
+ ```
104
+
105
+ **Problem:** This is extremely fragile code that:
106
+
107
+ - Relies on internal @effect/cli error structure that could change
108
+ - Uses unsafe type assertions (`as Record<string, unknown>`)
109
+ - Doesn't leverage Effect's error handling patterns
110
+ - Cannot be type-checked by TypeScript
111
+
112
+ **Priority:** HIGH
113
+
114
+ ---
115
+
116
+ ### Issue 1.3: Error Swallowing with catchAll
117
+
118
+ **Location:** Multiple files
119
+
120
+ **Current code:**
121
+
122
+ ```typescript
123
+ // search.ts:347-349
124
+ const estimate = yield* estimateEmbeddingCost(resolvedDir).pipe(
125
+ Effect.catchAll(() => Effect.succeed(null)),
126
+ )
127
+
128
+ // search.ts:376-377
129
+ ).pipe(handleApiKeyError, Effect.catchAll(() => Effect.succeed(null)))
130
+
131
+ // index-cmd.ts:231-233
132
+ const estimate = yield* estimateEmbeddingCost(resolvedDir).pipe(
133
+ Effect.catchAll(() => Effect.succeed(null)),
134
+ )
135
+ ```
136
+
137
+ **Problem:** Using `catchAll` to swallow all errors and return `null`:
138
+
139
+ - Hides actual error conditions
140
+ - Makes debugging difficult
141
+ - Violates "fail fast" principle
142
+ - No logging or indication of what went wrong
143
+
144
+ **Priority:** MEDIUM
145
+
146
+ ---
147
+
148
+ ### Issue 1.4: Console.error Inside Error Handling
149
+
150
+ **Location:** `/src/embeddings/openai-provider.ts:130-165`
151
+
152
+ **Current code:**
153
+
154
+ ```typescript
155
+ export const handleApiKeyError = <A, E>(
156
+ effect: Effect.Effect<A, E | MissingApiKeyError | InvalidApiKeyError>,
157
+ ): Effect.Effect<A, E | Error> =>
158
+ effect.pipe(
159
+ Effect.catchIf(
160
+ (e): e is MissingApiKeyError => e instanceof MissingApiKeyError,
161
+ () =>
162
+ Effect.gen(function* () {
163
+ yield* Console.error("");
164
+ yield* Console.error("Error: OPENAI_API_KEY not set");
165
+ yield* Console.error("");
166
+ // ... more console output
167
+ return yield* Effect.fail(new Error("Missing API key"));
168
+ }),
169
+ ),
170
+ // ...
171
+ );
172
+ ```
173
+
174
+ **Problem:** While better than console.error in constructors, this still:
175
+
176
+ - Mixes error handling with presentation
177
+ - Cannot be customized for different output formats (JSON vs human-readable)
178
+ - Prevents error reuse in non-CLI contexts
179
+ - Returns generic `Error` losing type information
180
+
181
+ **Priority:** MEDIUM
182
+
183
+ ---
184
+
185
+ ### Issue 1.5: Error Classes Not Using Data.TaggedError
186
+
187
+ **Location:** `/src/embeddings/openai-provider.ts:24-36`
188
+
189
+ **Current code:**
190
+
191
+ ```typescript
192
+ export class MissingApiKeyError extends Error {
193
+ constructor() {
194
+ super("OPENAI_API_KEY not set");
195
+ this.name = "MissingApiKeyError";
196
+ }
197
+ }
198
+
199
+ export class InvalidApiKeyError extends Error {
200
+ constructor(message?: string) {
201
+ super(message ?? "Invalid OPENAI_API_KEY");
202
+ this.name = "InvalidApiKeyError";
203
+ }
204
+ }
205
+ ```
206
+
207
+ **Problem:** Using plain `Error` extension instead of `Data.TaggedError`:
208
+
209
+ - Cannot use `Effect.catchTag` directly
210
+ - No automatic `_tag` discriminant
211
+ - Requires `catchIf` with type guards instead of cleaner patterns
212
+ - Not structural equals comparable
213
+
214
+ **Priority:** MEDIUM
215
+
216
+ ---
217
+
218
+ ### Issue 1.6: Throwing in Constructor
219
+
220
+ **Location:** `/src/embeddings/openai-provider.ts:56-60`
221
+
222
+ **Current code:**
223
+
224
+ ```typescript
225
+ constructor(options: OpenAIProviderOptions = {}) {
226
+ const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY
227
+ if (!apiKey) {
228
+ throw new MissingApiKeyError()
229
+ }
230
+ // ...
231
+ }
232
+ ```
233
+
234
+ **Problem:** Throwing in a constructor:
235
+
236
+ - Makes error unrecoverable at construction site
237
+ - Requires try/catch instead of Effect error channel
238
+ - Violates Effect's "errors as values" principle
239
+ - Cannot be composed with other effects
240
+
241
+ **Priority:** MEDIUM
242
+
243
+ ---
244
+
245
+ ### Issue 1.7: Inline Object Errors Without Type Definition
246
+
247
+ **Location:** `/src/parser/parser.ts:369-395`
248
+
249
+ **Current code:**
250
+
251
+ ```typescript
252
+ export const parseFile = (
253
+ filePath: string,
254
+ ): Effect.Effect<
255
+ MdDocument,
256
+ ParseError | { _tag: "IoError"; message: string; path: string }
257
+ > =>
258
+ Effect.gen(function* () {
259
+ // ...
260
+ return yield* Effect.fail({
261
+ _tag: "IoError" as const,
262
+ message: error instanceof Error ? error.message : "Unknown error",
263
+ path: filePath,
264
+ });
265
+ });
266
+ ```
267
+
268
+ **Problem:** Ad-hoc error objects:
269
+
270
+ - Not a proper class, cannot use `instanceof`
271
+ - Type assertion needed (`as const`)
272
+ - No reusable error type
273
+ - Cannot extend with methods or additional behavior
274
+
275
+ **Priority:** LOW
276
+
277
+ ---
278
+
279
+ ### Issue 1.8: Inconsistent Error Handling Across Commands
280
+
281
+ **Location:** All command files
282
+
283
+ **Analysis:**
284
+
285
+ | Command | Error Pattern Used | Issues |
286
+ | ------------ | -------------------------------------- | ------------------------------- |
287
+ | search.ts | `handleApiKeyError`, `catchAll` | Swallows errors, mixed patterns |
288
+ | index-cmd.ts | `handleApiKeyError`, `catchAll` | Swallows errors |
289
+ | context.ts | `Effect.fail(new Error())`, `mapError` | Loses type info |
290
+ | tree.ts | `Effect.fail(new Error())`, `mapError` | Loses type info |
291
+ | links.ts | None (propagates) | Actually good! |
292
+ | backlinks.ts | None (propagates) | Actually good! |
293
+ | stats.ts | None (propagates) | Actually good! |
294
+
295
+ **Priority:** MEDIUM
296
+
297
+ ---
298
+
299
+ ## 2. Specific Violations of Effect Best Practices
300
+
301
+ ### Violation 2.1: Anti-pattern - Converting to Generic Error Too Early
302
+
303
+ **Best practice:** Preserve specific error types throughout the call chain, only transform at the boundary.
304
+
305
+ **Violated in:**
306
+
307
+ - `context.ts:86`: `Effect.mapError((e) => new Error(\`${e.\_tag}: ${e.message}\`))`
308
+ - `tree.ts:36`: Same pattern
309
+
310
+ **Reference:** "Anti-pattern 2: Converting Typed Errors to Generic Error Too Early" from research docs
311
+
312
+ ---
313
+
314
+ ### Violation 2.2: Anti-pattern - Not Using Tagged Errors
315
+
316
+ **Best practice:** Use `Data.TaggedError` for all domain errors.
317
+
318
+ **Violated in:**
319
+
320
+ - `openai-provider.ts`: Uses `extends Error` instead of `Data.TaggedError`
321
+ - Multiple commands: Uses `new Error()` directly
322
+
323
+ **Reference:** "Anti-pattern 6: Not Using Tagged Errors" from research docs
324
+
325
+ ---
326
+
327
+ ### Violation 2.3: Anti-pattern - Over-catching with catchAll
328
+
329
+ **Best practice:** Handle specific errors with `catchTag`, let others propagate.
330
+
331
+ **Violated in:**
332
+
333
+ - `search.ts:347-349`: `catchAll(() => Effect.succeed(null))`
334
+ - `index-cmd.ts:231-233`: Same pattern
335
+
336
+ **Reference:** "Anti-pattern 7: Over-catching with catchAll" from research docs
337
+
338
+ ---
339
+
340
+ ### Violation 2.4: Anti-pattern - Mixing Error Handling with Presentation
341
+
342
+ **Best practice:** Keep errors as pure data, format at the boundary.
343
+
344
+ **Violated in:**
345
+
346
+ - `openai-provider.ts:130-165`: `handleApiKeyError` does Console.error inside
347
+
348
+ **Reference:** "Anti-pattern 1: console.error Inside Error Classes" and "Best Practice 5: Separate Error Presentation from Error Classes" from research docs
349
+
350
+ ---
351
+
352
+ ### Violation 2.5: Best Practice Not Followed - Structured Error Data
353
+
354
+ **Best practice:** Include machine-readable data in errors for programmatic handling.
355
+
356
+ **Current state:** Errors contain only string messages, no structured data like:
357
+
358
+ - Error codes
359
+ - Affected file paths (in some cases)
360
+ - Suggested fixes
361
+ - Exit codes
362
+
363
+ **Reference:** "Best Practice 2: Structured Error Data for Programmatic Handling" from research docs
364
+
365
+ ---
366
+
367
+ ## 3. Recommended Changes with Code Examples
368
+
369
+ ### Recommendation 3.1: Create Centralized Error Types
370
+
371
+ **File:** `src/errors/cli-errors.ts` (new file)
372
+
373
+ ```typescript
374
+ import { Data } from "effect";
375
+
376
+ // ============================================================================
377
+ // CLI/User-Facing Errors
378
+ // ============================================================================
379
+
380
+ export class MissingArgumentError extends Data.TaggedError(
381
+ "MissingArgumentError",
382
+ )<{
383
+ readonly argument: string;
384
+ readonly usage: string;
385
+ }> {}
386
+
387
+ export class InvalidOptionError extends Data.TaggedError("InvalidOptionError")<{
388
+ readonly option: string;
389
+ readonly value: string;
390
+ readonly expected: string;
391
+ }> {}
392
+
393
+ // ============================================================================
394
+ // File System Errors
395
+ // ============================================================================
396
+
397
+ export class FileNotFoundError extends Data.TaggedError("FileNotFoundError")<{
398
+ readonly path: string;
399
+ }> {}
400
+
401
+ export class FileReadError extends Data.TaggedError("FileReadError")<{
402
+ readonly path: string;
403
+ readonly cause: string;
404
+ }> {}
405
+
406
+ // ============================================================================
407
+ // Index Errors
408
+ // ============================================================================
409
+
410
+ export class IndexNotFoundError extends Data.TaggedError("IndexNotFoundError")<{
411
+ readonly directory: string;
412
+ }> {}
413
+
414
+ // ============================================================================
415
+ // API Errors
416
+ // ============================================================================
417
+
418
+ export class ApiKeyMissingError extends Data.TaggedError("ApiKeyMissingError")<{
419
+ readonly provider: string;
420
+ readonly envVar: string;
421
+ }> {}
422
+
423
+ export class ApiKeyInvalidError extends Data.TaggedError("ApiKeyInvalidError")<{
424
+ readonly provider: string;
425
+ readonly details: string;
426
+ }> {}
427
+
428
+ // ============================================================================
429
+ // Parse Errors
430
+ // ============================================================================
431
+
432
+ export class ParseError extends Data.TaggedError("ParseError")<{
433
+ readonly path: string;
434
+ readonly message: string;
435
+ readonly line?: number;
436
+ }> {}
437
+
438
+ export class IoError extends Data.TaggedError("IoError")<{
439
+ readonly path: string;
440
+ readonly message: string;
441
+ readonly operation: "read" | "write" | "stat";
442
+ }> {}
443
+ ```
444
+
445
+ **Priority:** HIGH
446
+
447
+ ---
448
+
449
+ ### Recommendation 3.2: Create Centralized Error Handler
450
+
451
+ **File:** `src/cli/error-handler.ts` (new file)
452
+
453
+ ```typescript
454
+ import { Effect, Console } from "effect";
455
+ import type {
456
+ MissingArgumentError,
457
+ FileNotFoundError,
458
+ ParseError,
459
+ IoError,
460
+ ApiKeyMissingError,
461
+ ApiKeyInvalidError,
462
+ IndexNotFoundError,
463
+ } from "../errors/cli-errors.js";
464
+
465
+ type CliError =
466
+ | MissingArgumentError
467
+ | FileNotFoundError
468
+ | ParseError
469
+ | IoError
470
+ | ApiKeyMissingError
471
+ | ApiKeyInvalidError
472
+ | IndexNotFoundError;
473
+
474
+ interface FormatOptions {
475
+ readonly json: boolean;
476
+ readonly verbose?: boolean;
477
+ }
478
+
479
+ // Format errors for CLI output - ONLY at the boundary
480
+ export const formatError = (
481
+ error: CliError,
482
+ options: FormatOptions,
483
+ ): string => {
484
+ if (options.json) {
485
+ return JSON.stringify({
486
+ error: {
487
+ code: error._tag,
488
+ ...error,
489
+ },
490
+ });
491
+ }
492
+
493
+ switch (error._tag) {
494
+ case "MissingArgumentError":
495
+ return `Error: Missing required argument '${error.argument}'\n\nUsage: ${error.usage}`;
496
+
497
+ case "FileNotFoundError":
498
+ return `Error: File not found: ${error.path}`;
499
+
500
+ case "ParseError":
501
+ return error.line
502
+ ? `Error: Parse error in ${error.path}:${error.line}\n ${error.message}`
503
+ : `Error: Parse error in ${error.path}\n ${error.message}`;
504
+
505
+ case "IoError":
506
+ return `Error: Could not ${error.operation} file: ${error.path}\n ${error.message}`;
507
+
508
+ case "ApiKeyMissingError":
509
+ return [
510
+ `Error: ${error.envVar} not set`,
511
+ "",
512
+ `To use ${error.provider}, set your API key:`,
513
+ ` export ${error.envVar}=sk-...`,
514
+ "",
515
+ "Or add to .env file in project root.",
516
+ ].join("\n");
517
+
518
+ case "ApiKeyInvalidError":
519
+ return [
520
+ `Error: Invalid ${error.provider} API key`,
521
+ "",
522
+ "The provided API key was rejected.",
523
+ `Details: ${error.details}`,
524
+ ].join("\n");
525
+
526
+ case "IndexNotFoundError":
527
+ return [
528
+ "No index found.",
529
+ "",
530
+ `Run: mdcontext index ${error.directory}`,
531
+ " Add --embed for semantic search capabilities",
532
+ ].join("\n");
533
+
534
+ default:
535
+ return `Error: ${String(error)}`;
536
+ }
537
+ };
538
+
539
+ // Exit code mapping
540
+ export const getExitCode = (error: CliError): number => {
541
+ switch (error._tag) {
542
+ case "MissingArgumentError":
543
+ case "InvalidOptionError":
544
+ return 2; // Invalid argument
545
+
546
+ case "FileNotFoundError":
547
+ return 3;
548
+
549
+ case "ParseError":
550
+ case "IoError":
551
+ return 4;
552
+
553
+ case "ApiKeyMissingError":
554
+ case "ApiKeyInvalidError":
555
+ return 5;
556
+
557
+ case "IndexNotFoundError":
558
+ return 6;
559
+
560
+ default:
561
+ return 1;
562
+ }
563
+ };
564
+
565
+ // Handler to apply at command level
566
+ export const handleCliErrors = <A>(
567
+ effect: Effect.Effect<A, CliError>,
568
+ options: FormatOptions,
569
+ ): Effect.Effect<A, never> =>
570
+ effect.pipe(
571
+ Effect.catchAll((error) =>
572
+ Effect.gen(function* () {
573
+ yield* Console.error(formatError(error, options));
574
+ return yield* Effect.die({ exitCode: getExitCode(error) });
575
+ }),
576
+ ),
577
+ );
578
+ ```
579
+
580
+ **Priority:** HIGH
581
+
582
+ ---
583
+
584
+ ### Recommendation 3.3: Update context.ts to Use New Error Types
585
+
586
+ **Before:**
587
+
588
+ ```typescript
589
+ if (fileList.length === 0) {
590
+ yield *
591
+ Effect.fail(
592
+ new Error(
593
+ "At least one file is required. Usage: mdcontext context <file> [files...]",
594
+ ),
595
+ );
596
+ }
597
+
598
+ const document =
599
+ yield *
600
+ parseFile(filePath).pipe(
601
+ Effect.mapError((e) => new Error(`${e._tag}: ${e.message}`)),
602
+ );
603
+ ```
604
+
605
+ **After:**
606
+
607
+ ```typescript
608
+ import { MissingArgumentError } from "../../errors/cli-errors.js";
609
+
610
+ if (fileList.length === 0) {
611
+ yield *
612
+ Effect.fail(
613
+ new MissingArgumentError({
614
+ argument: "files",
615
+ usage: "mdcontext context <file> [files...]",
616
+ }),
617
+ );
618
+ }
619
+
620
+ // Let errors propagate - handle at boundary
621
+ const document = yield * parseFile(filePath);
622
+ ```
623
+
624
+ **Priority:** HIGH
625
+
626
+ ---
627
+
628
+ ### Recommendation 3.4: Update OpenAI Provider to Use Tagged Errors
629
+
630
+ **Before:**
631
+
632
+ ```typescript
633
+ export class MissingApiKeyError extends Error {
634
+ constructor() {
635
+ super("OPENAI_API_KEY not set");
636
+ this.name = "MissingApiKeyError";
637
+ }
638
+ }
639
+
640
+ // In constructor:
641
+ if (!apiKey) {
642
+ throw new MissingApiKeyError();
643
+ }
644
+ ```
645
+
646
+ **After:**
647
+
648
+ ```typescript
649
+ import { Data, Effect } from "effect";
650
+ import {
651
+ ApiKeyMissingError,
652
+ ApiKeyInvalidError,
653
+ } from "../errors/cli-errors.js";
654
+
655
+ // Factory function instead of throwing constructor
656
+ export const createOpenAIProvider = (
657
+ options?: OpenAIProviderOptions,
658
+ ): Effect.Effect<EmbeddingProvider, ApiKeyMissingError> =>
659
+ Effect.gen(function* () {
660
+ const apiKey = options?.apiKey ?? process.env.OPENAI_API_KEY;
661
+ if (!apiKey) {
662
+ return yield* Effect.fail(
663
+ new ApiKeyMissingError({
664
+ provider: "OpenAI",
665
+ envVar: "OPENAI_API_KEY",
666
+ }),
667
+ );
668
+ }
669
+ return new OpenAIProvider(apiKey, options);
670
+ });
671
+ ```
672
+
673
+ **Priority:** MEDIUM
674
+
675
+ ---
676
+
677
+ ### Recommendation 3.5: Update handleApiKeyError to Not Do Console Output
678
+
679
+ **Before:**
680
+
681
+ ```typescript
682
+ export const handleApiKeyError = <A, E>(
683
+ effect: Effect.Effect<A, E | MissingApiKeyError | InvalidApiKeyError>,
684
+ ): Effect.Effect<A, E | Error> =>
685
+ effect.pipe(
686
+ Effect.catchIf(
687
+ (e): e is MissingApiKeyError => e instanceof MissingApiKeyError,
688
+ () =>
689
+ Effect.gen(function* () {
690
+ yield* Console.error("");
691
+ yield* Console.error("Error: OPENAI_API_KEY not set");
692
+ // ...
693
+ return yield* Effect.fail(new Error("Missing API key"));
694
+ }),
695
+ ),
696
+ );
697
+ ```
698
+
699
+ **After:**
700
+
701
+ ```typescript
702
+ import {
703
+ ApiKeyMissingError,
704
+ ApiKeyInvalidError,
705
+ } from "../errors/cli-errors.js";
706
+
707
+ // Simply transform to tagged errors - no presentation
708
+ export const normalizeApiKeyErrors = <A, E>(
709
+ effect: Effect.Effect<A, E | MissingApiKeyError | InvalidApiKeyError>,
710
+ ): Effect.Effect<A, E | ApiKeyMissingError | ApiKeyInvalidError> =>
711
+ effect.pipe(
712
+ Effect.catchTag("MissingApiKeyError", () =>
713
+ Effect.fail(
714
+ new ApiKeyMissingError({
715
+ provider: "OpenAI",
716
+ envVar: "OPENAI_API_KEY",
717
+ }),
718
+ ),
719
+ ),
720
+ Effect.catchTag("InvalidApiKeyError", (e) =>
721
+ Effect.fail(
722
+ new ApiKeyInvalidError({
723
+ provider: "OpenAI",
724
+ details: e.message,
725
+ }),
726
+ ),
727
+ ),
728
+ );
729
+ ```
730
+
731
+ **Priority:** MEDIUM
732
+
733
+ ---
734
+
735
+ ### Recommendation 3.6: Replace catchAll with Explicit Error Handling
736
+
737
+ **Before:**
738
+
739
+ ```typescript
740
+ const estimate =
741
+ yield *
742
+ estimateEmbeddingCost(resolvedDir).pipe(
743
+ Effect.catchAll(() => Effect.succeed(null)),
744
+ );
745
+ ```
746
+
747
+ **After:**
748
+
749
+ ```typescript
750
+ const estimate =
751
+ yield *
752
+ estimateEmbeddingCost(resolvedDir).pipe(
753
+ Effect.catchTag(
754
+ "IndexNotFoundError",
755
+ () => Effect.succeed(null), // Explicitly OK to skip if no index
756
+ ),
757
+ Effect.catchTag(
758
+ "ApiKeyMissingError",
759
+ () => Effect.succeed(null), // Can't estimate without API key
760
+ ),
761
+ // Other errors propagate
762
+ );
763
+ ```
764
+
765
+ **Priority:** MEDIUM
766
+
767
+ ---
768
+
769
+ ### Recommendation 3.7: Update main.ts Error Handling
770
+
771
+ **Before:**
772
+
773
+ ```typescript
774
+ const formatCliError = (error: unknown): string => {
775
+ if (error && typeof error === "object") {
776
+ const err = error as Record<string, unknown>;
777
+ if (err._tag === "ValidationError" && err.error) {
778
+ // ... deep manual inspection
779
+ }
780
+ }
781
+ return String(error);
782
+ };
783
+
784
+ Effect.suspend(() => cli(processedArgv)).pipe(
785
+ Effect.catchAll((error) =>
786
+ Effect.sync(() => {
787
+ if (isValidationError(error)) {
788
+ const message = formatCliError(error);
789
+ console.error(`\nError: ${message}`);
790
+ process.exit(1);
791
+ }
792
+ throw error;
793
+ }),
794
+ ),
795
+ NodeRuntime.runMain,
796
+ );
797
+ ```
798
+
799
+ **After:**
800
+
801
+ ```typescript
802
+ import { formatError, getExitCode } from "./error-handler.js";
803
+
804
+ Effect.suspend(() => cli(processedArgv)).pipe(
805
+ Effect.provide(Layer.merge(NodeContext.layer, cliConfigLayer)),
806
+ Effect.catchTags({
807
+ // Handle our domain errors
808
+ MissingArgumentError: (e) => displayErrorAndExit(e),
809
+ FileNotFoundError: (e) => displayErrorAndExit(e),
810
+ ParseError: (e) => displayErrorAndExit(e),
811
+ IoError: (e) => displayErrorAndExit(e),
812
+ ApiKeyMissingError: (e) => displayErrorAndExit(e),
813
+ ApiKeyInvalidError: (e) => displayErrorAndExit(e),
814
+ IndexNotFoundError: (e) => displayErrorAndExit(e),
815
+ }),
816
+ // Let @effect/cli handle its own ValidationError
817
+ NodeRuntime.runMain,
818
+ );
819
+
820
+ const displayErrorAndExit = (error: CliError) =>
821
+ Effect.gen(function* () {
822
+ yield* Console.error(formatError(error, { json: false }));
823
+ return yield* Effect.die({ exitCode: getExitCode(error) });
824
+ });
825
+ ```
826
+
827
+ **Priority:** HIGH
828
+
829
+ ---
830
+
831
+ ## 4. Priority Summary
832
+
833
+ ### High Priority (Address First)
834
+
835
+ | Issue | Description | Effort |
836
+ | ----- | -------------------------------------------- | ------ |
837
+ | 1.1 | Plain Error objects instead of tagged errors | Medium |
838
+ | 1.2 | Manual error type inspection in main.ts | Medium |
839
+ | 3.1 | Create centralized error types | Low |
840
+ | 3.2 | Create centralized error handler | Low |
841
+ | 3.3 | Update context.ts | Low |
842
+ | 3.7 | Update main.ts | Medium |
843
+
844
+ ### Medium Priority (Address Second)
845
+
846
+ | Issue | Description | Effort |
847
+ | ----- | ---------------------------------------- | ------ |
848
+ | 1.3 | Error swallowing with catchAll | Low |
849
+ | 1.4 | Console.error inside error handling | Medium |
850
+ | 1.5 | Error classes not using Data.TaggedError | Medium |
851
+ | 1.6 | Throwing in constructor | Medium |
852
+ | 1.8 | Inconsistent error handling | Medium |
853
+ | 3.4 | Update OpenAI provider | Medium |
854
+ | 3.5 | Update handleApiKeyError | Low |
855
+ | 3.6 | Replace catchAll | Low |
856
+
857
+ ### Low Priority (Address Later)
858
+
859
+ | Issue | Description | Effort |
860
+ | ----- | -------------------- | ------ |
861
+ | 1.7 | Inline object errors | Low |
862
+
863
+ ---
864
+
865
+ ## Migration Path
866
+
867
+ ### Phase 1: Foundation (High Priority)
868
+
869
+ 1. Create `src/errors/cli-errors.ts` with tagged error classes
870
+ 2. Create `src/cli/error-handler.ts` with formatters
871
+ 3. Export from index files
872
+
873
+ ### Phase 2: Command Updates (High Priority)
874
+
875
+ 1. Update `context.ts` and `tree.ts` to use new error types
876
+ 2. Update `main.ts` to use centralized error handling
877
+ 3. Remove manual error inspection code
878
+
879
+ ### Phase 3: Provider Updates (Medium Priority)
880
+
881
+ 1. Migrate OpenAI provider errors to tagged errors
882
+ 2. Update `handleApiKeyError` to be presentation-free
883
+ 3. Add factory functions instead of throwing constructors
884
+
885
+ ### Phase 4: Refinement (Medium/Low Priority)
886
+
887
+ 1. Replace `catchAll` with explicit `catchTag`
888
+ 2. Add logging for swallowed errors
889
+ 3. Ensure all commands have consistent patterns
890
+
891
+ ---
892
+
893
+ ## Testing Recommendations
894
+
895
+ After implementing changes:
896
+
897
+ 1. **Unit tests for error types:**
898
+ - Verify `_tag` discriminant exists
899
+ - Test error creation with all required fields
900
+ - Test `catchTag` works correctly
901
+
902
+ 2. **Integration tests for CLI:**
903
+ - Test each error scenario produces correct output
904
+ - Test JSON format error output
905
+ - Test exit codes are correct
906
+
907
+ 3. **Snapshot tests for error messages:**
908
+ - Ensure user-facing messages remain clear
909
+ - Verify formatting is consistent