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,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized CLI error handler
|
|
3
|
+
*
|
|
4
|
+
* This module provides a single point of error formatting and display for the CLI.
|
|
5
|
+
* It maps tagged errors to user-friendly messages with appropriate exit codes.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* program.pipe(handleCliErrors)
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Exit Codes:
|
|
13
|
+
* - 0: Success
|
|
14
|
+
* - 1: User error (invalid arguments, missing config, etc.)
|
|
15
|
+
* - 2: System error (file system, network, etc.)
|
|
16
|
+
* - 3: API error (authentication, rate limits)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Console, Effect, Match } from 'effect'
|
|
20
|
+
import type { EmbeddingProvider } from '../config/schema.js'
|
|
21
|
+
import {
|
|
22
|
+
detectProviderError,
|
|
23
|
+
getProviderErrorTitle,
|
|
24
|
+
getProviderSuggestions,
|
|
25
|
+
} from '../embeddings/provider-errors.js'
|
|
26
|
+
import type {
|
|
27
|
+
ApiKeyInvalidError,
|
|
28
|
+
ApiKeyMissingError,
|
|
29
|
+
CliValidationError,
|
|
30
|
+
ConfigError,
|
|
31
|
+
DirectoryCreateError,
|
|
32
|
+
DirectoryWalkError,
|
|
33
|
+
DocumentNotFoundError,
|
|
34
|
+
EmbeddingError,
|
|
35
|
+
EmbeddingsNotFoundError,
|
|
36
|
+
FileReadError,
|
|
37
|
+
FileWriteError,
|
|
38
|
+
IndexBuildError,
|
|
39
|
+
IndexCorruptedError,
|
|
40
|
+
IndexNotFoundError,
|
|
41
|
+
MdContextError,
|
|
42
|
+
ParseError,
|
|
43
|
+
VectorStoreError,
|
|
44
|
+
WatchError,
|
|
45
|
+
} from '../errors/index.js'
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Exit Codes
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
export const EXIT_CODE = {
|
|
52
|
+
SUCCESS: 0,
|
|
53
|
+
USER_ERROR: 1,
|
|
54
|
+
SYSTEM_ERROR: 2,
|
|
55
|
+
API_ERROR: 3,
|
|
56
|
+
} as const
|
|
57
|
+
|
|
58
|
+
export type ExitCode = (typeof EXIT_CODE)[keyof typeof EXIT_CODE]
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Formatted Error
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
export interface FormattedError {
|
|
65
|
+
readonly code: string
|
|
66
|
+
readonly message: string
|
|
67
|
+
readonly details?: string | undefined
|
|
68
|
+
readonly suggestions?: readonly string[] | undefined
|
|
69
|
+
readonly exitCode: ExitCode
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Config Error Formatter
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format a ConfigError with enhanced context information.
|
|
78
|
+
* Builds a detailed error message including source file, type expectations,
|
|
79
|
+
* and valid values when available.
|
|
80
|
+
*/
|
|
81
|
+
const formatConfigError = (e: ConfigError): FormattedError => {
|
|
82
|
+
const message = e.field
|
|
83
|
+
? `Invalid configuration: ${e.field}`
|
|
84
|
+
: 'Configuration error'
|
|
85
|
+
|
|
86
|
+
const detailParts: string[] = []
|
|
87
|
+
|
|
88
|
+
if (e.sourceFile) {
|
|
89
|
+
detailParts.push(`Source: ${e.sourceFile}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (e.expectedType) {
|
|
93
|
+
detailParts.push(`Expected: ${e.expectedType}`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (e.actualValue !== undefined) {
|
|
97
|
+
const actualStr =
|
|
98
|
+
typeof e.actualValue === 'string'
|
|
99
|
+
? `"${e.actualValue}"`
|
|
100
|
+
: String(e.actualValue)
|
|
101
|
+
detailParts.push(`Got: ${actualStr}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (e.validValues && e.validValues.length > 0) {
|
|
105
|
+
detailParts.push(`Valid values: ${e.validValues.join(', ')}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (e.message && detailParts.length === 0) {
|
|
109
|
+
detailParts.push(e.message)
|
|
110
|
+
} else if (e.message && !detailParts.some((p) => p.includes(e.message))) {
|
|
111
|
+
detailParts.unshift(e.message)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const suggestions: string[] = []
|
|
115
|
+
suggestions.push('Check your config file syntax')
|
|
116
|
+
suggestions.push("Run 'mdcontext config check' to validate configuration")
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
code: e.code,
|
|
120
|
+
message,
|
|
121
|
+
details: detailParts.length > 0 ? detailParts.join('\n ') : undefined,
|
|
122
|
+
suggestions,
|
|
123
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Error Formatter
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Format an error for user display.
|
|
133
|
+
* Returns a structured object with message, suggestions, and exit code.
|
|
134
|
+
*/
|
|
135
|
+
export const formatError = (error: MdContextError): FormattedError =>
|
|
136
|
+
Match.value(error).pipe(
|
|
137
|
+
// File system errors
|
|
138
|
+
Match.tag('FileReadError', (e) => ({
|
|
139
|
+
code: e.code,
|
|
140
|
+
message: `Cannot read file: ${e.path}`,
|
|
141
|
+
details: e.message,
|
|
142
|
+
suggestions: [
|
|
143
|
+
'Check that the file exists',
|
|
144
|
+
'Check file permissions',
|
|
145
|
+
] as const,
|
|
146
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
147
|
+
})),
|
|
148
|
+
Match.tag('FileWriteError', (e) => ({
|
|
149
|
+
code: e.code,
|
|
150
|
+
message: `Cannot write file: ${e.path}`,
|
|
151
|
+
details: e.message,
|
|
152
|
+
suggestions: [
|
|
153
|
+
'Check that the directory exists',
|
|
154
|
+
'Check write permissions',
|
|
155
|
+
'Check disk space',
|
|
156
|
+
] as const,
|
|
157
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
158
|
+
})),
|
|
159
|
+
Match.tag('DirectoryCreateError', (e) => ({
|
|
160
|
+
code: e.code,
|
|
161
|
+
message: `Cannot create directory: ${e.path}`,
|
|
162
|
+
details: e.message,
|
|
163
|
+
suggestions: [
|
|
164
|
+
'Check parent directory permissions',
|
|
165
|
+
'Check disk space',
|
|
166
|
+
] as const,
|
|
167
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
168
|
+
})),
|
|
169
|
+
Match.tag('DirectoryWalkError', (e) => ({
|
|
170
|
+
code: e.code,
|
|
171
|
+
message: `Cannot traverse directory: ${e.path}`,
|
|
172
|
+
details: e.message,
|
|
173
|
+
suggestions: [
|
|
174
|
+
'Check directory permissions',
|
|
175
|
+
'Check that the path exists',
|
|
176
|
+
] as const,
|
|
177
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
178
|
+
})),
|
|
179
|
+
|
|
180
|
+
// Parse errors
|
|
181
|
+
Match.tag('ParseError', (e) => ({
|
|
182
|
+
code: e.code,
|
|
183
|
+
message: e.path
|
|
184
|
+
? `Parse error in ${e.path}${e.line ? `:${e.line}` : ''}`
|
|
185
|
+
: 'Parse error',
|
|
186
|
+
details: e.message,
|
|
187
|
+
suggestions: ['Check the file syntax'] as const,
|
|
188
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
189
|
+
})),
|
|
190
|
+
|
|
191
|
+
// API key errors
|
|
192
|
+
Match.tag('ApiKeyMissingError', (e) => ({
|
|
193
|
+
code: e.code,
|
|
194
|
+
message: `${e.envVar} not set`,
|
|
195
|
+
suggestions: [
|
|
196
|
+
`export ${e.envVar}=your-api-key`,
|
|
197
|
+
'Or add to .env file in project root',
|
|
198
|
+
] as const,
|
|
199
|
+
exitCode: EXIT_CODE.API_ERROR,
|
|
200
|
+
})),
|
|
201
|
+
Match.tag('ApiKeyInvalidError', (e) => ({
|
|
202
|
+
code: e.code,
|
|
203
|
+
message: `Invalid API key for ${e.provider}`,
|
|
204
|
+
details: e.details,
|
|
205
|
+
suggestions: [
|
|
206
|
+
'Check that your API key is correct',
|
|
207
|
+
'Verify your API key has not expired',
|
|
208
|
+
'Check your API account status',
|
|
209
|
+
] as const,
|
|
210
|
+
exitCode: EXIT_CODE.API_ERROR,
|
|
211
|
+
})),
|
|
212
|
+
|
|
213
|
+
// Embedding errors - enhanced with provider-specific detection
|
|
214
|
+
Match.tag('EmbeddingError', (e) => {
|
|
215
|
+
// Try to detect provider-specific error for better messaging
|
|
216
|
+
const provider = (e.provider ?? 'openai') as EmbeddingProvider
|
|
217
|
+
const providerError = detectProviderError(provider, e.cause)
|
|
218
|
+
|
|
219
|
+
if (providerError) {
|
|
220
|
+
// Use provider-specific error formatting
|
|
221
|
+
return {
|
|
222
|
+
code: e.code,
|
|
223
|
+
message: getProviderErrorTitle(providerError),
|
|
224
|
+
details: e.message,
|
|
225
|
+
suggestions: getProviderSuggestions(providerError),
|
|
226
|
+
exitCode:
|
|
227
|
+
providerError.type === 'daemon-not-running' ||
|
|
228
|
+
providerError.type === 'gui-not-running' ||
|
|
229
|
+
providerError.type === 'connection-refused'
|
|
230
|
+
? EXIT_CODE.SYSTEM_ERROR
|
|
231
|
+
: EXIT_CODE.API_ERROR,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Fall back to reason-based handling
|
|
236
|
+
return Match.value(e.reason).pipe(
|
|
237
|
+
Match.when('RateLimit', () => ({
|
|
238
|
+
code: e.code,
|
|
239
|
+
message: 'Rate limit exceeded',
|
|
240
|
+
details: e.message,
|
|
241
|
+
suggestions: [
|
|
242
|
+
'Wait a few minutes and try again',
|
|
243
|
+
'Consider using a smaller batch size',
|
|
244
|
+
] as const,
|
|
245
|
+
exitCode: EXIT_CODE.API_ERROR,
|
|
246
|
+
})),
|
|
247
|
+
Match.when('QuotaExceeded', () => ({
|
|
248
|
+
code: e.code,
|
|
249
|
+
message: 'API quota exceeded',
|
|
250
|
+
details: e.message,
|
|
251
|
+
suggestions: [
|
|
252
|
+
'Check your API usage limits',
|
|
253
|
+
'Consider upgrading your API plan',
|
|
254
|
+
] as const,
|
|
255
|
+
exitCode: EXIT_CODE.API_ERROR,
|
|
256
|
+
})),
|
|
257
|
+
Match.when('Network', () => {
|
|
258
|
+
// Check for provider-specific network errors
|
|
259
|
+
const networkSuggestions =
|
|
260
|
+
provider === 'ollama'
|
|
261
|
+
? [
|
|
262
|
+
'Start the Ollama daemon: ollama serve',
|
|
263
|
+
'Install Ollama: https://ollama.com/download',
|
|
264
|
+
]
|
|
265
|
+
: provider === 'lm-studio'
|
|
266
|
+
? [
|
|
267
|
+
'Open LM Studio application',
|
|
268
|
+
'Start the local server in Developer tab',
|
|
269
|
+
]
|
|
270
|
+
: ['Check your internet connection', 'Try again later']
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
code: e.code,
|
|
274
|
+
message:
|
|
275
|
+
provider === 'ollama'
|
|
276
|
+
? 'Cannot connect to Ollama'
|
|
277
|
+
: provider === 'lm-studio'
|
|
278
|
+
? 'Cannot connect to LM Studio'
|
|
279
|
+
: 'Network error during embedding',
|
|
280
|
+
details: e.message,
|
|
281
|
+
suggestions: networkSuggestions,
|
|
282
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
283
|
+
}
|
|
284
|
+
}),
|
|
285
|
+
Match.when('ModelError', () => ({
|
|
286
|
+
code: e.code,
|
|
287
|
+
message:
|
|
288
|
+
provider === 'ollama'
|
|
289
|
+
? 'Ollama model not found'
|
|
290
|
+
: provider === 'lm-studio'
|
|
291
|
+
? 'LM Studio model not loaded'
|
|
292
|
+
: 'Model error',
|
|
293
|
+
details: e.message,
|
|
294
|
+
suggestions:
|
|
295
|
+
provider === 'ollama'
|
|
296
|
+
? [
|
|
297
|
+
'Download an embedding model: ollama pull nomic-embed-text',
|
|
298
|
+
'List available models: ollama list',
|
|
299
|
+
]
|
|
300
|
+
: provider === 'lm-studio'
|
|
301
|
+
? [
|
|
302
|
+
'Load an embedding model in LM Studio',
|
|
303
|
+
'Go to Models tab and download an embedding model',
|
|
304
|
+
]
|
|
305
|
+
: ['Check that the model name is correct'],
|
|
306
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
307
|
+
})),
|
|
308
|
+
Match.orElse(() => ({
|
|
309
|
+
code: e.code,
|
|
310
|
+
message: 'Embedding generation failed',
|
|
311
|
+
details: e.message,
|
|
312
|
+
exitCode: EXIT_CODE.API_ERROR,
|
|
313
|
+
})),
|
|
314
|
+
)
|
|
315
|
+
}),
|
|
316
|
+
|
|
317
|
+
// Index errors
|
|
318
|
+
Match.tag('IndexNotFoundError', (e) => ({
|
|
319
|
+
code: e.code,
|
|
320
|
+
message: 'Index not found',
|
|
321
|
+
details: `No index at ${e.path}`,
|
|
322
|
+
suggestions: ["Run 'mdcontext index' first to build the index"] as const,
|
|
323
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
324
|
+
})),
|
|
325
|
+
Match.tag('IndexCorruptedError', (e) => ({
|
|
326
|
+
code: e.code,
|
|
327
|
+
message: 'Index is corrupted',
|
|
328
|
+
details: e.details ?? `Corruption reason: ${e.reason}`,
|
|
329
|
+
suggestions: [
|
|
330
|
+
"Delete the .mdcontext folder and run 'mdcontext index' again",
|
|
331
|
+
] as const,
|
|
332
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
333
|
+
})),
|
|
334
|
+
Match.tag('IndexBuildError', (e) => ({
|
|
335
|
+
code: e.code,
|
|
336
|
+
message: `Failed to build index for: ${e.path}`,
|
|
337
|
+
details: e.message,
|
|
338
|
+
suggestions: ['Check the file is valid markdown'] as const,
|
|
339
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
340
|
+
})),
|
|
341
|
+
|
|
342
|
+
// Search errors
|
|
343
|
+
Match.tag('DocumentNotFoundError', (e) => ({
|
|
344
|
+
code: e.code,
|
|
345
|
+
message: `Document not found in index: ${e.path}`,
|
|
346
|
+
suggestions: [
|
|
347
|
+
"Run 'mdcontext index' to update the index",
|
|
348
|
+
'Check the file path is correct',
|
|
349
|
+
] as const,
|
|
350
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
351
|
+
})),
|
|
352
|
+
Match.tag('EmbeddingsNotFoundError', (e) => ({
|
|
353
|
+
code: e.code,
|
|
354
|
+
message: 'Embeddings not found',
|
|
355
|
+
details: `No embeddings at ${e.path}`,
|
|
356
|
+
suggestions: [
|
|
357
|
+
"Run 'mdcontext index --embed' to build embeddings for semantic search",
|
|
358
|
+
'Use -k flag for keyword search instead',
|
|
359
|
+
] as const,
|
|
360
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
361
|
+
})),
|
|
362
|
+
|
|
363
|
+
// Dimension mismatch errors
|
|
364
|
+
Match.tag('DimensionMismatchError', (e) => ({
|
|
365
|
+
code: e.code,
|
|
366
|
+
message: 'Embedding dimension mismatch',
|
|
367
|
+
details: e.message,
|
|
368
|
+
suggestions: [
|
|
369
|
+
e.corpusProvider
|
|
370
|
+
? `Switch back to original provider: --provider ${e.corpusProvider.split(':')[0]} --provider-model ${e.corpusProvider.split(':')[1] ?? ''}`
|
|
371
|
+
: 'Check your embedding provider configuration',
|
|
372
|
+
"Rebuild corpus with current provider: 'mdcontext index --embed --force'",
|
|
373
|
+
'The corpus was created with different embedding dimensions than your current provider',
|
|
374
|
+
] as const,
|
|
375
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
376
|
+
})),
|
|
377
|
+
|
|
378
|
+
// Vector store errors
|
|
379
|
+
Match.tag('VectorStoreError', (e) => ({
|
|
380
|
+
code: e.code,
|
|
381
|
+
message: `Vector store error during ${e.operation}`,
|
|
382
|
+
details: e.message,
|
|
383
|
+
suggestions: [
|
|
384
|
+
"Delete .mdcontext/embeddings and run 'mdcontext index --embed' again",
|
|
385
|
+
] as const,
|
|
386
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
387
|
+
})),
|
|
388
|
+
|
|
389
|
+
// Config errors
|
|
390
|
+
Match.tag('ConfigError', (e) => formatConfigError(e)),
|
|
391
|
+
|
|
392
|
+
// Watch errors
|
|
393
|
+
Match.tag('WatchError', (e) => ({
|
|
394
|
+
code: e.code,
|
|
395
|
+
message: `File watcher error: ${e.path}`,
|
|
396
|
+
details: e.message,
|
|
397
|
+
suggestions: [
|
|
398
|
+
'Check directory permissions',
|
|
399
|
+
'Check disk space',
|
|
400
|
+
'Try restarting the watch command',
|
|
401
|
+
] as const,
|
|
402
|
+
exitCode: EXIT_CODE.SYSTEM_ERROR,
|
|
403
|
+
})),
|
|
404
|
+
|
|
405
|
+
// CLI validation errors
|
|
406
|
+
Match.tag('CliValidationError', (e) => ({
|
|
407
|
+
code: e.code,
|
|
408
|
+
message: e.message,
|
|
409
|
+
details:
|
|
410
|
+
e.argument && e.expected
|
|
411
|
+
? `Expected ${e.expected}${e.received ? `, got ${e.received}` : ''}`
|
|
412
|
+
: undefined,
|
|
413
|
+
suggestions: ["Run 'mdcontext --help' for usage information"] as const,
|
|
414
|
+
exitCode: EXIT_CODE.USER_ERROR,
|
|
415
|
+
})),
|
|
416
|
+
|
|
417
|
+
Match.exhaustive,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
// ============================================================================
|
|
421
|
+
// Error Display
|
|
422
|
+
// ============================================================================
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Display a formatted error to stderr.
|
|
426
|
+
* This is the only place in the CLI that should output error messages.
|
|
427
|
+
*/
|
|
428
|
+
export const displayError = (
|
|
429
|
+
formatted: FormattedError,
|
|
430
|
+
): Effect.Effect<void, never> =>
|
|
431
|
+
Effect.gen(function* () {
|
|
432
|
+
yield* Console.error('')
|
|
433
|
+
yield* Console.error(`Error [${formatted.code}]: ${formatted.message}`)
|
|
434
|
+
|
|
435
|
+
if (formatted.details) {
|
|
436
|
+
yield* Console.error(` ${formatted.details}`)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (formatted.suggestions && formatted.suggestions.length > 0) {
|
|
440
|
+
yield* Console.error('')
|
|
441
|
+
for (const suggestion of formatted.suggestions) {
|
|
442
|
+
yield* Console.error(` ${suggestion}`)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
yield* Console.error('')
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Display error with debug information (stack trace, full context)
|
|
451
|
+
*/
|
|
452
|
+
export const displayErrorDebug = (
|
|
453
|
+
error: MdContextError,
|
|
454
|
+
formatted: FormattedError,
|
|
455
|
+
): Effect.Effect<void, never> =>
|
|
456
|
+
Effect.gen(function* () {
|
|
457
|
+
yield* displayError(formatted)
|
|
458
|
+
|
|
459
|
+
yield* Console.error('--- Debug Info ---')
|
|
460
|
+
yield* Console.error(`Code: ${formatted.code}`)
|
|
461
|
+
yield* Console.error(`Tag: ${error._tag}`)
|
|
462
|
+
yield* Console.error(`Error: ${JSON.stringify(error, null, 2)}`)
|
|
463
|
+
|
|
464
|
+
// Show cause/stack if available
|
|
465
|
+
if ('cause' in error && error.cause) {
|
|
466
|
+
yield* Console.error(`Cause: ${String(error.cause)}`)
|
|
467
|
+
if (error.cause instanceof Error && error.cause.stack) {
|
|
468
|
+
yield* Console.error(`Stack: ${error.cause.stack}`)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
// ============================================================================
|
|
474
|
+
// Main Handler
|
|
475
|
+
// ============================================================================
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Handle a typed error: format, display, and return appropriate exit code.
|
|
479
|
+
*/
|
|
480
|
+
export const handleError = (
|
|
481
|
+
error: MdContextError,
|
|
482
|
+
options: { debug?: boolean } = {},
|
|
483
|
+
): Effect.Effect<never, never, never> =>
|
|
484
|
+
Effect.gen(function* () {
|
|
485
|
+
const formatted = formatError(error)
|
|
486
|
+
|
|
487
|
+
if (options.debug) {
|
|
488
|
+
yield* displayErrorDebug(error, formatted)
|
|
489
|
+
} else {
|
|
490
|
+
yield* displayError(formatted)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return yield* Effect.fail(formatted.exitCode as never)
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Create an error handler that can be piped into an Effect.
|
|
498
|
+
* Handles all MdContextError types with proper formatting and exit codes.
|
|
499
|
+
*
|
|
500
|
+
* Usage:
|
|
501
|
+
* ```typescript
|
|
502
|
+
* program.pipe(
|
|
503
|
+
* Effect.catchTags(createErrorHandler())
|
|
504
|
+
* )
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
export const createErrorHandler = (options: { debug?: boolean } = {}) => ({
|
|
508
|
+
FileReadError: (e: FileReadError) => handleError(e, options),
|
|
509
|
+
FileWriteError: (e: FileWriteError) => handleError(e, options),
|
|
510
|
+
DirectoryCreateError: (e: DirectoryCreateError) => handleError(e, options),
|
|
511
|
+
DirectoryWalkError: (e: DirectoryWalkError) => handleError(e, options),
|
|
512
|
+
ParseError: (e: ParseError) => handleError(e, options),
|
|
513
|
+
ApiKeyMissingError: (e: ApiKeyMissingError) => handleError(e, options),
|
|
514
|
+
ApiKeyInvalidError: (e: ApiKeyInvalidError) => handleError(e, options),
|
|
515
|
+
EmbeddingError: (e: EmbeddingError) => handleError(e, options),
|
|
516
|
+
IndexNotFoundError: (e: IndexNotFoundError) => handleError(e, options),
|
|
517
|
+
IndexCorruptedError: (e: IndexCorruptedError) => handleError(e, options),
|
|
518
|
+
IndexBuildError: (e: IndexBuildError) => handleError(e, options),
|
|
519
|
+
DocumentNotFoundError: (e: DocumentNotFoundError) => handleError(e, options),
|
|
520
|
+
EmbeddingsNotFoundError: (e: EmbeddingsNotFoundError) =>
|
|
521
|
+
handleError(e, options),
|
|
522
|
+
VectorStoreError: (e: VectorStoreError) => handleError(e, options),
|
|
523
|
+
WatchError: (e: WatchError) => handleError(e, options),
|
|
524
|
+
ConfigError: (e: ConfigError) => handleError(e, options),
|
|
525
|
+
CliValidationError: (e: CliValidationError) => handleError(e, options),
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// Legacy Error Handling (for transition period)
|
|
530
|
+
// ============================================================================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Check if an error is an Effect CLI validation error.
|
|
534
|
+
* Used during transition to catch @effect/cli errors.
|
|
535
|
+
*/
|
|
536
|
+
export const isEffectCliValidationError = (error: unknown): boolean => {
|
|
537
|
+
if (error && typeof error === 'object') {
|
|
538
|
+
const err = error as Record<string, unknown>
|
|
539
|
+
return (
|
|
540
|
+
err._tag === 'ValidationError' ||
|
|
541
|
+
err._tag === 'MissingValue' ||
|
|
542
|
+
err._tag === 'InvalidValue' ||
|
|
543
|
+
err._tag === 'CommandDirective'
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
return false
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Extract the error message from an Effect CLI error structure.
|
|
551
|
+
*/
|
|
552
|
+
const extractEffectCliMessage = (error: unknown): string | null => {
|
|
553
|
+
if (error && typeof error === 'object') {
|
|
554
|
+
const err = error as Record<string, unknown>
|
|
555
|
+
if (err._tag === 'Paragraph' && err.value) {
|
|
556
|
+
const paragraph = err.value as Record<string, unknown>
|
|
557
|
+
if (paragraph._tag === 'Text' && typeof paragraph.value === 'string') {
|
|
558
|
+
return paragraph.value
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return null
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Extract user's invalid input from CLI arguments.
|
|
567
|
+
* Looks for --mode value in process.argv.
|
|
568
|
+
*/
|
|
569
|
+
const extractModeValue = (): string | null => {
|
|
570
|
+
const args = process.argv
|
|
571
|
+
for (let i = 0; i < args.length; i++) {
|
|
572
|
+
const arg = args[i]
|
|
573
|
+
if (arg === '--mode' || arg === '-m') {
|
|
574
|
+
return args[i + 1] ?? null
|
|
575
|
+
}
|
|
576
|
+
if (arg?.startsWith('--mode=')) {
|
|
577
|
+
return arg.slice('--mode='.length) || null
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return null
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Suggest a correction for mode flag using Levenshtein distance.
|
|
585
|
+
*/
|
|
586
|
+
const suggestModeCorrection = (invalidMode: string): string | null => {
|
|
587
|
+
const validModes = ['hybrid', 'semantic', 'keyword']
|
|
588
|
+
|
|
589
|
+
const levenshtein = (a: string, b: string): number => {
|
|
590
|
+
const matrix: number[][] = []
|
|
591
|
+
for (let i = 0; i <= a.length; i++) matrix[i] = [i]
|
|
592
|
+
for (let j = 0; j <= b.length; j++) matrix[0]![j] = j
|
|
593
|
+
|
|
594
|
+
for (let i = 1; i <= a.length; i++) {
|
|
595
|
+
for (let j = 1; j <= b.length; j++) {
|
|
596
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1
|
|
597
|
+
matrix[i]![j] = Math.min(
|
|
598
|
+
matrix[i - 1]![j]! + 1,
|
|
599
|
+
matrix[i]![j - 1]! + 1,
|
|
600
|
+
matrix[i - 1]![j - 1]! + cost,
|
|
601
|
+
)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return matrix[a.length]![b.length]!
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
let bestMatch: string | null = null
|
|
608
|
+
let bestDistance = Infinity
|
|
609
|
+
const maxDistance = 2
|
|
610
|
+
|
|
611
|
+
for (const mode of validModes) {
|
|
612
|
+
const distance = levenshtein(invalidMode.toLowerCase(), mode)
|
|
613
|
+
if (distance <= maxDistance && distance < bestDistance) {
|
|
614
|
+
bestDistance = distance
|
|
615
|
+
bestMatch = mode
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return bestMatch
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Check if error is a mode validation error and enhance message with suggestion.
|
|
624
|
+
*/
|
|
625
|
+
const enhanceModeError = (baseMessage: string): string => {
|
|
626
|
+
const modeValue = extractModeValue()
|
|
627
|
+
if (!modeValue) return baseMessage
|
|
628
|
+
|
|
629
|
+
const suggestion = suggestModeCorrection(modeValue)
|
|
630
|
+
if (!suggestion) return baseMessage
|
|
631
|
+
|
|
632
|
+
return `${baseMessage}\n\nDid you mean '--mode ${suggestion}'?`
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Format an Effect CLI validation error for display.
|
|
637
|
+
*/
|
|
638
|
+
export const formatEffectCliError = (error: unknown): string => {
|
|
639
|
+
if (error && typeof error === 'object') {
|
|
640
|
+
const err = error as Record<string, unknown>
|
|
641
|
+
|
|
642
|
+
if (
|
|
643
|
+
(err._tag === 'ValidationError' ||
|
|
644
|
+
err._tag === 'MissingValue' ||
|
|
645
|
+
err._tag === 'InvalidValue') &&
|
|
646
|
+
err.error
|
|
647
|
+
) {
|
|
648
|
+
const message = extractEffectCliMessage(err.error)
|
|
649
|
+
if (message) {
|
|
650
|
+
return enhanceModeError(message)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return String(error)
|
|
655
|
+
}
|