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
package/src/cli/main.ts
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* mdcontext CLI - Token-efficient markdown analysis
|
|
5
|
+
*
|
|
6
|
+
* CORE COMMANDS
|
|
7
|
+
* mdcontext index [path] Index markdown files (default: .)
|
|
8
|
+
* mdcontext search <query> [path] Search by meaning or structure
|
|
9
|
+
* mdcontext context <files...> Get LLM-ready summary
|
|
10
|
+
* mdcontext tree [path|file] Show files or document outline
|
|
11
|
+
*
|
|
12
|
+
* LINK ANALYSIS
|
|
13
|
+
* mdcontext links <file> What does this link to?
|
|
14
|
+
* mdcontext backlinks <file> What links to this?
|
|
15
|
+
*
|
|
16
|
+
* INSPECTION
|
|
17
|
+
* mdcontext stats [path] Index statistics
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as fs from 'node:fs'
|
|
21
|
+
import { createRequire } from 'node:module'
|
|
22
|
+
import * as path from 'node:path'
|
|
23
|
+
import * as util from 'node:util'
|
|
24
|
+
|
|
25
|
+
// Read version from package.json using createRequire for ESM compatibility
|
|
26
|
+
const require = createRequire(import.meta.url)
|
|
27
|
+
const packageJson = require('../../package.json') as { version: string }
|
|
28
|
+
const CLI_VERSION: string = packageJson.version
|
|
29
|
+
|
|
30
|
+
import { CliConfig, Command } from '@effect/cli'
|
|
31
|
+
import { NodeContext, NodeRuntime } from '@effect/platform-node'
|
|
32
|
+
import { Effect, Layer } from 'effect'
|
|
33
|
+
import { ConfigService, createConfigProviderSync } from '../config/index.js'
|
|
34
|
+
import { MdContextConfig } from '../config/schema.js'
|
|
35
|
+
import type { PartialMdContextConfig } from '../config/service.js'
|
|
36
|
+
import { preprocessArgv } from './argv-preprocessor.js'
|
|
37
|
+
import {
|
|
38
|
+
backlinksCommand,
|
|
39
|
+
configCommand,
|
|
40
|
+
contextCommand,
|
|
41
|
+
duplicatesCommand,
|
|
42
|
+
embeddingsCommand,
|
|
43
|
+
indexCommand,
|
|
44
|
+
linksCommand,
|
|
45
|
+
searchCommand,
|
|
46
|
+
statsCommand,
|
|
47
|
+
treeCommand,
|
|
48
|
+
} from './commands/index.js'
|
|
49
|
+
import { defaultCliConfigLayerSync } from './config-layer.js'
|
|
50
|
+
import {
|
|
51
|
+
formatEffectCliError,
|
|
52
|
+
isEffectCliValidationError,
|
|
53
|
+
} from './error-handler.js'
|
|
54
|
+
import {
|
|
55
|
+
checkBareSubcommandHelp,
|
|
56
|
+
checkSubcommandHelp,
|
|
57
|
+
shouldShowMainHelp,
|
|
58
|
+
showMainHelp,
|
|
59
|
+
} from './help.js'
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Main CLI
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
const mainCommand = Command.make('mdcontext').pipe(
|
|
66
|
+
Command.withDescription('Token-efficient markdown analysis for LLMs'),
|
|
67
|
+
Command.withSubcommands([
|
|
68
|
+
indexCommand,
|
|
69
|
+
searchCommand,
|
|
70
|
+
contextCommand,
|
|
71
|
+
treeCommand,
|
|
72
|
+
linksCommand,
|
|
73
|
+
backlinksCommand,
|
|
74
|
+
duplicatesCommand,
|
|
75
|
+
statsCommand,
|
|
76
|
+
configCommand,
|
|
77
|
+
embeddingsCommand,
|
|
78
|
+
]),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const cli = Command.run(mainCommand, {
|
|
82
|
+
name: 'mdcontext',
|
|
83
|
+
version: CLI_VERSION,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Clean CLI config: hide built-in options from help
|
|
87
|
+
const cliConfigLayer = CliConfig.layer({
|
|
88
|
+
showBuiltIns: false,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Error Handling
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
// Note: Error formatting and validation checking moved to error-handler.ts
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Custom Help Handling
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
// Check for subcommand help before anything else
|
|
102
|
+
checkSubcommandHelp()
|
|
103
|
+
|
|
104
|
+
// Check for bare subcommand that has nested subcommands (e.g., "config")
|
|
105
|
+
checkBareSubcommandHelp()
|
|
106
|
+
|
|
107
|
+
// Check if we should show main help
|
|
108
|
+
if (shouldShowMainHelp()) {
|
|
109
|
+
showMainHelp()
|
|
110
|
+
process.exit(0)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Preprocess argv to allow flexible flag positioning
|
|
114
|
+
const processedArgv = preprocessArgv(process.argv)
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Global --config Flag Handling
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract --config or -c flag from argv before CLI parsing.
|
|
122
|
+
* This allows loading custom config files before the CLI runs.
|
|
123
|
+
*/
|
|
124
|
+
const extractConfigPath = (
|
|
125
|
+
argv: string[],
|
|
126
|
+
): { configPath: string | undefined; filteredArgv: string[] } => {
|
|
127
|
+
const filteredArgv: string[] = []
|
|
128
|
+
let configPath: string | undefined
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < argv.length; i++) {
|
|
131
|
+
const arg = argv[i]
|
|
132
|
+
if (arg === undefined) continue
|
|
133
|
+
|
|
134
|
+
// --config=path or -c=path
|
|
135
|
+
if (arg.startsWith('--config=')) {
|
|
136
|
+
const value = arg.slice('--config='.length)
|
|
137
|
+
if (value.length === 0) {
|
|
138
|
+
console.error('\nError: --config requires a path')
|
|
139
|
+
console.error(' Usage: --config=path/to/config.js')
|
|
140
|
+
process.exit(1)
|
|
141
|
+
}
|
|
142
|
+
configPath = value
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
if (arg.startsWith('-c=')) {
|
|
146
|
+
const value = arg.slice('-c='.length)
|
|
147
|
+
if (value.length === 0) {
|
|
148
|
+
console.error('\nError: -c requires a path')
|
|
149
|
+
console.error(' Usage: -c=path/to/config.js')
|
|
150
|
+
process.exit(1)
|
|
151
|
+
}
|
|
152
|
+
configPath = value
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --config path or -c path
|
|
157
|
+
if (arg === '--config' || arg === '-c') {
|
|
158
|
+
const nextArg = argv[i + 1]
|
|
159
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
160
|
+
console.error('\nError: --config requires a path')
|
|
161
|
+
console.error(' Usage: --config path/to/config.js')
|
|
162
|
+
process.exit(1)
|
|
163
|
+
}
|
|
164
|
+
if (nextArg.length === 0) {
|
|
165
|
+
console.error('\nError: --config path cannot be empty')
|
|
166
|
+
process.exit(1)
|
|
167
|
+
}
|
|
168
|
+
configPath = nextArg
|
|
169
|
+
i++ // Skip the path argument
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
filteredArgv.push(arg)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { configPath, filteredArgv }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Extract config path from processed argv
|
|
180
|
+
const { configPath: customConfigPath, filteredArgv } =
|
|
181
|
+
extractConfigPath(processedArgv)
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Config Loading Utilities (shared between async and sync paths)
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate config file exists and exit with error if not found.
|
|
189
|
+
*/
|
|
190
|
+
function validateConfigFileExists(resolvedPath: string): void {
|
|
191
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
192
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
193
|
+
process.exit(1)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Handle config loading error with consistent formatting.
|
|
199
|
+
*/
|
|
200
|
+
const handleConfigLoadError = (error: unknown, resolvedPath: string): never => {
|
|
201
|
+
console.error(`\nError: Failed to load config file: ${resolvedPath}`)
|
|
202
|
+
if (error instanceof Error) {
|
|
203
|
+
console.error(` ${error.message}`)
|
|
204
|
+
}
|
|
205
|
+
process.exit(1)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Valid top-level config keys that the config system recognizes.
|
|
210
|
+
*/
|
|
211
|
+
const VALID_CONFIG_KEYS = [
|
|
212
|
+
'index',
|
|
213
|
+
'search',
|
|
214
|
+
'embeddings',
|
|
215
|
+
'summarization',
|
|
216
|
+
'output',
|
|
217
|
+
'paths',
|
|
218
|
+
] as const
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Validate that a loaded config is a valid object (not null, not array).
|
|
222
|
+
* Also validates that if it has keys, at least one is a recognized config key.
|
|
223
|
+
* Uses assertion function to narrow type for TypeScript.
|
|
224
|
+
*/
|
|
225
|
+
function validateConfigObject(
|
|
226
|
+
config: unknown,
|
|
227
|
+
resolvedPath: string,
|
|
228
|
+
): asserts config is PartialMdContextConfig {
|
|
229
|
+
// Check it's a non-null, non-array object
|
|
230
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
231
|
+
console.error(
|
|
232
|
+
`\nError: Config file must export a default object or named "config" export`,
|
|
233
|
+
)
|
|
234
|
+
console.error(` File: ${resolvedPath}`)
|
|
235
|
+
process.exit(1)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Validate structure - if there are keys, at least one should be recognized
|
|
239
|
+
const configKeys = Object.keys(config)
|
|
240
|
+
const hasValidKey = configKeys.some((key) =>
|
|
241
|
+
VALID_CONFIG_KEYS.includes(key as (typeof VALID_CONFIG_KEYS)[number]),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if (configKeys.length > 0 && !hasValidKey) {
|
|
245
|
+
console.error(`\nError: Config file has no recognized configuration keys`)
|
|
246
|
+
console.error(` File: ${resolvedPath}`)
|
|
247
|
+
console.error(` Found keys: ${configKeys.join(', ')}`)
|
|
248
|
+
console.error(` Expected at least one of: ${VALID_CONFIG_KEYS.join(', ')}`)
|
|
249
|
+
process.exit(1)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create a ConfigService Layer from a validated config object.
|
|
255
|
+
*/
|
|
256
|
+
const createConfigLayerFromConfig = (
|
|
257
|
+
fileConfig: PartialMdContextConfig,
|
|
258
|
+
): Layer.Layer<ConfigService, never, never> => {
|
|
259
|
+
const provider = createConfigProviderSync({
|
|
260
|
+
fileConfig,
|
|
261
|
+
skipEnv: false,
|
|
262
|
+
})
|
|
263
|
+
const configResult = Effect.runSync(
|
|
264
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
265
|
+
)
|
|
266
|
+
return Layer.succeed(ConfigService, configResult)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Load a TS/JS/MJS config file asynchronously using dynamic import.
|
|
271
|
+
* Returns a promise that resolves to a ConfigService Layer.
|
|
272
|
+
*/
|
|
273
|
+
async function loadConfigAsync(
|
|
274
|
+
configPath: string,
|
|
275
|
+
): Promise<Layer.Layer<ConfigService, never, never>> {
|
|
276
|
+
const resolvedPath = path.resolve(configPath)
|
|
277
|
+
validateConfigFileExists(resolvedPath)
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
// Use dynamic import to load TS/JS/MJS files
|
|
281
|
+
const fileUrl = `file://${resolvedPath}`
|
|
282
|
+
const module = (await import(fileUrl)) as {
|
|
283
|
+
default?: PartialMdContextConfig
|
|
284
|
+
config?: PartialMdContextConfig
|
|
285
|
+
}
|
|
286
|
+
const fileConfig = module.default ?? module.config
|
|
287
|
+
|
|
288
|
+
validateConfigObject(fileConfig, resolvedPath)
|
|
289
|
+
return createConfigLayerFromConfig(fileConfig)
|
|
290
|
+
} catch (error) {
|
|
291
|
+
// handleConfigLoadError calls process.exit(1) and never returns
|
|
292
|
+
// TypeScript needs explicit return for type checking - this is unreachable
|
|
293
|
+
return handleConfigLoadError(error, resolvedPath)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Determine if we need async loading (for TS/JS config files).
|
|
299
|
+
* All non-JSON config files need async loading via dynamic import.
|
|
300
|
+
*/
|
|
301
|
+
const needsAsyncLoading = (configPath: string | undefined): boolean => {
|
|
302
|
+
if (!configPath) return false
|
|
303
|
+
const ext = path.extname(configPath).toLowerCase()
|
|
304
|
+
// Async load for all JS/TS variants, sync for JSON only
|
|
305
|
+
return ext !== '.json'
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Create config layer synchronously (for JSON or no custom config).
|
|
310
|
+
*/
|
|
311
|
+
function createConfigLayerSync(): Layer.Layer<ConfigService, never, never> {
|
|
312
|
+
if (!customConfigPath) {
|
|
313
|
+
return defaultCliConfigLayerSync
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const resolvedPath = path.resolve(customConfigPath)
|
|
317
|
+
validateConfigFileExists(resolvedPath)
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8')
|
|
321
|
+
|
|
322
|
+
// Parse JSON with proper validation
|
|
323
|
+
let parsed: unknown
|
|
324
|
+
try {
|
|
325
|
+
parsed = JSON.parse(content)
|
|
326
|
+
} catch (parseError) {
|
|
327
|
+
console.error(`\nError: Invalid JSON in config file: ${resolvedPath}`)
|
|
328
|
+
console.error(
|
|
329
|
+
` ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
330
|
+
)
|
|
331
|
+
process.exit(1)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Validate structure before using
|
|
335
|
+
validateConfigObject(parsed, resolvedPath)
|
|
336
|
+
return createConfigLayerFromConfig(parsed)
|
|
337
|
+
} catch (error) {
|
|
338
|
+
// handleConfigLoadError calls process.exit(1) and never returns
|
|
339
|
+
return handleConfigLoadError(error, resolvedPath)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Run the CLI with error handling.
|
|
345
|
+
*/
|
|
346
|
+
const runCli = (
|
|
347
|
+
configLayer: Layer.Layer<ConfigService, never, never>,
|
|
348
|
+
): void => {
|
|
349
|
+
const appLayers = Layer.mergeAll(
|
|
350
|
+
NodeContext.layer,
|
|
351
|
+
cliConfigLayer,
|
|
352
|
+
configLayer,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
Effect.suspend(() => cli(filteredArgv)).pipe(
|
|
356
|
+
Effect.provide(appLayers),
|
|
357
|
+
Effect.tap(() =>
|
|
358
|
+
Effect.sync(() => {
|
|
359
|
+
// Force exit after successful completion to prevent hanging
|
|
360
|
+
// This is necessary because some dependencies (like OpenAI SDK)
|
|
361
|
+
// may keep the event loop alive with HTTP keep-alive connections
|
|
362
|
+
setImmediate(() => process.exit(0))
|
|
363
|
+
}),
|
|
364
|
+
),
|
|
365
|
+
Effect.catchAll((error) =>
|
|
366
|
+
Effect.sync(() => {
|
|
367
|
+
if (isEffectCliValidationError(error)) {
|
|
368
|
+
const message = formatEffectCliError(error)
|
|
369
|
+
console.error(`\nError: ${message}`)
|
|
370
|
+
console.error('\nRun "mdcontext --help" for usage information.')
|
|
371
|
+
process.exit(1)
|
|
372
|
+
}
|
|
373
|
+
// Handle all other unexpected errors instead of rethrowing
|
|
374
|
+
console.error('\nUnexpected error:')
|
|
375
|
+
if (error instanceof Error) {
|
|
376
|
+
console.error(` ${error.message}`)
|
|
377
|
+
if (error.stack) {
|
|
378
|
+
console.error(`\nStack trace:`)
|
|
379
|
+
console.error(error.stack)
|
|
380
|
+
}
|
|
381
|
+
} else {
|
|
382
|
+
console.error(util.inspect(error, { depth: null }))
|
|
383
|
+
}
|
|
384
|
+
process.exit(2)
|
|
385
|
+
}),
|
|
386
|
+
),
|
|
387
|
+
NodeRuntime.runMain,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Handle async vs sync config loading based on file type
|
|
392
|
+
if (needsAsyncLoading(customConfigPath)) {
|
|
393
|
+
// Async path for TS/JS/MJS config files using async/await with proper error handling
|
|
394
|
+
;(async () => {
|
|
395
|
+
// Runtime check for config path - TypeScript can't verify needsAsyncLoading's guard
|
|
396
|
+
if (!customConfigPath) {
|
|
397
|
+
console.error('\nError: Config path is required for async loading')
|
|
398
|
+
process.exit(1)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const configLayer = await loadConfigAsync(customConfigPath)
|
|
403
|
+
runCli(configLayer)
|
|
404
|
+
} catch (error) {
|
|
405
|
+
// This catches errors from runCli, not loadConfigAsync
|
|
406
|
+
// (loadConfigAsync has its own error handling that calls process.exit)
|
|
407
|
+
console.error(`\nError: Failed to initialize CLI`)
|
|
408
|
+
if (error instanceof Error) {
|
|
409
|
+
console.error(` ${error.message}`)
|
|
410
|
+
if (error.stack) {
|
|
411
|
+
console.error(`\nStack trace:`)
|
|
412
|
+
console.error(error.stack)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
process.exit(1)
|
|
416
|
+
}
|
|
417
|
+
})().catch((error) => {
|
|
418
|
+
// Catch any errors that escape the try-catch (e.g., errors before try block)
|
|
419
|
+
console.error('\nUnexpected error during initialization')
|
|
420
|
+
if (error instanceof Error) {
|
|
421
|
+
console.error(` ${error.message}`)
|
|
422
|
+
if (error.stack) {
|
|
423
|
+
console.error(`\nStack trace:`)
|
|
424
|
+
console.error(error.stack)
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
console.error(util.inspect(error, { depth: null }))
|
|
428
|
+
}
|
|
429
|
+
process.exit(1)
|
|
430
|
+
})
|
|
431
|
+
} else {
|
|
432
|
+
// Sync path for JSON configs or no custom config
|
|
433
|
+
const configLayer = createConfigLayerSync()
|
|
434
|
+
runCli(configLayer)
|
|
435
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CLI Options
|
|
3
|
+
*
|
|
4
|
+
* Common options used across multiple commands.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Options } from '@effect/cli'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Global config file path override
|
|
11
|
+
* Allows specifying a custom config file instead of auto-detection.
|
|
12
|
+
*/
|
|
13
|
+
export const configOption = Options.file('config').pipe(
|
|
14
|
+
Options.withAlias('c'),
|
|
15
|
+
Options.withDescription('Path to config file'),
|
|
16
|
+
Options.optional,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Output as JSON
|
|
21
|
+
*/
|
|
22
|
+
export const jsonOption = Options.boolean('json').pipe(
|
|
23
|
+
Options.withDescription('Output as JSON'),
|
|
24
|
+
Options.withDefault(false),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pretty-print JSON output
|
|
29
|
+
*/
|
|
30
|
+
export const prettyOption = Options.boolean('pretty').pipe(
|
|
31
|
+
Options.withDescription('Pretty-print JSON output'),
|
|
32
|
+
Options.withDefault(true),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Force full rebuild
|
|
37
|
+
*/
|
|
38
|
+
export const forceOption = Options.boolean('force').pipe(
|
|
39
|
+
Options.withDescription('Force full rebuild, ignoring cache'),
|
|
40
|
+
Options.withDefault(false),
|
|
41
|
+
)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Error Handling Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides reusable error handling patterns for CLI commands.
|
|
5
|
+
* It eliminates duplication in catchTags blocks across index-cmd.ts and search.ts.
|
|
6
|
+
*
|
|
7
|
+
* ## Design Principles
|
|
8
|
+
*
|
|
9
|
+
* 1. **Use proper Effect composition** - Never use Effect.runSync inside error handlers.
|
|
10
|
+
* Instead, return Effect values that compose properly in the Effect pipeline.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Graceful degradation** - For optional operations, return null on failure
|
|
13
|
+
* and let the caller decide what to do next.
|
|
14
|
+
*
|
|
15
|
+
* 3. **Consistent logging** - Use Effect.logWarning for debugging info and
|
|
16
|
+
* Console.error for user-facing errors. Support silent mode for JSON output.
|
|
17
|
+
*
|
|
18
|
+
* ## When to Catch vs Propagate
|
|
19
|
+
*
|
|
20
|
+
* **CATCH errors when:**
|
|
21
|
+
* - The operation is optional (e.g., cost estimate for user prompt)
|
|
22
|
+
* - Failure should fall back gracefully (e.g., auto-index attempt)
|
|
23
|
+
*
|
|
24
|
+
* **PROPAGATE errors when:**
|
|
25
|
+
* - The operation is required for the command to succeed
|
|
26
|
+
* - The centralized error handler (error-handler.ts) should format the message
|
|
27
|
+
*
|
|
28
|
+
* ## Usage
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const result = yield* someOperation.pipe(
|
|
31
|
+
* Effect.map((r): SomeType | null => r),
|
|
32
|
+
* Effect.catchTags(createEmbeddingErrorHandler({ silent: json }))
|
|
33
|
+
* )
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { Console, Effect } from 'effect'
|
|
38
|
+
import type { BuildEmbeddingsResult } from '../embeddings/semantic-search.js'
|
|
39
|
+
import type {
|
|
40
|
+
ApiKeyInvalidError,
|
|
41
|
+
ApiKeyMissingError,
|
|
42
|
+
EmbeddingError,
|
|
43
|
+
FileReadError,
|
|
44
|
+
IndexCorruptedError,
|
|
45
|
+
IndexNotFoundError,
|
|
46
|
+
VectorStoreError,
|
|
47
|
+
} from '../errors/index.js'
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Types
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Options for error handlers.
|
|
55
|
+
*/
|
|
56
|
+
export interface ErrorHandlerOptions {
|
|
57
|
+
/**
|
|
58
|
+
* When true, suppress error output (for JSON mode).
|
|
59
|
+
*/
|
|
60
|
+
readonly silent?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A result type that can be null when operation fails gracefully.
|
|
65
|
+
*/
|
|
66
|
+
export type NullableResult<T> = T | null
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Logging Helpers
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Log an error message if not in silent mode.
|
|
74
|
+
* Returns an Effect that can be composed properly.
|
|
75
|
+
*/
|
|
76
|
+
const logErrorUnlessSilent = (
|
|
77
|
+
message: string,
|
|
78
|
+
silent: boolean,
|
|
79
|
+
): Effect.Effect<void, never, never> =>
|
|
80
|
+
silent ? Effect.void : Console.error(message)
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Log a warning message if not in silent mode.
|
|
84
|
+
* Returns an Effect that can be composed properly.
|
|
85
|
+
*/
|
|
86
|
+
const logWarningUnlessSilent = (
|
|
87
|
+
message: string,
|
|
88
|
+
silent: boolean,
|
|
89
|
+
): Effect.Effect<void, never, never> =>
|
|
90
|
+
silent ? Effect.void : Effect.logWarning(message)
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Index/File Error Handlers
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a handler for index-related errors that returns null on failure.
|
|
98
|
+
* Use this for operations where index errors should gracefully degrade.
|
|
99
|
+
*/
|
|
100
|
+
export const createIndexErrorHandler = <T>(options: ErrorHandlerOptions = {}) =>
|
|
101
|
+
({
|
|
102
|
+
IndexNotFoundError: (_e: IndexNotFoundError) =>
|
|
103
|
+
Effect.succeed(null as NullableResult<T>),
|
|
104
|
+
FileReadError: (e: FileReadError) =>
|
|
105
|
+
logWarningUnlessSilent(
|
|
106
|
+
`Could not read index files: ${e.message}`,
|
|
107
|
+
options.silent ?? false,
|
|
108
|
+
).pipe(Effect.map(() => null as NullableResult<T>)),
|
|
109
|
+
IndexCorruptedError: (e: IndexCorruptedError) =>
|
|
110
|
+
logWarningUnlessSilent(
|
|
111
|
+
`Index is corrupted: ${e.details ?? e.reason}`,
|
|
112
|
+
options.silent ?? false,
|
|
113
|
+
).pipe(Effect.map(() => null as NullableResult<T>)),
|
|
114
|
+
}) as const
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Embedding Error Handlers
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a comprehensive handler for embedding-related errors.
|
|
122
|
+
* Handles API key errors, index errors, embedding errors, and vector store errors.
|
|
123
|
+
* Returns null on failure for graceful degradation.
|
|
124
|
+
*
|
|
125
|
+
* Use this for:
|
|
126
|
+
* - Optional embedding operations (e.g., user prompt in index command)
|
|
127
|
+
* - Auto-index attempts in search command
|
|
128
|
+
* - Any embedding operation that should fall back gracefully
|
|
129
|
+
*/
|
|
130
|
+
export const createEmbeddingErrorHandler = (
|
|
131
|
+
options: ErrorHandlerOptions = {},
|
|
132
|
+
) => {
|
|
133
|
+
const silent = options.silent ?? false
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
// API key errors - user needs to set up API key
|
|
137
|
+
ApiKeyMissingError: (e: ApiKeyMissingError) =>
|
|
138
|
+
logErrorUnlessSilent(`\n${e.message}`, silent).pipe(
|
|
139
|
+
Effect.map(() => null as BuildEmbeddingsResult | null),
|
|
140
|
+
),
|
|
141
|
+
ApiKeyInvalidError: (e: ApiKeyInvalidError) =>
|
|
142
|
+
logErrorUnlessSilent(`\n${e.message}`, silent).pipe(
|
|
143
|
+
Effect.map(() => null as BuildEmbeddingsResult | null),
|
|
144
|
+
),
|
|
145
|
+
// Index not found - shouldn't happen after buildIndex but handle gracefully
|
|
146
|
+
IndexNotFoundError: (_e: IndexNotFoundError) =>
|
|
147
|
+
Effect.succeed(null as BuildEmbeddingsResult | null),
|
|
148
|
+
// File system errors
|
|
149
|
+
FileReadError: (e: FileReadError) =>
|
|
150
|
+
logErrorUnlessSilent(
|
|
151
|
+
`\nCannot read index files: ${e.message}`,
|
|
152
|
+
silent,
|
|
153
|
+
).pipe(Effect.map(() => null as BuildEmbeddingsResult | null)),
|
|
154
|
+
IndexCorruptedError: (e: IndexCorruptedError) =>
|
|
155
|
+
logErrorUnlessSilent(
|
|
156
|
+
`\nIndex is corrupted: ${e.details ?? e.reason}`,
|
|
157
|
+
silent,
|
|
158
|
+
).pipe(Effect.map(() => null as BuildEmbeddingsResult | null)),
|
|
159
|
+
// Embedding errors - network, rate limit, etc
|
|
160
|
+
EmbeddingError: (e: EmbeddingError) =>
|
|
161
|
+
logErrorUnlessSilent(`\nEmbedding failed: ${e.message}`, silent).pipe(
|
|
162
|
+
Effect.map(() => null as BuildEmbeddingsResult | null),
|
|
163
|
+
),
|
|
164
|
+
// Vector store errors
|
|
165
|
+
VectorStoreError: (e: VectorStoreError) =>
|
|
166
|
+
logErrorUnlessSilent(`\nVector store error: ${e.message}`, silent).pipe(
|
|
167
|
+
Effect.map(() => null as BuildEmbeddingsResult | null),
|
|
168
|
+
),
|
|
169
|
+
} as const
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create a handler for cost estimation errors.
|
|
174
|
+
* Returns null on failure for graceful degradation.
|
|
175
|
+
*
|
|
176
|
+
* NOTE: Use with Effect.catchTags after Effect.map to preserve type:
|
|
177
|
+
* ```typescript
|
|
178
|
+
* const result = yield* operation.pipe(
|
|
179
|
+
* Effect.map((r): TargetType | null => r),
|
|
180
|
+
* Effect.catchTags(createCostEstimateErrorHandler())
|
|
181
|
+
* )
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export const createCostEstimateErrorHandler = (
|
|
185
|
+
options: ErrorHandlerOptions = {},
|
|
186
|
+
) =>
|
|
187
|
+
({
|
|
188
|
+
IndexNotFoundError: (_e: IndexNotFoundError) => Effect.succeed(null),
|
|
189
|
+
FileReadError: (e: FileReadError) =>
|
|
190
|
+
logWarningUnlessSilent(
|
|
191
|
+
`Could not read index files: ${e.message}`,
|
|
192
|
+
options.silent ?? false,
|
|
193
|
+
).pipe(Effect.map(() => null)),
|
|
194
|
+
IndexCorruptedError: (e: IndexCorruptedError) =>
|
|
195
|
+
logWarningUnlessSilent(
|
|
196
|
+
`Index is corrupted: ${e.details ?? e.reason}`,
|
|
197
|
+
options.silent ?? false,
|
|
198
|
+
).pipe(Effect.map(() => null)),
|
|
199
|
+
}) as const
|