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