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,709 @@
|
|
|
1
|
+
# Embeddings Module Error Handling Analysis
|
|
2
|
+
|
|
3
|
+
> Analysis of the embeddings module against Effect error handling best practices
|
|
4
|
+
|
|
5
|
+
## Files Analyzed
|
|
6
|
+
|
|
7
|
+
- `/src/embeddings/openai-provider.ts`
|
|
8
|
+
- `/src/embeddings/semantic-search.ts`
|
|
9
|
+
- `/src/embeddings/types.ts`
|
|
10
|
+
- `/src/embeddings/vector-store.ts`
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Current Error Handling Issues Found
|
|
15
|
+
|
|
16
|
+
### Issue 1.1: Non-Effect Error Classes in openai-provider.ts
|
|
17
|
+
|
|
18
|
+
**Location**: `openai-provider.ts:24-36`
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
export class MissingApiKeyError extends Error {
|
|
22
|
+
constructor() {
|
|
23
|
+
super('OPENAI_API_KEY not set')
|
|
24
|
+
this.name = 'MissingApiKeyError'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class InvalidApiKeyError extends Error {
|
|
29
|
+
constructor(message?: string) {
|
|
30
|
+
super(message ?? 'Invalid OPENAI_API_KEY')
|
|
31
|
+
this.name = 'InvalidApiKeyError'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Problem**: These error classes extend `Error` instead of using `Data.TaggedError`. They lack the `_tag` discriminant field required for `catchTag`/`catchTags` pattern matching.
|
|
37
|
+
|
|
38
|
+
**Priority**: HIGH
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### Issue 1.2: Constructor Throws Exception Instead of Effect.fail
|
|
43
|
+
|
|
44
|
+
**Location**: `openai-provider.ts:56-60`
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
constructor(options: OpenAIProviderOptions = {}) {
|
|
48
|
+
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY
|
|
49
|
+
if (!apiKey) {
|
|
50
|
+
throw new MissingApiKeyError() // Throws exception!
|
|
51
|
+
}
|
|
52
|
+
// ...
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Problem**: The constructor throws a synchronous exception instead of returning an Effect that can fail. This bypasses Effect's error tracking entirely and becomes a "defect" when wrapped in Effect operations.
|
|
57
|
+
|
|
58
|
+
**Priority**: HIGH
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### Issue 1.3: Unused EmbedError Type in types.ts
|
|
63
|
+
|
|
64
|
+
**Location**: `types.ts:68-82`
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
export interface EmbedError {
|
|
68
|
+
readonly _tag: 'EmbedError'
|
|
69
|
+
readonly cause: 'RateLimit' | 'ApiKey' | 'Network' | 'Unknown'
|
|
70
|
+
readonly message: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const embedError = (
|
|
74
|
+
cause: EmbedError['cause'],
|
|
75
|
+
message: string,
|
|
76
|
+
): EmbedError => ({
|
|
77
|
+
_tag: 'EmbedError',
|
|
78
|
+
cause,
|
|
79
|
+
message,
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Problem**: A well-designed `EmbedError` type exists but is never used anywhere in the module. The actual error handling uses plain `Error` classes instead.
|
|
84
|
+
|
|
85
|
+
**Priority**: MEDIUM
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Issue 1.4: Generic Error Type in Effect Return Types
|
|
90
|
+
|
|
91
|
+
**Location**: `semantic-search.ts:73`, `semantic-search.ts:184`, etc.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
export const estimateEmbeddingCost = (
|
|
95
|
+
rootPath: string,
|
|
96
|
+
options: { excludePatterns?: readonly string[] | undefined } = {},
|
|
97
|
+
): Effect.Effect<EmbeddingEstimate, Error> => // Generic Error type
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Problem**: All functions return `Effect.Effect<A, Error>` instead of specific tagged error types. This loses type safety and makes exhaustive error handling impossible at the call site.
|
|
101
|
+
|
|
102
|
+
**Priority**: HIGH
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Issue 1.5: Silent Error Swallowing with Empty catch Blocks
|
|
107
|
+
|
|
108
|
+
**Location**: `semantic-search.ts:329-332`
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
try {
|
|
112
|
+
fileContent = yield* Effect.promise(() =>
|
|
113
|
+
fs.readFile(filePath, 'utf-8'),
|
|
114
|
+
)
|
|
115
|
+
} catch {
|
|
116
|
+
// Skip files that can't be read
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Location**: `semantic-search.ts:532-534`
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
} catch {
|
|
125
|
+
resultsWithContent.push(result)
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Problem**: Errors are silently swallowed without logging or tracking. Users have no visibility into which files failed or why.
|
|
130
|
+
|
|
131
|
+
**Priority**: MEDIUM
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### Issue 1.6: Inconsistent Error Transformation
|
|
136
|
+
|
|
137
|
+
**Location**: `semantic-search.ts:366-375`
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const result = yield* Effect.tryPromise({
|
|
141
|
+
try: () => provider.embed(texts),
|
|
142
|
+
catch: (e) => {
|
|
143
|
+
// Preserve InvalidApiKeyError so handleApiKeyError can catch it
|
|
144
|
+
if (e instanceof InvalidApiKeyError) return e
|
|
145
|
+
return new Error(
|
|
146
|
+
`Embedding failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
147
|
+
)
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Problem**: Conditional error preservation logic is fragile. Some errors are preserved (`InvalidApiKeyError`), others are converted to generic `Error`, losing type information.
|
|
153
|
+
|
|
154
|
+
**Priority**: MEDIUM
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### Issue 1.7: Error Messages Embedded in Code
|
|
159
|
+
|
|
160
|
+
**Location**: `semantic-search.ts:82-84`, `semantic-search.ts:195-196`, etc.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
return yield* Effect.fail(
|
|
164
|
+
new Error("Index not found. Run 'mdcontext index' first."),
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Problem**: User-facing error messages are scattered throughout the codebase rather than being formatted at the application boundary. This violates the Effect best practice of keeping error classes as pure data.
|
|
169
|
+
|
|
170
|
+
**Priority**: LOW
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Issue 1.8: handleApiKeyError Converts to Generic Error
|
|
175
|
+
|
|
176
|
+
**Location**: `openai-provider.ts:130-165`
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
export const handleApiKeyError = <A, E>(
|
|
180
|
+
effect: Effect.Effect<A, E | MissingApiKeyError | InvalidApiKeyError>,
|
|
181
|
+
): Effect.Effect<A, E | Error> => // Returns generic Error
|
|
182
|
+
effect.pipe(
|
|
183
|
+
Effect.catchIf(
|
|
184
|
+
(e): e is MissingApiKeyError => e instanceof MissingApiKeyError,
|
|
185
|
+
() =>
|
|
186
|
+
Effect.gen(function* () {
|
|
187
|
+
yield* Console.error('')
|
|
188
|
+
yield* Console.error('Error: OPENAI_API_KEY not set')
|
|
189
|
+
// ...
|
|
190
|
+
return yield* Effect.fail(new Error('Missing API key')) // Loses type
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
// ...
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Problem**: After displaying the error message, it re-fails with a generic `Error` type, losing the original typed error. Also mixes presentation (Console.error) with error handling logic.
|
|
198
|
+
|
|
199
|
+
**Priority**: MEDIUM
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Issue 1.9: Effect.sync Used Where Effect.try Should Be
|
|
204
|
+
|
|
205
|
+
**Location**: `vector-store.ts:99-121`, `vector-store.ts:123-169`
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
add(entries: VectorEntry[]): Effect.Effect<void, Error> {
|
|
209
|
+
return Effect.sync(() => { // Effect.sync doesn't catch errors!
|
|
210
|
+
const index = this.ensureIndex()
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
// ... operations that could throw
|
|
213
|
+
index.addPoint(entry.embedding as number[], idx)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Problem**: `Effect.sync` is used for operations that could throw (like `addPoint`). If an exception occurs, it becomes an untracked defect instead of a typed error.
|
|
220
|
+
|
|
221
|
+
**Priority**: HIGH
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### Issue 1.10: JSON.parse Without Error Handling
|
|
226
|
+
|
|
227
|
+
**Location**: `vector-store.ts:234`
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const meta = JSON.parse(metaContent) as VectorIndex
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Problem**: `JSON.parse` can throw but is not wrapped in error handling. If the metadata file is corrupted, this throws an untracked exception.
|
|
234
|
+
|
|
235
|
+
**Priority**: MEDIUM
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 2. Specific Violations of Effect Best Practices
|
|
240
|
+
|
|
241
|
+
### Violation 2.1: Not Using Data.TaggedError
|
|
242
|
+
|
|
243
|
+
**Best Practice**: "The recommended way to define errors in Effect is using `Data.TaggedError`"
|
|
244
|
+
|
|
245
|
+
**Current Code**:
|
|
246
|
+
```typescript
|
|
247
|
+
export class MissingApiKeyError extends Error {
|
|
248
|
+
constructor() {
|
|
249
|
+
super('OPENAI_API_KEY not set')
|
|
250
|
+
this.name = 'MissingApiKeyError'
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Violation**: Plain `Error` extension lacks `_tag` discriminant, preventing `catchTag` usage.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Violation 2.2: Throwing in Constructors
|
|
260
|
+
|
|
261
|
+
**Best Practice**: "Effect treats errors as first-class values tracked in the type system"
|
|
262
|
+
|
|
263
|
+
**Current Code**:
|
|
264
|
+
```typescript
|
|
265
|
+
constructor(options: OpenAIProviderOptions = {}) {
|
|
266
|
+
if (!apiKey) {
|
|
267
|
+
throw new MissingApiKeyError()
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Violation**: Synchronous throws bypass Effect's error tracking. Should use factory function returning `Effect.Effect<OpenAIProvider, MissingApiKeyError>`.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### Violation 2.3: Mixing Presentation with Error Handling
|
|
277
|
+
|
|
278
|
+
**Best Practice**: "Keep error classes clean - format at the boundary"
|
|
279
|
+
|
|
280
|
+
**Current Code**:
|
|
281
|
+
```typescript
|
|
282
|
+
Effect.catchIf(
|
|
283
|
+
(e): e is MissingApiKeyError => e instanceof MissingApiKeyError,
|
|
284
|
+
() =>
|
|
285
|
+
Effect.gen(function* () {
|
|
286
|
+
yield* Console.error('Error: OPENAI_API_KEY not set')
|
|
287
|
+
// ... presentation logic in error handler
|
|
288
|
+
}),
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Violation**: Error handler contains `Console.error` calls. Error formatting should happen at the CLI boundary, not in domain code.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
### Violation 2.4: Swallowing Errors Without Logging
|
|
297
|
+
|
|
298
|
+
**Best Practice**: "DO: Log or preserve error information"
|
|
299
|
+
|
|
300
|
+
**Current Code**:
|
|
301
|
+
```typescript
|
|
302
|
+
} catch {
|
|
303
|
+
// Skip files that can't be read
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Violation**: Errors are silently ignored. Should at minimum use `Effect.logWarning` to track failures.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### Violation 2.5: Losing Error Context in Transformations
|
|
313
|
+
|
|
314
|
+
**Best Practice**: "When transforming errors, preserve the original cause"
|
|
315
|
+
|
|
316
|
+
**Current Code**:
|
|
317
|
+
```typescript
|
|
318
|
+
return new Error(
|
|
319
|
+
`Embedding failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Violation**: Original error object is converted to string, losing stack trace and error type. Should preserve as `cause` property.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### Violation 2.6: Converting Typed Errors to Generic Error
|
|
328
|
+
|
|
329
|
+
**Best Practice**: "DO: Preserve specific error types"
|
|
330
|
+
|
|
331
|
+
**Current Code**:
|
|
332
|
+
```typescript
|
|
333
|
+
): Effect.Effect<A, E | Error> => // Returns generic Error
|
|
334
|
+
// ...
|
|
335
|
+
return yield* Effect.fail(new Error('Missing API key'))
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Violation**: Specific `MissingApiKeyError` is transformed to generic `Error`, losing discriminated union benefits.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
### Violation 2.7: Using Defects for Expected Errors (Implicit)
|
|
343
|
+
|
|
344
|
+
**Best Practice**: "Use fail for expected/recoverable errors, die for bugs/invariant violations"
|
|
345
|
+
|
|
346
|
+
**Current Code**:
|
|
347
|
+
```typescript
|
|
348
|
+
return Effect.sync(() => {
|
|
349
|
+
index.addPoint(entry.embedding as number[], idx) // Can throw
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Violation**: `Effect.sync` converts thrown errors to defects. These are expected operational errors that should be recoverable.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Violation 2.8: Defined Error Type Not Used
|
|
358
|
+
|
|
359
|
+
**Best Practice**: "Define domain-specific error types... Create clear, tagged errors for your CLI's domain"
|
|
360
|
+
|
|
361
|
+
**Current Code**: `EmbedError` interface exists but is never used.
|
|
362
|
+
|
|
363
|
+
**Violation**: Well-designed error type is ignored in favor of generic `Error` and plain classes.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 3. Recommended Changes with Code Examples
|
|
368
|
+
|
|
369
|
+
### Recommendation 3.1: Convert Error Classes to Data.TaggedError
|
|
370
|
+
|
|
371
|
+
**Priority**: HIGH
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// src/embeddings/errors.ts
|
|
375
|
+
import { Data } from 'effect'
|
|
376
|
+
|
|
377
|
+
export class MissingApiKeyError extends Data.TaggedError('MissingApiKeyError')<{
|
|
378
|
+
readonly provider: string
|
|
379
|
+
}> {}
|
|
380
|
+
|
|
381
|
+
export class InvalidApiKeyError extends Data.TaggedError('InvalidApiKeyError')<{
|
|
382
|
+
readonly provider: string
|
|
383
|
+
readonly details: string
|
|
384
|
+
}> {}
|
|
385
|
+
|
|
386
|
+
export class EmbeddingError extends Data.TaggedError('EmbeddingError')<{
|
|
387
|
+
readonly cause: 'RateLimit' | 'Network' | 'Unknown'
|
|
388
|
+
readonly message: string
|
|
389
|
+
readonly originalError?: unknown
|
|
390
|
+
}> {}
|
|
391
|
+
|
|
392
|
+
export class IndexNotFoundError extends Data.TaggedError('IndexNotFoundError')<{
|
|
393
|
+
readonly path: string
|
|
394
|
+
readonly indexType: 'document' | 'section' | 'vector'
|
|
395
|
+
}> {}
|
|
396
|
+
|
|
397
|
+
export class VectorStoreError extends Data.TaggedError('VectorStoreError')<{
|
|
398
|
+
readonly operation: 'add' | 'search' | 'save' | 'load'
|
|
399
|
+
readonly message: string
|
|
400
|
+
readonly cause?: unknown
|
|
401
|
+
}> {}
|
|
402
|
+
|
|
403
|
+
export class FileReadError extends Data.TaggedError('FileReadError')<{
|
|
404
|
+
readonly path: string
|
|
405
|
+
readonly cause: string
|
|
406
|
+
}> {}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### Recommendation 3.2: Factory Function Instead of Throwing Constructor
|
|
412
|
+
|
|
413
|
+
**Priority**: HIGH
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// src/embeddings/openai-provider.ts
|
|
417
|
+
import { Effect } from 'effect'
|
|
418
|
+
import { MissingApiKeyError, InvalidApiKeyError } from './errors.js'
|
|
419
|
+
|
|
420
|
+
export const createOpenAIProvider = (
|
|
421
|
+
options: OpenAIProviderOptions = {},
|
|
422
|
+
): Effect.Effect<OpenAIProvider, MissingApiKeyError> =>
|
|
423
|
+
Effect.gen(function* () {
|
|
424
|
+
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY
|
|
425
|
+
|
|
426
|
+
if (!apiKey) {
|
|
427
|
+
return yield* Effect.fail(
|
|
428
|
+
new MissingApiKeyError({ provider: 'openai' })
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return new OpenAIProvider(apiKey, options)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
// Private constructor - only accessible via factory
|
|
436
|
+
class OpenAIProvider implements EmbeddingProvider {
|
|
437
|
+
private constructor(
|
|
438
|
+
private readonly apiKey: string,
|
|
439
|
+
options: OpenAIProviderOptions,
|
|
440
|
+
) {
|
|
441
|
+
this.client = new OpenAI({ apiKey })
|
|
442
|
+
this.model = options.model ?? 'text-embedding-3-small'
|
|
443
|
+
// ...
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### Recommendation 3.3: Typed Error Returns with Proper Transformation
|
|
451
|
+
|
|
452
|
+
**Priority**: HIGH
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// src/embeddings/semantic-search.ts
|
|
456
|
+
import {
|
|
457
|
+
IndexNotFoundError,
|
|
458
|
+
MissingApiKeyError,
|
|
459
|
+
EmbeddingError,
|
|
460
|
+
FileReadError
|
|
461
|
+
} from './errors.js'
|
|
462
|
+
|
|
463
|
+
type BuildEmbeddingsError =
|
|
464
|
+
| IndexNotFoundError
|
|
465
|
+
| MissingApiKeyError
|
|
466
|
+
| InvalidApiKeyError
|
|
467
|
+
| EmbeddingError
|
|
468
|
+
|
|
469
|
+
export const buildEmbeddings = (
|
|
470
|
+
rootPath: string,
|
|
471
|
+
options: BuildEmbeddingsOptions = {},
|
|
472
|
+
): Effect.Effect<BuildEmbeddingsResult, BuildEmbeddingsError> =>
|
|
473
|
+
Effect.gen(function* () {
|
|
474
|
+
// ...
|
|
475
|
+
if (!docIndex || !sectionIndex) {
|
|
476
|
+
return yield* Effect.fail(
|
|
477
|
+
new IndexNotFoundError({
|
|
478
|
+
path: resolvedRoot,
|
|
479
|
+
indexType: 'document'
|
|
480
|
+
})
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Provider creation now returns Effect
|
|
485
|
+
const provider = options.provider ?? (yield* createOpenAIProvider())
|
|
486
|
+
|
|
487
|
+
// ...
|
|
488
|
+
})
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
### Recommendation 3.4: Replace Silent Catches with Logged Failures
|
|
494
|
+
|
|
495
|
+
**Priority**: MEDIUM
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// Instead of silent catch:
|
|
499
|
+
for (let fileIndex = 0; fileIndex < docPaths.length; fileIndex++) {
|
|
500
|
+
const docPath = docPaths[fileIndex]!
|
|
501
|
+
const filePath = path.join(resolvedRoot, docPath)
|
|
502
|
+
|
|
503
|
+
const fileContentResult = yield* Effect.tryPromise({
|
|
504
|
+
try: () => fs.readFile(filePath, 'utf-8'),
|
|
505
|
+
catch: (e) => new FileReadError({
|
|
506
|
+
path: filePath,
|
|
507
|
+
cause: e instanceof Error ? e.message : String(e)
|
|
508
|
+
})
|
|
509
|
+
}).pipe(
|
|
510
|
+
Effect.catchTag('FileReadError', (error) =>
|
|
511
|
+
Effect.gen(function* () {
|
|
512
|
+
yield* Effect.logWarning(`Skipping unreadable file: ${error.path}`)
|
|
513
|
+
return null // Return null to indicate skip
|
|
514
|
+
})
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
if (fileContentResult === null) continue
|
|
519
|
+
|
|
520
|
+
// Process file content...
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
### Recommendation 3.5: Move Error Formatting to CLI Boundary
|
|
527
|
+
|
|
528
|
+
**Priority**: MEDIUM
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// src/embeddings/openai-provider.ts - Keep it simple
|
|
532
|
+
export const handleApiKeyError = <A, E>(
|
|
533
|
+
effect: Effect.Effect<A, E | MissingApiKeyError | InvalidApiKeyError>,
|
|
534
|
+
): Effect.Effect<A, E | MissingApiKeyError | InvalidApiKeyError> =>
|
|
535
|
+
effect // Just pass through - let CLI layer format
|
|
536
|
+
|
|
537
|
+
// src/cli/error-formatter.ts - Centralize formatting
|
|
538
|
+
export const formatEmbeddingError = (error: EmbeddingModuleError): string => {
|
|
539
|
+
switch (error._tag) {
|
|
540
|
+
case 'MissingApiKeyError':
|
|
541
|
+
return [
|
|
542
|
+
'',
|
|
543
|
+
'Error: OPENAI_API_KEY not set',
|
|
544
|
+
'',
|
|
545
|
+
'To use semantic search, set your OpenAI API key:',
|
|
546
|
+
' export OPENAI_API_KEY=sk-...',
|
|
547
|
+
'',
|
|
548
|
+
'Or add to .env file in project root.',
|
|
549
|
+
].join('\n')
|
|
550
|
+
|
|
551
|
+
case 'InvalidApiKeyError':
|
|
552
|
+
return [
|
|
553
|
+
'',
|
|
554
|
+
'Error: Invalid OPENAI_API_KEY',
|
|
555
|
+
'',
|
|
556
|
+
'The provided API key was rejected by OpenAI.',
|
|
557
|
+
`Details: ${error.details}`,
|
|
558
|
+
].join('\n')
|
|
559
|
+
|
|
560
|
+
case 'IndexNotFoundError':
|
|
561
|
+
return `Error: ${error.indexType} index not found. Run 'mdcontext index' first.`
|
|
562
|
+
|
|
563
|
+
// ... other cases
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
### Recommendation 3.6: Use Effect.try for Fallible Operations
|
|
571
|
+
|
|
572
|
+
**Priority**: HIGH
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
// src/embeddings/vector-store.ts
|
|
576
|
+
add(entries: VectorEntry[]): Effect.Effect<void, VectorStoreError> {
|
|
577
|
+
return Effect.try({
|
|
578
|
+
try: () => {
|
|
579
|
+
const index = this.ensureIndex()
|
|
580
|
+
for (const entry of entries) {
|
|
581
|
+
if (this.idToIndex.has(entry.id)) continue
|
|
582
|
+
|
|
583
|
+
const idx = this.nextIndex++
|
|
584
|
+
if (idx >= index.getMaxElements()) {
|
|
585
|
+
index.resizeIndex(index.getMaxElements() * 2)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
index.addPoint(entry.embedding as number[], idx)
|
|
589
|
+
this.entries.set(idx, entry)
|
|
590
|
+
this.idToIndex.set(entry.id, idx)
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
catch: (e) => new VectorStoreError({
|
|
594
|
+
operation: 'add',
|
|
595
|
+
message: e instanceof Error ? e.message : String(e),
|
|
596
|
+
cause: e,
|
|
597
|
+
})
|
|
598
|
+
})
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
### Recommendation 3.7: Wrap JSON.parse with Error Handling
|
|
605
|
+
|
|
606
|
+
**Priority**: MEDIUM
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// src/embeddings/vector-store.ts
|
|
610
|
+
load(): Effect.Effect<boolean, VectorStoreError> {
|
|
611
|
+
return Effect.gen(function* (this: HnswVectorStore) {
|
|
612
|
+
// ...
|
|
613
|
+
const metaContent = yield* Effect.promise(() =>
|
|
614
|
+
fs.readFile(metaPath, 'utf-8'),
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
const meta = yield* Effect.try({
|
|
618
|
+
try: () => JSON.parse(metaContent) as VectorIndex,
|
|
619
|
+
catch: (e) => new VectorStoreError({
|
|
620
|
+
operation: 'load',
|
|
621
|
+
message: 'Failed to parse vector metadata',
|
|
622
|
+
cause: e,
|
|
623
|
+
})
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
// ...
|
|
627
|
+
}.bind(this))
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
### Recommendation 3.8: Update Types to Use Tagged Errors
|
|
634
|
+
|
|
635
|
+
**Priority**: MEDIUM
|
|
636
|
+
|
|
637
|
+
Replace `types.ts` `EmbedError` with proper `Data.TaggedError` or remove it in favor of the errors defined in `errors.ts`:
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
// src/embeddings/types.ts
|
|
641
|
+
// Remove the EmbedError interface and embedError factory
|
|
642
|
+
// Import from errors.ts instead:
|
|
643
|
+
export type {
|
|
644
|
+
MissingApiKeyError,
|
|
645
|
+
InvalidApiKeyError,
|
|
646
|
+
EmbeddingError,
|
|
647
|
+
// ...
|
|
648
|
+
} from './errors.js'
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## 4. Priority Summary
|
|
654
|
+
|
|
655
|
+
### HIGH Priority (Address First)
|
|
656
|
+
| Issue | Description | Impact |
|
|
657
|
+
|-------|-------------|--------|
|
|
658
|
+
| 1.1 | Non-Effect error classes | Cannot use `catchTag`, breaks type safety |
|
|
659
|
+
| 1.2 | Constructor throws exception | Errors become defects, untracked |
|
|
660
|
+
| 1.4 | Generic `Error` return types | No exhaustive error handling |
|
|
661
|
+
| 1.9 | `Effect.sync` for fallible ops | Exceptions become untracked defects |
|
|
662
|
+
|
|
663
|
+
### MEDIUM Priority
|
|
664
|
+
| Issue | Description | Impact |
|
|
665
|
+
|-------|-------------|--------|
|
|
666
|
+
| 1.3 | Unused `EmbedError` type | Code inconsistency |
|
|
667
|
+
| 1.5 | Silent error swallowing | Users blind to failures |
|
|
668
|
+
| 1.6 | Inconsistent transformation | Fragile error handling |
|
|
669
|
+
| 1.8 | `handleApiKeyError` loses type | Type safety degradation |
|
|
670
|
+
| 1.10 | Unhandled `JSON.parse` | Potential crash on corrupt data |
|
|
671
|
+
|
|
672
|
+
### LOW Priority
|
|
673
|
+
| Issue | Description | Impact |
|
|
674
|
+
|-------|-------------|--------|
|
|
675
|
+
| 1.7 | Messages in code | Harder to maintain/localize |
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## 5. Migration Strategy
|
|
680
|
+
|
|
681
|
+
### Phase 1: Create Error Types
|
|
682
|
+
1. Create `/src/embeddings/errors.ts` with `Data.TaggedError` classes
|
|
683
|
+
2. Export from module index
|
|
684
|
+
3. No breaking changes yet
|
|
685
|
+
|
|
686
|
+
### Phase 2: Update OpenAI Provider
|
|
687
|
+
1. Convert constructor to factory function
|
|
688
|
+
2. Update error classes to use new tagged errors
|
|
689
|
+
3. Remove presentation logic from `handleApiKeyError`
|
|
690
|
+
|
|
691
|
+
### Phase 3: Update Vector Store
|
|
692
|
+
1. Replace `Effect.sync` with `Effect.try`
|
|
693
|
+
2. Add error handling for `JSON.parse`
|
|
694
|
+
3. Use `VectorStoreError` for all operations
|
|
695
|
+
|
|
696
|
+
### Phase 4: Update Semantic Search
|
|
697
|
+
1. Update return types to use union of tagged errors
|
|
698
|
+
2. Replace silent catches with logged failures
|
|
699
|
+
3. Use proper error transformation with cause preservation
|
|
700
|
+
|
|
701
|
+
### Phase 5: CLI Integration
|
|
702
|
+
1. Create centralized error formatter
|
|
703
|
+
2. Update CLI commands to use `catchTags` at boundary
|
|
704
|
+
3. Add verbosity support for error detail levels
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
_Analysis created: 2026-01-22_
|
|
709
|
+
_Based on: effect-errors-as-values.md, effect-cli-error-handling.md_
|