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,812 @@
1
+ # Index and Search Module Error Handling Analysis
2
+
3
+ > Analysis of `src/index/` and `src/search/` modules against Effect error handling best practices
4
+
5
+ **Date**: 2026-01-22
6
+ **Modules Analyzed**:
7
+
8
+ - `/Users/alphab/Dev/LLM/DEV/mdcontext/src/index/indexer.ts`
9
+ - `/Users/alphab/Dev/LLM/DEV/mdcontext/src/index/storage.ts`
10
+ - `/Users/alphab/Dev/LLM/DEV/mdcontext/src/index/watcher.ts`
11
+ - `/Users/alphab/Dev/LLM/DEV/mdcontext/src/search/searcher.ts`
12
+
13
+ ---
14
+
15
+ ## Table of Contents
16
+
17
+ 1. [Executive Summary](#executive-summary)
18
+ 2. [Current Error Handling Issues](#current-error-handling-issues)
19
+ 3. [Effect Best Practice Violations](#effect-best-practice-violations)
20
+ 4. [Recommended Changes](#recommended-changes)
21
+ 5. [Priority Matrix](#priority-matrix)
22
+
23
+ ---
24
+
25
+ ## Executive Summary
26
+
27
+ The index and search modules use Effect for asynchronous operations but violate several core Effect error handling patterns. The primary issues are:
28
+
29
+ 1. **Generic `Error` types instead of tagged errors** - All functions use `Effect.Effect<T, Error>` instead of specific tagged error types
30
+ 2. **Error context loss** - Errors are converted to generic `Error` with string messages, losing structured data
31
+ 3. **Swallowed errors via catch blocks** - Multiple `try/catch` patterns silently discard errors
32
+ 4. **Mixed async patterns** - Combines `async/await`, `Effect.promise`, and `Effect.tryPromise` inconsistently
33
+ 5. **No error transformation at boundaries** - Infrastructure errors propagate directly without domain mapping
34
+
35
+ ---
36
+
37
+ ## Current Error Handling Issues
38
+
39
+ ### Issue 1: Generic Error Type Usage
40
+
41
+ **Location**: All modules
42
+ **Severity**: HIGH
43
+
44
+ All Effect-returning functions declare `Error` as their error type rather than domain-specific tagged errors:
45
+
46
+ ```typescript
47
+ // storage.ts - Line 28
48
+ const readJsonFile = <T>(filePath: string): Effect.Effect<T | null, Error> =>
49
+
50
+ // storage.ts - Line 41
51
+ const writeJsonFile = <T>(filePath: string, data: T): Effect.Effect<void, Error> =>
52
+
53
+ // indexer.ts - Line 164
54
+ export const buildIndex = (
55
+ rootPath: string,
56
+ options: IndexOptions = {},
57
+ ): Effect.Effect<IndexResult, Error> =>
58
+
59
+ // searcher.ts - Line 99
60
+ export const search = (
61
+ rootPath: string,
62
+ options: SearchOptions = {},
63
+ ): Effect.Effect<readonly SearchResult[], Error> =>
64
+ ```
65
+
66
+ **Problem**: Using generic `Error` loses Effect's type-safe error tracking. Callers cannot use `catchTag` for specific error handling, and the type signature provides no information about what can go wrong.
67
+
68
+ ---
69
+
70
+ ### Issue 2: String-Based Error Messages
71
+
72
+ **Location**: `storage.ts`, `indexer.ts`
73
+ **Severity**: HIGH
74
+
75
+ Errors are constructed with string interpolation, losing structured data:
76
+
77
+ ```typescript
78
+ // storage.ts - Line 24-25
79
+ const ensureDir = (dirPath: string): Effect.Effect<void, Error> =>
80
+ Effect.tryPromise({
81
+ try: () => fs.mkdir(dirPath, { recursive: true }),
82
+ catch: (e) => new Error(`Failed to create directory ${dirPath}: ${e}`),
83
+ })
84
+
85
+ // storage.ts - Line 37-38
86
+ catch: (e) => new Error(`Failed to read ${filePath}: ${e}`)
87
+
88
+ // indexer.ts - Line 192-193
89
+ catch: (e) => new Error(`Failed to walk directory: ${e}`),
90
+
91
+ // indexer.ts - Line 250-252
92
+ .pipe(
93
+ Effect.mapError(
94
+ (e) => new Error(`Parse error in ${relativePath}: ${e.message}`),
95
+ ),
96
+ )
97
+ ```
98
+
99
+ **Problem**: String concatenation:
100
+
101
+ - Destroys the original error's stack trace and type
102
+ - Makes programmatic error handling impossible
103
+ - Cannot distinguish error types for user-facing messages
104
+ - Violates "preserve error context through transformations" best practice
105
+
106
+ ---
107
+
108
+ ### Issue 3: Swallowed Errors in Catch Blocks
109
+
110
+ **Location**: `storage.ts`, `searcher.ts`
111
+ **Severity**: HIGH
112
+
113
+ Multiple locations silently catch and discard errors:
114
+
115
+ ```typescript
116
+ // storage.ts - Lines 30-35
117
+ const readJsonFile = <T>(filePath: string): Effect.Effect<T | null, Error> =>
118
+ Effect.tryPromise({
119
+ try: async () => {
120
+ try {
121
+ const content = await fs.readFile(filePath, 'utf-8')
122
+ return JSON.parse(content) as T
123
+ } catch {
124
+ return null // ERROR SWALLOWED - no logging, no context
125
+ }
126
+ },
127
+ catch: (e) => new Error(`Failed to read ${filePath}: ${e}`),
128
+ })
129
+
130
+ // storage.ts - Lines 186-193
131
+ export const indexExists = (storage: IndexStorage): Effect.Effect<boolean, Error> =>
132
+ Effect.tryPromise({
133
+ try: async () => {
134
+ try {
135
+ await fs.access(storage.paths.config)
136
+ return true
137
+ } catch {
138
+ return false // Acceptable - checking existence
139
+ }
140
+ },
141
+ ...
142
+ })
143
+
144
+ // searcher.ts - Lines 253-260
145
+ if (parsedQuery || contentRegex) {
146
+ const filePath = path.join(storage.rootPath, docPath)
147
+ try {
148
+ fileContent = yield* Effect.promise(() =>
149
+ fs.readFile(filePath, 'utf-8'),
150
+ )
151
+ fileLines = fileContent.split('\n')
152
+ } catch {
153
+ continue // Skip files that can't be read - ERROR SWALLOWED
154
+ }
155
+ }
156
+
157
+ // searcher.ts - Lines 433-437
158
+ } catch {
159
+ // If file can't be read, include result without content
160
+ resultsWithContent.push(result) // No logging of what failed
161
+ }
162
+
163
+ // searcher.ts - Lines 510-516
164
+ if (includeContent) {
165
+ try {
166
+ fileContent = yield* Effect.promise(() =>
167
+ fs.readFile(resolvedFile, 'utf-8'),
168
+ )
169
+ } catch {
170
+ // Continue without content - no logging
171
+ }
172
+ }
173
+ ```
174
+
175
+ **Problem**:
176
+
177
+ - Silent failures make debugging difficult
178
+ - Users get incomplete results without knowing why
179
+ - Violates "don't swallow errors" anti-pattern guideline
180
+ - Lost opportunity to provide helpful error context
181
+
182
+ ---
183
+
184
+ ### Issue 4: Inconsistent Async Patterns
185
+
186
+ **Location**: `indexer.ts`, `searcher.ts`
187
+ **Severity**: MEDIUM
188
+
189
+ The code mixes different async handling approaches:
190
+
191
+ ```typescript
192
+ // indexer.ts - Uses raw async function (not Effect)
193
+ const walkDirectory = async (
194
+ dir: string,
195
+ exclude: readonly string[],
196
+ ): Promise<string[]> => {
197
+ const files: string[] = [];
198
+ const entries = await fs.readdir(dir, { withFileTypes: true });
199
+ // ... async/await throughout
200
+ };
201
+
202
+ // indexer.ts - Wraps in Effect.tryPromise
203
+ const files =
204
+ yield *
205
+ Effect.tryPromise({
206
+ try: () => walkDirectory(storage.rootPath, exclude),
207
+ catch: (e) => new Error(`Failed to walk directory: ${e}`),
208
+ });
209
+
210
+ // indexer.ts - Uses Effect.promise (no error mapping)
211
+ const [content, stats] =
212
+ yield *
213
+ Effect.promise(() =>
214
+ Promise.all([fs.readFile(filePath, "utf-8"), fs.stat(filePath)]),
215
+ );
216
+
217
+ // searcher.ts - Also uses Effect.promise
218
+ fileContent = yield * Effect.promise(() => fs.readFile(filePath, "utf-8"));
219
+ ```
220
+
221
+ **Problem**:
222
+
223
+ - `Effect.promise` does not map errors - exceptions become defects
224
+ - `walkDirectory` throws raw exceptions without Effect error handling
225
+ - Inconsistent error propagation makes reasoning about failures difficult
226
+
227
+ ---
228
+
229
+ ### Issue 5: Callback-Based Error Handling
230
+
231
+ **Location**: `watcher.ts`
232
+ **Severity**: MEDIUM
233
+
234
+ The watcher uses callback-based error handling instead of Effect patterns:
235
+
236
+ ```typescript
237
+ // watcher.ts - Lines 17-23
238
+ export interface WatcherOptions extends IndexOptions {
239
+ readonly debounceMs?: number
240
+ readonly onIndex?: (result: { documentsIndexed: number; duration: number }) => void
241
+ readonly onError?: (error: Error) => void // Callback-based
242
+ }
243
+
244
+ // watcher.ts - Lines 78-82
245
+ } catch (error) {
246
+ options.onError?.(
247
+ error instanceof Error ? error : new Error(String(error)),
248
+ )
249
+ }
250
+
251
+ // watcher.ts - Lines 117-120
252
+ watcher.on('error', (error: unknown) => {
253
+ options.onError?.(
254
+ error instanceof Error ? error : new Error(String(error)),
255
+ )
256
+ })
257
+ ```
258
+
259
+ **Problem**:
260
+
261
+ - Breaks Effect's error tracking - errors bypass the type system
262
+ - Callers must provide callbacks instead of using Effect's error handling
263
+ - Cannot compose error handling with other Effect operations
264
+ - Violates principle of keeping errors in the Effect channel
265
+
266
+ ---
267
+
268
+ ### Issue 6: Missing Index Error Types
269
+
270
+ **Location**: `searcher.ts`
271
+ **Severity**: MEDIUM
272
+
273
+ When index doesn't exist, functions return empty results instead of typed errors:
274
+
275
+ ```typescript
276
+ // searcher.ts - Lines 109-111
277
+ if (!docIndex || !sectionIndex) {
278
+ return []; // Silent failure - caller doesn't know index is missing
279
+ }
280
+
281
+ // searcher.ts - Lines 197-199
282
+ if (!docIndex || !sectionIndex) {
283
+ return []; // Same issue
284
+ }
285
+ ```
286
+
287
+ But contrast with `getContext` which does fail explicitly:
288
+
289
+ ```typescript
290
+ // searcher.ts - Lines 487-491
291
+ if (!docIndex || !sectionIndex) {
292
+ return (
293
+ yield *
294
+ Effect.fail(new Error("Index not found. Run 'mdcontext index' first."))
295
+ );
296
+ }
297
+ ```
298
+
299
+ **Problem**:
300
+
301
+ - Inconsistent behavior between search and context functions
302
+ - Empty results are ambiguous - did search find nothing or is index missing?
303
+ - User gets no guidance on how to fix the issue
304
+
305
+ ---
306
+
307
+ ### Issue 7: Errors in buildIndex Are Collected But Not Typed
308
+
309
+ **Location**: `indexer.ts`
310
+ **Severity**: MEDIUM
311
+
312
+ The `buildIndex` function collects errors but uses untyped structure:
313
+
314
+ ```typescript
315
+ // indexer.ts - Types from types.js (inferred)
316
+ // IndexBuildError = { path: string; message: string }
317
+
318
+ // indexer.ts - Lines 335-343
319
+ .pipe(
320
+ Effect.catchAll((error) => {
321
+ errors.push({
322
+ path: relativePath,
323
+ message: error instanceof Error ? error.message : String(error),
324
+ })
325
+ return Effect.void
326
+ }),
327
+ )
328
+ ```
329
+
330
+ **Problem**:
331
+
332
+ - `IndexBuildError` is just a plain object, not a tagged error
333
+ - Original error type is lost when converting to message string
334
+ - Cannot distinguish parse errors from I/O errors from permission errors
335
+
336
+ ---
337
+
338
+ ## Effect Best Practice Violations
339
+
340
+ ### Violation 1: Not Using Data.TaggedError
341
+
342
+ **Best Practice**: "Define errors using `Data.TaggedError` for type-safe discriminated unions"
343
+
344
+ **Current State**: All errors are `new Error(string)`
345
+
346
+ **Impact**:
347
+
348
+ - Cannot use `Effect.catchTag` for specific error handling
349
+ - Error types not tracked in Effect's `E` parameter
350
+ - Exhaustive handling with `catchTags` impossible
351
+
352
+ ---
353
+
354
+ ### Violation 2: Mixing Thrown Exceptions with Effect Errors
355
+
356
+ **Best Practice**: "Never throw inside Effect - use Effect.fail for expected errors"
357
+
358
+ **Current State**:
359
+
360
+ - `walkDirectory` is `async` function that can throw
361
+ - `try/catch` blocks inside Effect.tryPromise
362
+ - `Effect.promise` used without error mapping (exceptions become defects)
363
+
364
+ **Impact**:
365
+
366
+ - Some errors tracked as expected (E channel), others as defects
367
+ - Inconsistent error recovery behavior
368
+ - Harder to reason about failure modes
369
+
370
+ ---
371
+
372
+ ### Violation 3: Losing Error Context in Transformations
373
+
374
+ **Best Practice**: "Preserve the cause chain when transforming errors"
375
+
376
+ **Current State**:
377
+
378
+ ```typescript
379
+ Effect.mapError(
380
+ (e) => new Error(`Parse error in ${relativePath}: ${e.message}`),
381
+ );
382
+ ```
383
+
384
+ **Impact**:
385
+
386
+ - Original stack trace lost
387
+ - Original error type lost
388
+ - Cannot access structured error data (line number, column, etc.)
389
+
390
+ ---
391
+
392
+ ### Violation 4: Converting Typed Errors to Generic Error Too Early
393
+
394
+ **Best Practice**: "Keep specific error types as long as possible"
395
+
396
+ **Current State**: Parser returns `IoError | ParseError` but immediately converted:
397
+
398
+ ```typescript
399
+ const doc = yield* parse(content, ...).pipe(
400
+ Effect.mapError(
401
+ (e) => new Error(`Parse error in ${relativePath}: ${e.message}`),
402
+ ),
403
+ )
404
+ ```
405
+
406
+ **Impact**:
407
+
408
+ - Can't distinguish I/O errors from parse errors at call site
409
+ - All parser errors treated the same regardless of type
410
+
411
+ ---
412
+
413
+ ### Violation 5: Using catchAll Instead of Specific Handlers
414
+
415
+ **Best Practice**: "Handle specific errors, let others propagate"
416
+
417
+ **Current State**:
418
+
419
+ ```typescript
420
+ // indexer.ts - Lines 335-343
421
+ .pipe(
422
+ Effect.catchAll((error) => {
423
+ errors.push({ ... })
424
+ return Effect.void
425
+ }),
426
+ )
427
+ ```
428
+
429
+ **Impact**:
430
+
431
+ - All errors treated the same way
432
+ - Cannot apply different recovery strategies based on error type
433
+ - Defects (bugs) handled same as expected errors
434
+
435
+ ---
436
+
437
+ ## Recommended Changes
438
+
439
+ ### Recommendation 1: Create Domain-Specific Error Types
440
+
441
+ **Priority**: HIGH
442
+ **Files**: New file `src/index/errors.ts`, updates to all modules
443
+
444
+ ```typescript
445
+ // src/index/errors.ts
446
+ import { Data } from "effect";
447
+
448
+ // ============================================================================
449
+ // Storage Errors
450
+ // ============================================================================
451
+
452
+ export class DirectoryCreateError extends Data.TaggedError(
453
+ "DirectoryCreateError",
454
+ )<{
455
+ path: string;
456
+ cause: unknown;
457
+ }> {}
458
+
459
+ export class FileReadError extends Data.TaggedError("FileReadError")<{
460
+ path: string;
461
+ cause: unknown;
462
+ }> {}
463
+
464
+ export class FileWriteError extends Data.TaggedError("FileWriteError")<{
465
+ path: string;
466
+ cause: unknown;
467
+ }> {}
468
+
469
+ export class JsonParseError extends Data.TaggedError("JsonParseError")<{
470
+ path: string;
471
+ cause: unknown;
472
+ }> {}
473
+
474
+ // ============================================================================
475
+ // Index Errors
476
+ // ============================================================================
477
+
478
+ export class IndexNotFoundError extends Data.TaggedError("IndexNotFoundError")<{
479
+ rootPath: string;
480
+ message: string;
481
+ }> {}
482
+
483
+ export class DirectoryWalkError extends Data.TaggedError("DirectoryWalkError")<{
484
+ rootPath: string;
485
+ cause: unknown;
486
+ }> {}
487
+
488
+ export class DocumentParseError extends Data.TaggedError("DocumentParseError")<{
489
+ path: string;
490
+ line?: number;
491
+ message: string;
492
+ cause: unknown;
493
+ }> {}
494
+
495
+ // ============================================================================
496
+ // Search Errors
497
+ // ============================================================================
498
+
499
+ export class DocumentNotFoundError extends Data.TaggedError(
500
+ "DocumentNotFoundError",
501
+ )<{
502
+ path: string;
503
+ }> {}
504
+
505
+ export class ContentReadError extends Data.TaggedError("ContentReadError")<{
506
+ path: string;
507
+ cause: unknown;
508
+ }> {}
509
+
510
+ // Union type for all index/search errors
511
+ export type IndexError =
512
+ | DirectoryCreateError
513
+ | FileReadError
514
+ | FileWriteError
515
+ | JsonParseError
516
+ | IndexNotFoundError
517
+ | DirectoryWalkError
518
+ | DocumentParseError;
519
+
520
+ export type SearchError =
521
+ | IndexNotFoundError
522
+ | DocumentNotFoundError
523
+ | ContentReadError;
524
+ ```
525
+
526
+ ---
527
+
528
+ ### Recommendation 2: Update Storage Operations
529
+
530
+ **Priority**: HIGH
531
+ **File**: `src/index/storage.ts`
532
+
533
+ ```typescript
534
+ // Before
535
+ const ensureDir = (dirPath: string): Effect.Effect<void, Error> =>
536
+ Effect.tryPromise({
537
+ try: () => fs.mkdir(dirPath, { recursive: true }),
538
+ catch: (e) => new Error(`Failed to create directory ${dirPath}: ${e}`),
539
+ });
540
+
541
+ // After
542
+ const ensureDir = (
543
+ dirPath: string,
544
+ ): Effect.Effect<void, DirectoryCreateError> =>
545
+ Effect.tryPromise({
546
+ try: () => fs.mkdir(dirPath, { recursive: true }),
547
+ catch: (cause) => new DirectoryCreateError({ path: dirPath, cause }),
548
+ }).pipe(Effect.map(() => undefined));
549
+
550
+ // Before
551
+ const readJsonFile = <T>(filePath: string): Effect.Effect<T | null, Error> =>
552
+ Effect.tryPromise({
553
+ try: async () => {
554
+ try {
555
+ const content = await fs.readFile(filePath, "utf-8");
556
+ return JSON.parse(content) as T;
557
+ } catch {
558
+ return null;
559
+ }
560
+ },
561
+ catch: (e) => new Error(`Failed to read ${filePath}: ${e}`),
562
+ });
563
+
564
+ // After - Separate file read from JSON parse
565
+ const readFile = (filePath: string): Effect.Effect<string, FileReadError> =>
566
+ Effect.tryPromise({
567
+ try: () => fs.readFile(filePath, "utf-8"),
568
+ catch: (cause) => new FileReadError({ path: filePath, cause }),
569
+ });
570
+
571
+ const parseJson = <T>(
572
+ content: string,
573
+ path: string,
574
+ ): Effect.Effect<T, JsonParseError> =>
575
+ Effect.try({
576
+ try: () => JSON.parse(content) as T,
577
+ catch: (cause) => new JsonParseError({ path, cause }),
578
+ });
579
+
580
+ const readJsonFile = <T>(
581
+ filePath: string,
582
+ ): Effect.Effect<T | null, FileReadError | JsonParseError> =>
583
+ readFile(filePath).pipe(
584
+ Effect.flatMap((content) => parseJson<T>(content, filePath)),
585
+ Effect.catchTag("FileReadError", (e) => {
586
+ // File doesn't exist is expected - return null
587
+ if (isNotFoundError(e.cause)) {
588
+ return Effect.succeed(null as T | null);
589
+ }
590
+ return Effect.fail(e);
591
+ }),
592
+ );
593
+ ```
594
+
595
+ ---
596
+
597
+ ### Recommendation 3: Convert walkDirectory to Effect
598
+
599
+ **Priority**: MEDIUM
600
+ **File**: `src/index/indexer.ts`
601
+
602
+ ```typescript
603
+ // Before - async function that throws
604
+ const walkDirectory = async (
605
+ dir: string,
606
+ exclude: readonly string[],
607
+ ): Promise<string[]> => {
608
+ const files: string[] = [];
609
+ const entries = await fs.readdir(dir, { withFileTypes: true });
610
+ // ...
611
+ };
612
+
613
+ // After - Effect-based with proper error handling
614
+ const walkDirectory = (
615
+ dir: string,
616
+ exclude: readonly string[],
617
+ ): Effect.Effect<string[], DirectoryWalkError> =>
618
+ Effect.gen(function* () {
619
+ const files: string[] = [];
620
+
621
+ const entries = yield* Effect.tryPromise({
622
+ try: () => fs.readdir(dir, { withFileTypes: true }),
623
+ catch: (cause) => new DirectoryWalkError({ rootPath: dir, cause }),
624
+ });
625
+
626
+ for (const entry of entries) {
627
+ const fullPath = path.join(dir, entry.name);
628
+
629
+ if (entry.name.startsWith(".") || entry.name === "node_modules") {
630
+ continue;
631
+ }
632
+
633
+ if (shouldExclude(fullPath, exclude)) {
634
+ continue;
635
+ }
636
+
637
+ if (entry.isDirectory()) {
638
+ const subFiles = yield* walkDirectory(fullPath, exclude);
639
+ files.push(...subFiles);
640
+ } else if (entry.isFile() && isMarkdownFile(entry.name)) {
641
+ files.push(fullPath);
642
+ }
643
+ }
644
+
645
+ return files;
646
+ });
647
+ ```
648
+
649
+ ---
650
+
651
+ ### Recommendation 4: Handle Search Errors Explicitly
652
+
653
+ **Priority**: MEDIUM
654
+ **File**: `src/search/searcher.ts`
655
+
656
+ ```typescript
657
+ // Before - silent empty return
658
+ export const search = (
659
+ rootPath: string,
660
+ options: SearchOptions = {},
661
+ ): Effect.Effect<readonly SearchResult[], Error> =>
662
+ Effect.gen(function* () {
663
+ const storage = createStorage(rootPath);
664
+ const docIndex = yield* loadDocumentIndex(storage);
665
+ const sectionIndex = yield* loadSectionIndex(storage);
666
+
667
+ if (!docIndex || !sectionIndex) {
668
+ return []; // Silent failure
669
+ }
670
+ // ...
671
+ });
672
+
673
+ // After - typed error with recovery option
674
+ export const search = (
675
+ rootPath: string,
676
+ options: SearchOptions = {},
677
+ ): Effect.Effect<
678
+ readonly SearchResult[],
679
+ IndexNotFoundError | FileReadError | JsonParseError
680
+ > =>
681
+ Effect.gen(function* () {
682
+ const storage = createStorage(rootPath);
683
+ const docIndex = yield* loadDocumentIndex(storage);
684
+ const sectionIndex = yield* loadSectionIndex(storage);
685
+
686
+ if (!docIndex || !sectionIndex) {
687
+ return yield* Effect.fail(
688
+ new IndexNotFoundError({
689
+ rootPath: storage.rootPath,
690
+ message: "Index not found. Run 'mdcontext index' first.",
691
+ }),
692
+ );
693
+ }
694
+ // ...
695
+ });
696
+
697
+ // Allow callers to choose recovery strategy
698
+ const searchWithFallback = (rootPath: string, options: SearchOptions) =>
699
+ search(rootPath, options).pipe(
700
+ Effect.catchTag("IndexNotFoundError", () => Effect.succeed([])),
701
+ );
702
+ ```
703
+
704
+ ---
705
+
706
+ ### Recommendation 5: Log Skipped Files Instead of Silent Continue
707
+
708
+ **Priority**: MEDIUM
709
+ **File**: `src/search/searcher.ts`
710
+
711
+ ```typescript
712
+ // Before - silent skip
713
+ try {
714
+ fileContent = yield * Effect.promise(() => fs.readFile(filePath, "utf-8"));
715
+ fileLines = fileContent.split("\n");
716
+ } catch {
717
+ continue; // Skip files that can't be read
718
+ }
719
+
720
+ // After - log warning and continue
721
+ const fileResult =
722
+ yield *
723
+ Effect.tryPromise({
724
+ try: () => fs.readFile(filePath, "utf-8"),
725
+ catch: (cause) => new ContentReadError({ path: filePath, cause }),
726
+ }).pipe(
727
+ Effect.catchTag("ContentReadError", (error) =>
728
+ Effect.logWarning(
729
+ `Skipping file that cannot be read: ${error.path}`,
730
+ ).pipe(Effect.map(() => null)),
731
+ ),
732
+ );
733
+
734
+ if (fileResult === null) {
735
+ continue;
736
+ }
737
+ fileContent = fileResult;
738
+ fileLines = fileContent.split("\n");
739
+ ```
740
+
741
+ ---
742
+
743
+ ### Recommendation 6: Update Watcher to Use Effect Streams
744
+
745
+ **Priority**: LOW
746
+ **File**: `src/index/watcher.ts`
747
+
748
+ This is a larger refactor that would convert the watcher to use Effect's streaming primitives instead of callbacks. For now, a minimal improvement:
749
+
750
+ ```typescript
751
+ // Minimal improvement - convert WatcherOptions to return errors in Effect
752
+ export interface WatcherResult {
753
+ readonly watcher: Watcher;
754
+ readonly errors: Effect.Effect<never, WatcherError, never>; // Stream of errors
755
+ }
756
+
757
+ // Or keep simple but document callback limitation
758
+ export interface WatcherOptions extends IndexOptions {
759
+ readonly debounceMs?: number;
760
+ readonly onIndex?: (result: {
761
+ documentsIndexed: number;
762
+ duration: number;
763
+ }) => void;
764
+ /**
765
+ * Error callback for watcher errors.
766
+ * Note: These errors bypass Effect's error channel due to chokidar's callback-based API.
767
+ * Consider using Effect Streams for better error handling in future versions.
768
+ */
769
+ readonly onError?: (error: Error) => void;
770
+ }
771
+ ```
772
+
773
+ ---
774
+
775
+ ## Priority Matrix
776
+
777
+ | Issue | Priority | Effort | Impact | Recommendation |
778
+ | ----------------------- | -------- | ------ | ------ | ------------------------------- |
779
+ | Generic `Error` types | HIGH | Medium | High | Create tagged error types |
780
+ | String error messages | HIGH | Medium | High | Use structured error data |
781
+ | Swallowed errors | HIGH | Low | High | Add logging or explicit returns |
782
+ | Inconsistent async | MEDIUM | Medium | Medium | Standardize on Effect patterns |
783
+ | Callback error handling | MEDIUM | High | Medium | Consider Effect Streams |
784
+ | Missing index errors | MEDIUM | Low | Medium | Return explicit errors |
785
+ | Untyped IndexBuildError | MEDIUM | Low | Low | Convert to TaggedError |
786
+
787
+ ### Suggested Implementation Order
788
+
789
+ 1. **Phase 1 (High Priority)**
790
+ - Create `src/index/errors.ts` with tagged error types
791
+ - Update `storage.ts` to use new error types
792
+ - Update `indexer.ts` to use new error types
793
+
794
+ 2. **Phase 2 (Medium Priority)**
795
+ - Update `searcher.ts` to use explicit index errors
796
+ - Add logging for skipped files
797
+ - Convert `walkDirectory` to Effect
798
+
799
+ 3. **Phase 3 (Low Priority)**
800
+ - Evaluate Effect Streams for watcher
801
+ - Consider typed error unions for CLI boundary
802
+
803
+ ---
804
+
805
+ ## Related Documents
806
+
807
+ - [Effect Errors as Values Research](/Users/alphab/Dev/LLM/DEV/mdcontext/research/effect-errors-as-values.md)
808
+ - [Effect CLI Error Handling Research](/Users/alphab/Dev/LLM/DEV/mdcontext/research/effect-cli-error-handling.md)
809
+
810
+ ---
811
+
812
+ _Analysis completed: 2026-01-22_