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.
- package/.changeset/README.md +28 -0
- package/.changeset/config.json +11 -0
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/.github/workflows/release.yml +113 -0
- package/.tldrignore +112 -0
- package/BACKLOG.md +338 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +434 -11
- package/biome.json +36 -0
- package/cspell.config.yaml +14 -0
- package/dist/chunk-23UPXDNL.js +3044 -0
- package/dist/chunk-2W7MO2DL.js +1366 -0
- package/dist/chunk-3NUAZGMA.js +1689 -0
- package/dist/chunk-7TOWB2XB.js +366 -0
- package/dist/chunk-7XOTOADQ.js +3065 -0
- package/dist/chunk-AH2PDM2K.js +3042 -0
- package/dist/chunk-BNXWSZ63.js +3742 -0
- package/dist/chunk-BTL5DJVU.js +3222 -0
- package/dist/chunk-HDHYG7E4.js +104 -0
- package/dist/chunk-HLR4KZBP.js +3234 -0
- package/dist/chunk-IP3FRFEB.js +1045 -0
- package/dist/chunk-KHU56VDO.js +3042 -0
- package/dist/chunk-KRYIFLQR.js +88 -0
- package/dist/chunk-LBSDNLEM.js +287 -0
- package/dist/chunk-MNTQ7HCP.js +2643 -0
- package/dist/chunk-MUJELQQ6.js +1387 -0
- package/dist/chunk-MXJGMSLV.js +2199 -0
- package/dist/chunk-N6QJGC3Z.js +2636 -0
- package/dist/chunk-OBELGBPM.js +1713 -0
- package/dist/chunk-OT7R5XTA.js +3192 -0
- package/dist/chunk-P7X4RA2T.js +106 -0
- package/dist/chunk-PIDUQNC2.js +3185 -0
- package/dist/chunk-POGCDIH4.js +3187 -0
- package/dist/chunk-PSIEOQGZ.js +3043 -0
- package/dist/chunk-PVRT3IHA.js +3238 -0
- package/dist/chunk-QNN4TT23.js +1430 -0
- package/dist/chunk-RE3R45RJ.js +3042 -0
- package/dist/chunk-S7E6TFX6.js +803 -0
- package/dist/chunk-SG6GLU4U.js +1378 -0
- package/dist/chunk-SJCDV2ST.js +274 -0
- package/dist/chunk-SYE5XLF3.js +104 -0
- package/dist/chunk-T5VLYBZD.js +103 -0
- package/dist/chunk-TOQB7VWU.js +3238 -0
- package/dist/chunk-VFNMZ4ZQ.js +3228 -0
- package/dist/chunk-VVTGZNBT.js +1629 -0
- package/dist/chunk-W7Q4RFEV.js +104 -0
- package/dist/chunk-XTYYVRLO.js +3190 -0
- package/dist/chunk-Y6MDYVJD.js +3063 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +5458 -0
- package/dist/index.d.ts +653 -0
- package/dist/index.js +79 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +472 -0
- package/dist/schema-BAWSG7KY.js +22 -0
- package/dist/schema-E3QUPL26.js +20 -0
- package/dist/schema-EHL7WUT6.js +20 -0
- package/docs/019-USAGE.md +625 -0
- package/docs/020-current-implementation.md +364 -0
- package/docs/021-DOGFOODING-FINDINGS.md +175 -0
- package/docs/BACKLOG.md +80 -0
- package/docs/CONFIG.md +1123 -0
- package/docs/DESIGN.md +439 -0
- package/docs/ERRORS.md +383 -0
- package/docs/PROJECT.md +88 -0
- package/docs/ROADMAP.md +407 -0
- package/docs/summarization.md +320 -0
- package/docs/test-links.md +9 -0
- package/justfile +40 -0
- package/package.json +74 -9
- package/pnpm-workspace.yaml +5 -0
- package/research/INDEX.md +315 -0
- package/research/code-review/README.md +90 -0
- package/research/code-review/cli-error-handling-review.md +979 -0
- package/research/code-review/code-review-validation-report.md +464 -0
- package/research/code-review/main-ts-review.md +1128 -0
- package/research/config-analysis/01-current-implementation.md +470 -0
- package/research/config-analysis/02-strategy-recommendation.md +428 -0
- package/research/config-analysis/03-task-candidates.md +715 -0
- package/research/config-analysis/033-research-configuration-management.md +828 -0
- package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
- package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
- package/research/config-docs/SUMMARY.md +357 -0
- package/research/config-docs/TEST-RESULTS.md +776 -0
- package/research/config-docs/TODO.md +542 -0
- package/research/config-docs/analysis.md +744 -0
- package/research/config-docs/fix-validation.md +502 -0
- package/research/config-docs/help-audit.md +264 -0
- package/research/config-docs/help-system-analysis.md +890 -0
- package/research/dogfood/consolidated-tool-evaluation.md +373 -0
- package/research/dogfood/strategy-a/a-synthesis.md +184 -0
- package/research/dogfood/strategy-a/a1-docs.md +226 -0
- package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
- package/research/dogfood/strategy-a/a3-llm.md +164 -0
- package/research/dogfood/strategy-b/b-synthesis.md +228 -0
- package/research/dogfood/strategy-b/b1-architecture.md +207 -0
- package/research/dogfood/strategy-b/b2-gaps.md +258 -0
- package/research/dogfood/strategy-b/b3-workflows.md +250 -0
- package/research/dogfood/strategy-c/c-synthesis.md +451 -0
- package/research/dogfood/strategy-c/c1-explorer.md +192 -0
- package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
- package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
- package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
- package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
- package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
- package/research/effect-cli-error-handling.md +845 -0
- package/research/effect-errors-as-values.md +943 -0
- package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
- package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
- package/research/errors-task-analysis/embeddings-analysis.md +709 -0
- package/research/errors-task-analysis/index-search-analysis.md +812 -0
- package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
- package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
- package/research/issue-review.md +603 -0
- package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
- package/research/llm-summarization/alternative-providers-2026.md +1428 -0
- package/research/llm-summarization/anthropic-2026.md +367 -0
- package/research/llm-summarization/claude-cli-integration.md +1706 -0
- package/research/llm-summarization/cli-integration-patterns.md +3155 -0
- package/research/llm-summarization/openai-2026.md +473 -0
- package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
- package/research/llm-summarization/opencode-cli-integration.md +1552 -0
- package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
- package/research/llm-summarization/prototype-results.md +56 -0
- package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
- package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
- package/research/mdcontext-error-analysis.md +521 -0
- package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
- package/research/mdcontext-pudding/01-index-embed.md +956 -0
- package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
- package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
- package/research/mdcontext-pudding/02-search.md +970 -0
- package/research/mdcontext-pudding/03-context.md +779 -0
- package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
- package/research/mdcontext-pudding/04-tree.md +704 -0
- package/research/mdcontext-pudding/05-config.md +1038 -0
- package/research/mdcontext-pudding/06-links-summary.txt +87 -0
- package/research/mdcontext-pudding/06-links.md +679 -0
- package/research/mdcontext-pudding/07-stats.md +693 -0
- package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
- package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
- package/research/mdcontext-pudding/README.md +168 -0
- package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
- package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
- package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
- package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
- package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
- package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
- package/research/research-quality-review.md +834 -0
- package/research/semantic-search/002-research-embedding-models.md +490 -0
- package/research/semantic-search/003-research-rag-alternatives.md +523 -0
- package/research/semantic-search/004-research-vector-search.md +841 -0
- package/research/semantic-search/032-research-semantic-search.md +427 -0
- package/research/semantic-search/embedding-text-analysis.md +156 -0
- package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
- package/research/semantic-search/query-processing-analysis.md +207 -0
- package/research/semantic-search/root-cause-and-solution.md +114 -0
- package/research/semantic-search/threshold-validation-report.md +69 -0
- package/research/semantic-search/vector-search-analysis.md +63 -0
- package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
- package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
- package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
- package/research/task-management-2026/03-lightweight-file-based.md +567 -0
- package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
- package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
- package/research/task-management-2026/linear/02-api-integrations.md +930 -0
- package/research/task-management-2026/linear/03-ai-features.md +368 -0
- package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
- package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
- package/research/test-path-issues.md +276 -0
- package/review/ALP-76/1-error-type-design.md +962 -0
- package/review/ALP-76/2-error-handling-patterns.md +906 -0
- package/review/ALP-76/3-error-presentation.md +624 -0
- package/review/ALP-76/4-test-coverage.md +625 -0
- package/review/ALP-76/5-migration-completeness.md +440 -0
- package/review/ALP-76/6-effect-best-practices.md +755 -0
- package/scripts/apply-branch-protection.sh +47 -0
- package/scripts/branch-protection-templates.json +79 -0
- package/scripts/prototype-summarization.ts +346 -0
- package/scripts/rebuild-hnswlib.js +58 -0
- package/scripts/setup-branch-protection.sh +64 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
- package/src/cli/argv-preprocessor.test.ts +210 -0
- package/src/cli/argv-preprocessor.ts +202 -0
- package/src/cli/cli.test.ts +627 -0
- package/src/cli/commands/backlinks.ts +54 -0
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +285 -0
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +480 -0
- package/src/cli/commands/index.ts +16 -0
- package/src/cli/commands/links.ts +52 -0
- package/src/cli/commands/search.ts +1281 -0
- package/src/cli/commands/stats.ts +149 -0
- package/src/cli/commands/tree.ts +128 -0
- package/src/cli/config-layer.ts +176 -0
- package/src/cli/error-handler.test.ts +235 -0
- package/src/cli/error-handler.ts +655 -0
- package/src/cli/flag-schemas.ts +341 -0
- package/src/cli/help.ts +588 -0
- package/src/cli/index.ts +9 -0
- package/src/cli/main.ts +435 -0
- package/src/cli/options.ts +41 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/typo-suggester.test.ts +105 -0
- package/src/cli/typo-suggester.ts +130 -0
- package/src/cli/utils.ts +259 -0
- package/src/config/file-provider.test.ts +320 -0
- package/src/config/file-provider.ts +273 -0
- package/src/config/index.ts +72 -0
- package/src/config/integration.test.ts +667 -0
- package/src/config/precedence.test.ts +277 -0
- package/src/config/precedence.ts +451 -0
- package/src/config/schema.test.ts +414 -0
- package/src/config/schema.ts +603 -0
- package/src/config/service.test.ts +320 -0
- package/src/config/service.ts +243 -0
- package/src/config/testing.test.ts +264 -0
- package/src/config/testing.ts +110 -0
- package/src/core/index.ts +1 -0
- package/src/core/types.ts +113 -0
- package/src/duplicates/detector.test.ts +183 -0
- package/src/duplicates/detector.ts +414 -0
- package/src/duplicates/index.ts +18 -0
- package/src/embeddings/embedding-namespace.test.ts +300 -0
- package/src/embeddings/embedding-namespace.ts +947 -0
- package/src/embeddings/heading-boost.test.ts +222 -0
- package/src/embeddings/hnsw-build-options.test.ts +198 -0
- package/src/embeddings/hyde.test.ts +272 -0
- package/src/embeddings/hyde.ts +264 -0
- package/src/embeddings/index.ts +10 -0
- package/src/embeddings/openai-provider.ts +414 -0
- package/src/embeddings/pricing.json +22 -0
- package/src/embeddings/provider-constants.ts +204 -0
- package/src/embeddings/provider-errors.test.ts +967 -0
- package/src/embeddings/provider-errors.ts +565 -0
- package/src/embeddings/provider-factory.test.ts +240 -0
- package/src/embeddings/provider-factory.ts +225 -0
- package/src/embeddings/provider-integration.test.ts +788 -0
- package/src/embeddings/query-preprocessing.test.ts +187 -0
- package/src/embeddings/semantic-search-threshold.test.ts +508 -0
- package/src/embeddings/semantic-search.ts +1270 -0
- package/src/embeddings/types.ts +359 -0
- package/src/embeddings/vector-store.ts +708 -0
- package/src/embeddings/voyage-provider.ts +313 -0
- package/src/errors/errors.test.ts +845 -0
- package/src/errors/index.ts +533 -0
- package/src/index/ignore-patterns.test.ts +354 -0
- package/src/index/ignore-patterns.ts +305 -0
- package/src/index/index.ts +4 -0
- package/src/index/indexer.ts +684 -0
- package/src/index/storage.ts +260 -0
- package/src/index/types.ts +147 -0
- package/src/index/watcher.ts +189 -0
- package/src/index.ts +30 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +612 -0
- package/src/parser/index.ts +1 -0
- package/src/parser/parser.test.ts +291 -0
- package/src/parser/parser.ts +394 -0
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +392 -0
- package/src/search/__tests__/hybrid-search.test.ts +650 -0
- package/src/search/bm25-store.ts +366 -0
- package/src/search/cross-encoder.test.ts +253 -0
- package/src/search/cross-encoder.ts +406 -0
- package/src/search/fuzzy-search.test.ts +419 -0
- package/src/search/fuzzy-search.ts +273 -0
- package/src/search/hybrid-search.ts +448 -0
- package/src/search/path-matcher.test.ts +276 -0
- package/src/search/path-matcher.ts +33 -0
- package/src/search/query-parser.test.ts +260 -0
- package/src/search/query-parser.ts +319 -0
- package/src/search/searcher.test.ts +280 -0
- package/src/search/searcher.ts +724 -0
- package/src/search/wink-bm25.d.ts +30 -0
- package/src/summarization/cli-providers/claude.ts +202 -0
- package/src/summarization/cli-providers/detection.test.ts +273 -0
- package/src/summarization/cli-providers/detection.ts +118 -0
- package/src/summarization/cli-providers/index.ts +8 -0
- package/src/summarization/cost.test.ts +139 -0
- package/src/summarization/cost.ts +102 -0
- package/src/summarization/error-handler.test.ts +127 -0
- package/src/summarization/error-handler.ts +111 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/pipeline.test.ts +498 -0
- package/src/summarization/pipeline.ts +231 -0
- package/src/summarization/prompts.test.ts +269 -0
- package/src/summarization/prompts.ts +133 -0
- package/src/summarization/provider-factory.test.ts +396 -0
- package/src/summarization/provider-factory.ts +178 -0
- package/src/summarization/types.ts +184 -0
- package/src/summarize/budget-bugs.test.ts +620 -0
- package/src/summarize/formatters.ts +419 -0
- package/src/summarize/index.ts +20 -0
- package/src/summarize/summarizer.test.ts +275 -0
- package/src/summarize/summarizer.ts +597 -0
- package/src/summarize/verify-bugs.test.ts +238 -0
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tokens.test.ts +142 -0
- package/src/utils/tokens.ts +186 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
- package/tests/fixtures/cli/.mdcontext/config.json +8 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
- package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
- package/tests/fixtures/cli/README.md +9 -0
- package/tests/fixtures/cli/api-reference.md +11 -0
- package/tests/fixtures/cli/getting-started.md +11 -0
- package/tests/integration/embed-index.test.ts +712 -0
- package/tests/integration/search-context.test.ts +469 -0
- package/tests/integration/search-semantic.test.ts +522 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +16 -0
- 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_
|