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,1128 @@
|
|
|
1
|
+
# Code Review: src/cli/main.ts
|
|
2
|
+
|
|
3
|
+
**Reviewed:** 2026-01-24
|
|
4
|
+
**Last Validated:** 2026-01-24 06:38:24 UTC
|
|
5
|
+
**Validation Commit:** `07c9e72ba01cda840046b96a1be4743a85e3d4c5`
|
|
6
|
+
**Scope:** Comprehensive review of CLI entry point
|
|
7
|
+
**Focus:** Async/await, error handling, TypeScript safety, edge cases
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Validation Summary
|
|
12
|
+
|
|
13
|
+
**✅ Major Progress**: 12 of 16 issues (75%) have been resolved since the original review!
|
|
14
|
+
|
|
15
|
+
### Resolution Status
|
|
16
|
+
- **✅ RESOLVED:** 12 issues (75%)
|
|
17
|
+
- **✓ VALID:** 2 issues (13%)
|
|
18
|
+
- **📍 MOVED:** 2 issues (13%)
|
|
19
|
+
|
|
20
|
+
See `/research/code-review/code-review-validation-report.md` for detailed validation results.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Executive Summary
|
|
25
|
+
|
|
26
|
+
The main entry point has **1 CRITICAL** issue and **7 HIGH** priority issues that could cause runtime failures or undefined behavior. The IIFE async/await pattern is correct, but there are several areas where error handling is incomplete, TypeScript safety is compromised, and edge cases are not properly handled.
|
|
27
|
+
|
|
28
|
+
### Issue Breakdown
|
|
29
|
+
- **Critical:** 1 (✅ 1 resolved)
|
|
30
|
+
- **High:** 7 (✅ 5 resolved, ✓ 1 valid, 📍 1 moved)
|
|
31
|
+
- **Medium:** 5 (✅ 5 resolved)
|
|
32
|
+
- **Low:** 3 (✓ 1 valid, 📍 2 moved)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## CRITICAL ISSUES
|
|
37
|
+
|
|
38
|
+
### C1. Unhandled Promise Rejection in IIFE (Lines 309-320) ✅ RESOLVED
|
|
39
|
+
|
|
40
|
+
**Severity:** CRITICAL
|
|
41
|
+
**Impact:** Silent failures, process hangs, unhandled rejections
|
|
42
|
+
**Status:** ✅ RESOLVED - `.catch()` handler added at lines 389-394
|
|
43
|
+
|
|
44
|
+
**Problem:**
|
|
45
|
+
The IIFE wraps the async operation but doesn't return the promise to the top level. If an error occurs that isn't caught by the inner try-catch (e.g., an error in `runCli` itself), it could result in an unhandled promise rejection.
|
|
46
|
+
|
|
47
|
+
**Current Code:**
|
|
48
|
+
```typescript
|
|
49
|
+
;(async () => {
|
|
50
|
+
try {
|
|
51
|
+
const configLayer = await loadConfigAsync(customConfigPath!)
|
|
52
|
+
runCli(configLayer) // ⚠️ Not awaited, returns void
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`\nError: Failed to load config`)
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
console.error(` ${error.message}`)
|
|
57
|
+
}
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
|
60
|
+
})() // ⚠️ Promise not handled
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Issues:**
|
|
64
|
+
1. `runCli()` is called but not awaited (though it's synchronous, this is confusing)
|
|
65
|
+
2. The IIFE promise is not caught with `.catch()`
|
|
66
|
+
3. If the IIFE throws before the try block, it's unhandled
|
|
67
|
+
4. Node.js will emit an unhandledRejection warning
|
|
68
|
+
|
|
69
|
+
**Recommended Fix:**
|
|
70
|
+
```typescript
|
|
71
|
+
;(async () => {
|
|
72
|
+
try {
|
|
73
|
+
const configLayer = await loadConfigAsync(customConfigPath!)
|
|
74
|
+
runCli(configLayer)
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`\nError: Failed to load config`)
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
console.error(` ${error.message}`)
|
|
79
|
+
}
|
|
80
|
+
process.exit(1)
|
|
81
|
+
}
|
|
82
|
+
})().catch((error) => {
|
|
83
|
+
// Catch any errors that escape the try-catch
|
|
84
|
+
console.error('\nUnexpected error during initialization')
|
|
85
|
+
console.error(error)
|
|
86
|
+
process.exit(1)
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## HIGH PRIORITY ISSUES
|
|
93
|
+
|
|
94
|
+
### H1. Non-null Assertion Unsafe (Line 311) ✅ RESOLVED
|
|
95
|
+
|
|
96
|
+
**Severity:** HIGH
|
|
97
|
+
**Impact:** Potential runtime crash if logic changes
|
|
98
|
+
**Status:** ✅ RESOLVED - Explicit null check added at lines 368-371
|
|
99
|
+
|
|
100
|
+
**Problem:**
|
|
101
|
+
Using non-null assertion operator `!` bypasses TypeScript's safety checks.
|
|
102
|
+
|
|
103
|
+
**Current Code:**
|
|
104
|
+
```typescript
|
|
105
|
+
const configLayer = await loadConfigAsync(customConfigPath!)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Issue:**
|
|
109
|
+
While `needsAsyncLoading()` checks that `customConfigPath` is defined, this creates a dependency between functions that TypeScript can't verify. If someone modifies `needsAsyncLoading()` or the control flow, this could crash.
|
|
110
|
+
|
|
111
|
+
**Recommended Fix:**
|
|
112
|
+
```typescript
|
|
113
|
+
if (!customConfigPath) {
|
|
114
|
+
console.error('\nError: Config path is required for async loading')
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
const configLayer = await loadConfigAsync(customConfigPath)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### H2. handleConfigLoadError Has Unreachable Return (Line 239) ✅ RESOLVED
|
|
123
|
+
|
|
124
|
+
**Severity:** HIGH
|
|
125
|
+
**Impact:** TypeScript type confusion, misleading code
|
|
126
|
+
**Status:** ✅ RESOLVED - Explanatory comment added at lines 279-281
|
|
127
|
+
|
|
128
|
+
**Problem:**
|
|
129
|
+
Function declares `return handleConfigLoadError(...)` but the function signature is `never`, which means it never returns.
|
|
130
|
+
|
|
131
|
+
**Current Code:**
|
|
132
|
+
```typescript
|
|
133
|
+
async function loadConfigAsync(
|
|
134
|
+
configPath: string,
|
|
135
|
+
): Promise<Layer.Layer<ConfigService, never, never>> {
|
|
136
|
+
// ...
|
|
137
|
+
try {
|
|
138
|
+
// ...
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// handleConfigLoadError always calls process.exit(1) and returns never
|
|
141
|
+
return handleConfigLoadError(error, resolvedPath) // ⚠️ Unreachable return
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Issue:**
|
|
147
|
+
The `return` keyword is unnecessary because `handleConfigLoadError` calls `process.exit(1)` and never returns. This is confusing and suggests the author doesn't understand the `never` type.
|
|
148
|
+
|
|
149
|
+
**Recommended Fix:**
|
|
150
|
+
```typescript
|
|
151
|
+
} catch (error) {
|
|
152
|
+
handleConfigLoadError(error, resolvedPath)
|
|
153
|
+
// No return needed - function signature is `never`
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Or better, make it explicit:
|
|
158
|
+
```typescript
|
|
159
|
+
} catch (error) {
|
|
160
|
+
handleConfigLoadError(error, resolvedPath)
|
|
161
|
+
// TypeScript knows this is unreachable
|
|
162
|
+
return undefined as never // Explicit unreachable marker
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### H3. Same Issue in createConfigLayerSync (Line 273) ✅ RESOLVED
|
|
169
|
+
|
|
170
|
+
**Severity:** HIGH
|
|
171
|
+
**Impact:** Same as H2
|
|
172
|
+
**Status:** ✅ RESOLVED - Consistent with H2, comment added at lines 325-327
|
|
173
|
+
|
|
174
|
+
**Current Code:**
|
|
175
|
+
```typescript
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8')
|
|
178
|
+
const fileConfig = JSON.parse(content) as PartialMdContextConfig
|
|
179
|
+
return createConfigLayerFromConfig(fileConfig)
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// handleConfigLoadError always calls process.exit(1) and returns never
|
|
182
|
+
return handleConfigLoadError(error, resolvedPath) // ⚠️ Unreachable return
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Recommended Fix:**
|
|
187
|
+
```typescript
|
|
188
|
+
} catch (error) {
|
|
189
|
+
handleConfigLoadError(error, resolvedPath)
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### H4. Unsafe Type Assertion in JSON.parse (Line 269) ✅ RESOLVED
|
|
196
|
+
|
|
197
|
+
**Severity:** HIGH
|
|
198
|
+
**Impact:** Runtime errors if JSON is invalid structure
|
|
199
|
+
**Status:** ✅ RESOLVED - Proper JSON validation with try-catch at lines 308-320
|
|
200
|
+
|
|
201
|
+
**Problem:**
|
|
202
|
+
Using `as PartialMdContextConfig` assumes the JSON structure is valid without validation.
|
|
203
|
+
|
|
204
|
+
**Current Code:**
|
|
205
|
+
```typescript
|
|
206
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8')
|
|
207
|
+
const fileConfig = JSON.parse(content) as PartialMdContextConfig
|
|
208
|
+
return createConfigLayerFromConfig(fileConfig)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Issue:**
|
|
212
|
+
If the JSON file contains `{"foo": "bar"}`, TypeScript will happily cast it to `PartialMdContextConfig`, but it's not actually valid. This will cause runtime errors later when the config is used.
|
|
213
|
+
|
|
214
|
+
**Recommended Fix:**
|
|
215
|
+
```typescript
|
|
216
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8')
|
|
217
|
+
let parsed: unknown
|
|
218
|
+
try {
|
|
219
|
+
parsed = JSON.parse(content)
|
|
220
|
+
} catch (parseError) {
|
|
221
|
+
console.error(`\nError: Invalid JSON in config file: ${resolvedPath}`)
|
|
222
|
+
console.error(` ${parseError instanceof Error ? parseError.message : String(parseError)}`)
|
|
223
|
+
process.exit(1)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Validate it's an object before using
|
|
227
|
+
validateConfigObject(parsed, resolvedPath)
|
|
228
|
+
const fileConfig = parsed
|
|
229
|
+
return createConfigLayerFromConfig(fileConfig)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### H5. Missing Error Handling in runCli (Line 289-303) ✅ RESOLVED
|
|
235
|
+
|
|
236
|
+
**Severity:** HIGH
|
|
237
|
+
**Impact:** Uncaught errors could crash the process
|
|
238
|
+
**Status:** ✅ RESOLVED - Catch-all error handler added at lines 353-356
|
|
239
|
+
|
|
240
|
+
**Problem:**
|
|
241
|
+
The `runCli` function only catches `CliValidationError` but lets all other errors through with `throw error`.
|
|
242
|
+
|
|
243
|
+
**Current Code:**
|
|
244
|
+
```typescript
|
|
245
|
+
Effect.suspend(() => cli(filteredArgv)).pipe(
|
|
246
|
+
Effect.provide(appLayers),
|
|
247
|
+
Effect.catchAll((error) =>
|
|
248
|
+
Effect.sync(() => {
|
|
249
|
+
if (isEffectCliValidationError(error)) {
|
|
250
|
+
const message = formatEffectCliError(error)
|
|
251
|
+
console.error(`\nError: ${message}`)
|
|
252
|
+
console.error('\nRun "mdcontext --help" for usage information.')
|
|
253
|
+
process.exit(1)
|
|
254
|
+
}
|
|
255
|
+
throw error // ⚠️ Uncaught errors rethrown
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
258
|
+
NodeRuntime.runMain,
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Issue:**
|
|
263
|
+
Any error that isn't an `EffectCliValidationError` is rethrown and will be handled by `NodeRuntime.runMain`. If that doesn't handle it properly, it could crash the process or leave it hanging.
|
|
264
|
+
|
|
265
|
+
**Recommended Fix:**
|
|
266
|
+
```typescript
|
|
267
|
+
Effect.suspend(() => cli(filteredArgv)).pipe(
|
|
268
|
+
Effect.provide(appLayers),
|
|
269
|
+
Effect.catchAll((error) =>
|
|
270
|
+
Effect.sync(() => {
|
|
271
|
+
if (isEffectCliValidationError(error)) {
|
|
272
|
+
const message = formatEffectCliError(error)
|
|
273
|
+
console.error(`\nError: ${message}`)
|
|
274
|
+
console.error('\nRun "mdcontext --help" for usage information.')
|
|
275
|
+
process.exit(1)
|
|
276
|
+
}
|
|
277
|
+
// Handle all other errors
|
|
278
|
+
console.error('\nUnexpected error:')
|
|
279
|
+
console.error(error)
|
|
280
|
+
process.exit(2) // Different exit code for unexpected errors
|
|
281
|
+
}),
|
|
282
|
+
),
|
|
283
|
+
NodeRuntime.runMain,
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### H6. Race Condition in Help Checks (Lines 90-98) ✓ VALID
|
|
290
|
+
|
|
291
|
+
**Severity:** HIGH
|
|
292
|
+
**Impact:** Process hangs or unexpected behavior
|
|
293
|
+
**Status:** ✓ VALID - Code unchanged, issue remains but acceptable (no module-level async)
|
|
294
|
+
|
|
295
|
+
**Problem:**
|
|
296
|
+
Help checks call `process.exit(0)` synchronously, but if async config loading has started, there could be pending promises.
|
|
297
|
+
|
|
298
|
+
**Current Code:**
|
|
299
|
+
```typescript
|
|
300
|
+
// Check for subcommand help before anything else
|
|
301
|
+
checkSubcommandHelp() // May call process.exit(0)
|
|
302
|
+
|
|
303
|
+
// Check for bare subcommand that has nested subcommands (e.g., "config")
|
|
304
|
+
checkBareSubcommandHelp() // May call process.exit(0)
|
|
305
|
+
|
|
306
|
+
// Check if we should show main help
|
|
307
|
+
if (shouldShowMainHelp()) {
|
|
308
|
+
showMainHelp()
|
|
309
|
+
process.exit(0) // ⚠️ Abrupt exit during module loading
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Issue:**
|
|
314
|
+
These run during module initialization, before any async operations start. However, if any module-level code starts async work, calling `process.exit(0)` could interrupt it.
|
|
315
|
+
|
|
316
|
+
**Current State:** Acceptable (no module-level async work detected)
|
|
317
|
+
|
|
318
|
+
**Recommended:** Add comment explaining why this is safe:
|
|
319
|
+
```typescript
|
|
320
|
+
// SAFETY: Help checks run during module initialization, before any async
|
|
321
|
+
// operations start. If we add module-level async work in the future,
|
|
322
|
+
// we must refactor these to wait for cleanup.
|
|
323
|
+
checkSubcommandHelp()
|
|
324
|
+
checkBareSubcommandHelp()
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### H7. validateConfigObject Type Guard Too Permissive (Lines 182-198) ✅ RESOLVED
|
|
330
|
+
|
|
331
|
+
**Severity:** HIGH
|
|
332
|
+
**Impact:** Invalid configs could pass validation
|
|
333
|
+
**Status:** ✅ RESOLVED - Structure validation with VALID_CONFIG_KEYS at lines 213-239
|
|
334
|
+
|
|
335
|
+
**Problem:**
|
|
336
|
+
The type guard only checks that the value is a non-null, non-array object. It doesn't validate the actual structure.
|
|
337
|
+
|
|
338
|
+
**Current Code:**
|
|
339
|
+
```typescript
|
|
340
|
+
function validateConfigObject(
|
|
341
|
+
config: unknown,
|
|
342
|
+
resolvedPath: string,
|
|
343
|
+
): asserts config is PartialMdContextConfig {
|
|
344
|
+
if (
|
|
345
|
+
!config ||
|
|
346
|
+
typeof config !== 'object' ||
|
|
347
|
+
config === null || // ⚠️ Redundant check (!config catches null)
|
|
348
|
+
Array.isArray(config)
|
|
349
|
+
) {
|
|
350
|
+
console.error(
|
|
351
|
+
`\nError: Config file must export a default object or named "config" export`,
|
|
352
|
+
)
|
|
353
|
+
console.error(` File: ${resolvedPath}`)
|
|
354
|
+
process.exit(1)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Issues:**
|
|
360
|
+
1. `config === null` is redundant because `!config` already catches null
|
|
361
|
+
2. Doesn't actually validate structure - `{}` passes but isn't a valid config
|
|
362
|
+
3. TypeScript thinks it's safe but it's not
|
|
363
|
+
|
|
364
|
+
**Recommended Fix:**
|
|
365
|
+
```typescript
|
|
366
|
+
function validateConfigObject(
|
|
367
|
+
config: unknown,
|
|
368
|
+
resolvedPath: string,
|
|
369
|
+
): asserts config is PartialMdContextConfig {
|
|
370
|
+
// Check it's an object
|
|
371
|
+
if (
|
|
372
|
+
!config ||
|
|
373
|
+
typeof config !== 'object' ||
|
|
374
|
+
Array.isArray(config)
|
|
375
|
+
) {
|
|
376
|
+
console.error(
|
|
377
|
+
`\nError: Config file must export a default object or named "config" export`,
|
|
378
|
+
)
|
|
379
|
+
console.error(` File: ${resolvedPath}`)
|
|
380
|
+
process.exit(1)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Validate it has expected top-level keys (at least one)
|
|
384
|
+
const validKeys = ['index', 'search', 'embeddings', 'summarization', 'output', 'paths']
|
|
385
|
+
const configKeys = Object.keys(config)
|
|
386
|
+
const hasValidKey = configKeys.some(key => validKeys.includes(key))
|
|
387
|
+
|
|
388
|
+
if (configKeys.length > 0 && !hasValidKey) {
|
|
389
|
+
console.error(`\nError: Config file has no recognized configuration keys`)
|
|
390
|
+
console.error(` File: ${resolvedPath}`)
|
|
391
|
+
console.error(` Found keys: ${configKeys.join(', ')}`)
|
|
392
|
+
console.error(` Expected at least one of: ${validKeys.join(', ')}`)
|
|
393
|
+
process.exit(1)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## MEDIUM PRIORITY ISSUES
|
|
401
|
+
|
|
402
|
+
### M1. extractConfigPath Edge Case: Empty String (Lines 112-146) ✅ RESOLVED
|
|
403
|
+
|
|
404
|
+
**Severity:** MEDIUM
|
|
405
|
+
**Impact:** Silent bugs if user passes empty config path
|
|
406
|
+
**Status:** ✅ RESOLVED - Empty path validation at lines 123-141
|
|
407
|
+
|
|
408
|
+
**Problem:**
|
|
409
|
+
If user passes `--config=` or `--config ""`, the function sets `configPath = ""` which is falsy but still a string.
|
|
410
|
+
|
|
411
|
+
**Current Code:**
|
|
412
|
+
```typescript
|
|
413
|
+
// --config=path or -c=path
|
|
414
|
+
if (arg.startsWith('--config=')) {
|
|
415
|
+
configPath = arg.slice('--config='.length) // Could be ""
|
|
416
|
+
continue
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**Edge Case:**
|
|
421
|
+
```bash
|
|
422
|
+
mdcontext --config= index
|
|
423
|
+
# configPath === ""
|
|
424
|
+
# needsAsyncLoading("") returns false
|
|
425
|
+
# createConfigLayerSync() checks !customConfigPath and uses default
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Recommended Fix:**
|
|
429
|
+
```typescript
|
|
430
|
+
// --config=path or -c=path
|
|
431
|
+
if (arg.startsWith('--config=')) {
|
|
432
|
+
const value = arg.slice('--config='.length)
|
|
433
|
+
if (value.length === 0) {
|
|
434
|
+
console.error('\nError: --config requires a path')
|
|
435
|
+
console.error(' Usage: --config=path/to/config.js')
|
|
436
|
+
process.exit(1)
|
|
437
|
+
}
|
|
438
|
+
configPath = value
|
|
439
|
+
continue
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### M2. validateConfigFileExists Type Annotation Misleading (Line 160) ✅ RESOLVED
|
|
446
|
+
|
|
447
|
+
**Severity:** MEDIUM
|
|
448
|
+
**Impact:** Confusing function signature
|
|
449
|
+
**Status:** ✅ RESOLVED - Signature changed to `void` at line 178
|
|
450
|
+
|
|
451
|
+
**Problem:**
|
|
452
|
+
The assertion function signature uses `asserts resolvedPath` but the parameter name suggests it's the value being asserted.
|
|
453
|
+
|
|
454
|
+
**Current Code:**
|
|
455
|
+
```typescript
|
|
456
|
+
function validateConfigFileExists(resolvedPath: string): asserts resolvedPath {
|
|
457
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
458
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
459
|
+
process.exit(1)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Issue:**
|
|
465
|
+
This reads as "assert that `resolvedPath` is truthy" which is confusing. The function actually asserts that the file exists, not the type of the parameter.
|
|
466
|
+
|
|
467
|
+
**Recommended Fix:**
|
|
468
|
+
Remove the assertion signature since it doesn't narrow types:
|
|
469
|
+
```typescript
|
|
470
|
+
function validateConfigFileExists(resolvedPath: string): void {
|
|
471
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
472
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
473
|
+
process.exit(1)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Or use a type guard pattern:
|
|
479
|
+
```typescript
|
|
480
|
+
function ensureConfigFileExists(resolvedPath: string): asserts resolvedPath is string {
|
|
481
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
482
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
483
|
+
process.exit(1)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
### M3. needsAsyncLoading Doesn't Handle .cjs or .mjs (Line 246) ✅ RESOLVED
|
|
491
|
+
|
|
492
|
+
**Severity:** MEDIUM
|
|
493
|
+
**Impact:** .cjs and .mjs files handled incorrectly
|
|
494
|
+
**Status:** ✅ RESOLVED - Extension-based check at lines 289-294 handles all JS/TS variants
|
|
495
|
+
|
|
496
|
+
**Problem:**
|
|
497
|
+
Only checks for `.ts`, `.js`, `.mjs` but not `.cjs` (CommonJS).
|
|
498
|
+
|
|
499
|
+
**Current Code:**
|
|
500
|
+
```typescript
|
|
501
|
+
const needsAsyncLoading = (configPath: string | undefined): boolean => {
|
|
502
|
+
if (!configPath) return false
|
|
503
|
+
const resolved = path.resolve(configPath)
|
|
504
|
+
return (
|
|
505
|
+
resolved.endsWith('.ts') ||
|
|
506
|
+
resolved.endsWith('.js') ||
|
|
507
|
+
resolved.endsWith('.mjs')
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Issue:**
|
|
513
|
+
`.cjs` files need dynamic import too, but will fall through to the sync JSON path.
|
|
514
|
+
|
|
515
|
+
**Recommended Fix:**
|
|
516
|
+
```typescript
|
|
517
|
+
const needsAsyncLoading = (configPath: string | undefined): boolean => {
|
|
518
|
+
if (!configPath) return false
|
|
519
|
+
const resolved = path.resolve(configPath)
|
|
520
|
+
return (
|
|
521
|
+
resolved.endsWith('.ts') ||
|
|
522
|
+
resolved.endsWith('.js') ||
|
|
523
|
+
resolved.endsWith('.mjs') ||
|
|
524
|
+
resolved.endsWith('.cjs')
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**Better Fix:**
|
|
530
|
+
```typescript
|
|
531
|
+
const needsAsyncLoading = (configPath: string | undefined): boolean => {
|
|
532
|
+
if (!configPath) return false
|
|
533
|
+
const ext = path.extname(configPath).toLowerCase()
|
|
534
|
+
// Async load for all JS/TS variants, sync for JSON
|
|
535
|
+
return ext !== '.json'
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
### M4. Error Message Duplication in IIFE Catch (Lines 314-317) ✅ RESOLVED
|
|
542
|
+
|
|
543
|
+
**Severity:** MEDIUM
|
|
544
|
+
**Impact:** Redundant error handling
|
|
545
|
+
**Status:** ✅ RESOLVED - Specific error message at lines 376-388
|
|
546
|
+
|
|
547
|
+
**Problem:**
|
|
548
|
+
The IIFE catch block duplicates error handling that's already in `loadConfigAsync`.
|
|
549
|
+
|
|
550
|
+
**Current Code:**
|
|
551
|
+
```typescript
|
|
552
|
+
;(async () => {
|
|
553
|
+
try {
|
|
554
|
+
const configLayer = await loadConfigAsync(customConfigPath!)
|
|
555
|
+
runCli(configLayer)
|
|
556
|
+
} catch (error) {
|
|
557
|
+
// loadConfigAsync already calls handleConfigLoadError which exits
|
|
558
|
+
// This catch block is unreachable if loadConfigAsync fails
|
|
559
|
+
console.error(`\nError: Failed to load config`)
|
|
560
|
+
if (error instanceof Error) {
|
|
561
|
+
console.error(` ${error.message}`)
|
|
562
|
+
}
|
|
563
|
+
process.exit(1)
|
|
564
|
+
}
|
|
565
|
+
})()
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Issue:**
|
|
569
|
+
`loadConfigAsync` calls `handleConfigLoadError` which calls `process.exit(1)`. This catch block will never run for config loading errors, only for errors in `runCli`.
|
|
570
|
+
|
|
571
|
+
**Recommended Fix:**
|
|
572
|
+
Make the error message more specific:
|
|
573
|
+
```typescript
|
|
574
|
+
} catch (error) {
|
|
575
|
+
// This catches errors from runCli, not loadConfigAsync
|
|
576
|
+
console.error(`\nError: Failed to initialize CLI`)
|
|
577
|
+
if (error instanceof Error) {
|
|
578
|
+
console.error(` ${error.message}`)
|
|
579
|
+
if (error.stack) {
|
|
580
|
+
console.error(`\nStack trace:`)
|
|
581
|
+
console.error(error.stack)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
process.exit(1)
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
### M5. Missing Validation for --config Path (Line 134) ✅ RESOLVED
|
|
591
|
+
|
|
592
|
+
**Severity:** MEDIUM
|
|
593
|
+
**Impact:** Confusing error if user passes flag as path
|
|
594
|
+
**Status:** ✅ RESOLVED - Comprehensive validation at lines 145-159
|
|
595
|
+
|
|
596
|
+
**Problem:**
|
|
597
|
+
Checks `!nextArg.startsWith('-')` but doesn't validate it's a reasonable file path.
|
|
598
|
+
|
|
599
|
+
**Current Code:**
|
|
600
|
+
```typescript
|
|
601
|
+
if (arg === '--config' || arg === '-c') {
|
|
602
|
+
const nextArg = argv[i + 1]
|
|
603
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
604
|
+
configPath = nextArg
|
|
605
|
+
i++ // Skip the path argument
|
|
606
|
+
continue
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**Edge Case:**
|
|
612
|
+
```bash
|
|
613
|
+
mdcontext --config --help
|
|
614
|
+
# nextArg === "--help", doesn't set configPath (correct)
|
|
615
|
+
# But what if:
|
|
616
|
+
mdcontext --config ./path --help
|
|
617
|
+
# nextArg === "./path", sets configPath (correct)
|
|
618
|
+
# But also:
|
|
619
|
+
mdcontext -c ''
|
|
620
|
+
# nextArg === '', sets configPath to empty string
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Recommended Fix:**
|
|
624
|
+
```typescript
|
|
625
|
+
if (arg === '--config' || arg === '-c') {
|
|
626
|
+
const nextArg = argv[i + 1]
|
|
627
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
628
|
+
if (nextArg.length === 0) {
|
|
629
|
+
console.error('\nError: --config requires a path')
|
|
630
|
+
process.exit(1)
|
|
631
|
+
}
|
|
632
|
+
configPath = nextArg
|
|
633
|
+
i++
|
|
634
|
+
continue
|
|
635
|
+
}
|
|
636
|
+
// If no next arg or next arg is a flag, show error
|
|
637
|
+
console.error('\nError: --config requires a path')
|
|
638
|
+
console.error(' Usage: --config path/to/config.js')
|
|
639
|
+
process.exit(1)
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## LOW PRIORITY ISSUES
|
|
646
|
+
|
|
647
|
+
### L1. Magic Number in extractConfigPath (Line 118) 📍 MOVED
|
|
648
|
+
|
|
649
|
+
**Severity:** LOW
|
|
650
|
+
**Impact:** Code readability
|
|
651
|
+
**Status:** 📍 MOVED - Now at line 119, still has unnecessary undefined check
|
|
652
|
+
|
|
653
|
+
**Problem:**
|
|
654
|
+
Uses magic numbers for array bounds checking.
|
|
655
|
+
|
|
656
|
+
**Current Code:**
|
|
657
|
+
```typescript
|
|
658
|
+
for (let i = 0; i < argv.length; i++) {
|
|
659
|
+
const arg = argv[i]
|
|
660
|
+
if (arg === undefined) continue // ⚠️ Unnecessary check in for loop
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Issue:**
|
|
664
|
+
In a standard for loop, `arg` can never be undefined if `i < argv.length`. This check is unnecessary.
|
|
665
|
+
|
|
666
|
+
**Recommended Fix:**
|
|
667
|
+
```typescript
|
|
668
|
+
for (let i = 0; i < argv.length; i++) {
|
|
669
|
+
const arg = argv[i]! // Use non-null assertion since loop bounds guarantee it
|
|
670
|
+
// ... rest of code
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Or just remove the check:
|
|
674
|
+
```typescript
|
|
675
|
+
for (let i = 0; i < argv.length; i++) {
|
|
676
|
+
const arg = argv[i]
|
|
677
|
+
// Continue directly without undefined check
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
### L2. Inconsistent String Interpolation (Line 162) ✓ VALID
|
|
683
|
+
|
|
684
|
+
**Severity:** LOW
|
|
685
|
+
**Impact:** Code style consistency
|
|
686
|
+
**Status:** ✓ VALID - Pattern used consistently, now at line 180
|
|
687
|
+
|
|
688
|
+
**Problem:**
|
|
689
|
+
Mixes template literals and string concatenation.
|
|
690
|
+
|
|
691
|
+
**Current Code:**
|
|
692
|
+
```typescript
|
|
693
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Issue:**
|
|
697
|
+
Consistent, but the leading `\n` is in the template literal. Could be clearer.
|
|
698
|
+
|
|
699
|
+
**Recommended Fix:**
|
|
700
|
+
```typescript
|
|
701
|
+
console.error('\n' + `Error: Config file not found: ${resolvedPath}`)
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
Or keep it but add comment:
|
|
705
|
+
```typescript
|
|
706
|
+
// Blank line before error message
|
|
707
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
---
|
|
711
|
+
|
|
712
|
+
### L3. Effect.runSync Not Wrapped in Try-Catch (Line 210) 📍 MOVED
|
|
713
|
+
|
|
714
|
+
**Severity:** LOW
|
|
715
|
+
**Impact:** Potential crash if config parsing fails
|
|
716
|
+
**Status:** 📍 MOVED - Now at lines 244-255, still not wrapped but acceptable
|
|
717
|
+
|
|
718
|
+
**Problem:**
|
|
719
|
+
`Effect.runSync` can throw if the Effect fails, but it's not wrapped in try-catch.
|
|
720
|
+
|
|
721
|
+
**Current Code:**
|
|
722
|
+
```typescript
|
|
723
|
+
const createConfigLayerFromConfig = (
|
|
724
|
+
fileConfig: PartialMdContextConfig,
|
|
725
|
+
): Layer.Layer<ConfigService, never, never> => {
|
|
726
|
+
const provider = createConfigProviderSync({
|
|
727
|
+
fileConfig,
|
|
728
|
+
skipEnv: false,
|
|
729
|
+
})
|
|
730
|
+
const configResult = Effect.runSync(
|
|
731
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
732
|
+
)
|
|
733
|
+
return Layer.succeed(ConfigService, configResult)
|
|
734
|
+
}
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**Issue:**
|
|
738
|
+
If `MdContextConfig` parsing fails (invalid config values), `Effect.runSync` will throw and crash. This function is called inside try-catch blocks in the callers, so it's handled, but not obviously.
|
|
739
|
+
|
|
740
|
+
**Recommended Fix:**
|
|
741
|
+
Add explicit error handling or document the throw behavior:
|
|
742
|
+
```typescript
|
|
743
|
+
/**
|
|
744
|
+
* Create a ConfigService Layer from a validated config object.
|
|
745
|
+
* @throws {ConfigError} If config validation fails
|
|
746
|
+
*/
|
|
747
|
+
const createConfigLayerFromConfig = (
|
|
748
|
+
fileConfig: PartialMdContextConfig,
|
|
749
|
+
): Layer.Layer<ConfigService, never, never> => {
|
|
750
|
+
try {
|
|
751
|
+
const provider = createConfigProviderSync({
|
|
752
|
+
fileConfig,
|
|
753
|
+
skipEnv: false,
|
|
754
|
+
})
|
|
755
|
+
const configResult = Effect.runSync(
|
|
756
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
757
|
+
)
|
|
758
|
+
return Layer.succeed(ConfigService, configResult)
|
|
759
|
+
} catch (error) {
|
|
760
|
+
// Re-throw with more context
|
|
761
|
+
console.error('\nError: Invalid configuration values')
|
|
762
|
+
if (error instanceof Error) {
|
|
763
|
+
console.error(` ${error.message}`)
|
|
764
|
+
}
|
|
765
|
+
process.exit(1)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## SUMMARY OF RECOMMENDATIONS
|
|
773
|
+
|
|
774
|
+
### Immediate Actions (Critical/High)
|
|
775
|
+
|
|
776
|
+
1. **C1:** Add `.catch()` handler to IIFE to prevent unhandled rejections
|
|
777
|
+
2. **H1:** Remove non-null assertion, add explicit check
|
|
778
|
+
3. **H2, H3:** Remove unreachable `return` statements
|
|
779
|
+
4. **H4:** Add JSON validation before casting
|
|
780
|
+
5. **H5:** Add catch-all error handler in `runCli`
|
|
781
|
+
6. **H7:** Improve `validateConfigObject` to check actual structure
|
|
782
|
+
|
|
783
|
+
### Important Improvements (Medium)
|
|
784
|
+
|
|
785
|
+
1. **M1, M5:** Add validation for empty/missing config paths
|
|
786
|
+
2. **M3:** Handle `.cjs` files in `needsAsyncLoading`
|
|
787
|
+
3. **M4:** Improve IIFE error message specificity
|
|
788
|
+
|
|
789
|
+
### Code Quality (Low)
|
|
790
|
+
|
|
791
|
+
1. **L1:** Remove unnecessary undefined check
|
|
792
|
+
2. **L3:** Document or wrap `Effect.runSync` throw behavior
|
|
793
|
+
|
|
794
|
+
---
|
|
795
|
+
|
|
796
|
+
## TESTING RECOMMENDATIONS
|
|
797
|
+
|
|
798
|
+
Create tests for:
|
|
799
|
+
|
|
800
|
+
1. **Config loading edge cases:**
|
|
801
|
+
- Empty config path `--config=`
|
|
802
|
+
- Non-existent file
|
|
803
|
+
- Invalid JSON syntax
|
|
804
|
+
- Valid JSON but invalid structure
|
|
805
|
+
- .cjs, .mjs, .ts files
|
|
806
|
+
|
|
807
|
+
2. **Async error scenarios:**
|
|
808
|
+
- Errors thrown in `loadConfigAsync`
|
|
809
|
+
- Errors thrown in `runCli`
|
|
810
|
+
- Unhandled promise rejections
|
|
811
|
+
|
|
812
|
+
3. **Help flag interactions:**
|
|
813
|
+
- `--config` with `--help`
|
|
814
|
+
- Bare subcommand help
|
|
815
|
+
- Invalid subcommands
|
|
816
|
+
|
|
817
|
+
4. **Type safety:**
|
|
818
|
+
- Verify TypeScript catches unsafe operations
|
|
819
|
+
- Test assertion functions actually narrow types
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
## DIFF: Proposed Changes
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
// ============================================================================
|
|
827
|
+
// CRITICAL FIX: C1 - Unhandled Promise Rejection
|
|
828
|
+
// ============================================================================
|
|
829
|
+
|
|
830
|
+
// OLD:
|
|
831
|
+
;(async () => {
|
|
832
|
+
try {
|
|
833
|
+
const configLayer = await loadConfigAsync(customConfigPath!)
|
|
834
|
+
runCli(configLayer)
|
|
835
|
+
} catch (error) {
|
|
836
|
+
console.error(`\nError: Failed to load config`)
|
|
837
|
+
if (error instanceof Error) {
|
|
838
|
+
console.error(` ${error.message}`)
|
|
839
|
+
}
|
|
840
|
+
process.exit(1)
|
|
841
|
+
}
|
|
842
|
+
})()
|
|
843
|
+
|
|
844
|
+
// NEW:
|
|
845
|
+
;(async () => {
|
|
846
|
+
try {
|
|
847
|
+
// H1: Remove non-null assertion
|
|
848
|
+
if (!customConfigPath) {
|
|
849
|
+
console.error('\nError: Config path is required for async loading')
|
|
850
|
+
process.exit(1)
|
|
851
|
+
}
|
|
852
|
+
const configLayer = await loadConfigAsync(customConfigPath)
|
|
853
|
+
runCli(configLayer)
|
|
854
|
+
} catch (error) {
|
|
855
|
+
// M4: More specific error message
|
|
856
|
+
console.error(`\nError: Failed to initialize CLI`)
|
|
857
|
+
if (error instanceof Error) {
|
|
858
|
+
console.error(` ${error.message}`)
|
|
859
|
+
}
|
|
860
|
+
process.exit(1)
|
|
861
|
+
}
|
|
862
|
+
})().catch((error) => {
|
|
863
|
+
// C1: Catch unhandled promise rejections
|
|
864
|
+
console.error('\nUnexpected error during initialization')
|
|
865
|
+
console.error(error)
|
|
866
|
+
process.exit(1)
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
// ============================================================================
|
|
870
|
+
// HIGH FIX: H2, H3 - Remove Unreachable Returns
|
|
871
|
+
// ============================================================================
|
|
872
|
+
|
|
873
|
+
// loadConfigAsync (H2):
|
|
874
|
+
async function loadConfigAsync(
|
|
875
|
+
configPath: string,
|
|
876
|
+
): Promise<Layer.Layer<ConfigService, never, never>> {
|
|
877
|
+
const resolvedPath = path.resolve(configPath)
|
|
878
|
+
validateConfigFileExists(resolvedPath)
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
const fileUrl = `file://${resolvedPath}`
|
|
882
|
+
const module = (await import(fileUrl)) as {
|
|
883
|
+
default?: PartialMdContextConfig
|
|
884
|
+
config?: PartialMdContextConfig
|
|
885
|
+
}
|
|
886
|
+
const fileConfig = module.default ?? module.config
|
|
887
|
+
|
|
888
|
+
validateConfigObject(fileConfig, resolvedPath)
|
|
889
|
+
return createConfigLayerFromConfig(fileConfig)
|
|
890
|
+
} catch (error) {
|
|
891
|
+
// OLD: return handleConfigLoadError(error, resolvedPath)
|
|
892
|
+
// NEW: Remove unreachable return
|
|
893
|
+
handleConfigLoadError(error, resolvedPath)
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// createConfigLayerSync (H3):
|
|
898
|
+
function createConfigLayerSync(): Layer.Layer<ConfigService, never, never> {
|
|
899
|
+
if (!customConfigPath) {
|
|
900
|
+
return defaultCliConfigLayerSync
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const resolvedPath = path.resolve(customConfigPath)
|
|
904
|
+
validateConfigFileExists(resolvedPath)
|
|
905
|
+
|
|
906
|
+
try {
|
|
907
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8')
|
|
908
|
+
|
|
909
|
+
// H4: Better JSON parsing with validation
|
|
910
|
+
let parsed: unknown
|
|
911
|
+
try {
|
|
912
|
+
parsed = JSON.parse(content)
|
|
913
|
+
} catch (parseError) {
|
|
914
|
+
console.error(`\nError: Invalid JSON in config file: ${resolvedPath}`)
|
|
915
|
+
console.error(` ${parseError instanceof Error ? parseError.message : String(parseError)}`)
|
|
916
|
+
process.exit(1)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
validateConfigObject(parsed, resolvedPath)
|
|
920
|
+
const fileConfig = parsed
|
|
921
|
+
return createConfigLayerFromConfig(fileConfig)
|
|
922
|
+
} catch (error) {
|
|
923
|
+
// OLD: return handleConfigLoadError(error, resolvedPath)
|
|
924
|
+
// NEW: Remove unreachable return
|
|
925
|
+
handleConfigLoadError(error, resolvedPath)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// ============================================================================
|
|
930
|
+
// HIGH FIX: H5 - Better Error Handling in runCli
|
|
931
|
+
// ============================================================================
|
|
932
|
+
|
|
933
|
+
const runCli = (
|
|
934
|
+
configLayer: Layer.Layer<ConfigService, never, never>,
|
|
935
|
+
): void => {
|
|
936
|
+
const appLayers = Layer.mergeAll(
|
|
937
|
+
NodeContext.layer,
|
|
938
|
+
cliConfigLayer,
|
|
939
|
+
configLayer,
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
Effect.suspend(() => cli(filteredArgv)).pipe(
|
|
943
|
+
Effect.provide(appLayers),
|
|
944
|
+
Effect.catchAll((error) =>
|
|
945
|
+
Effect.sync(() => {
|
|
946
|
+
if (isEffectCliValidationError(error)) {
|
|
947
|
+
const message = formatEffectCliError(error)
|
|
948
|
+
console.error(`\nError: ${message}`)
|
|
949
|
+
console.error('\nRun "mdcontext --help" for usage information.')
|
|
950
|
+
process.exit(1)
|
|
951
|
+
}
|
|
952
|
+
// NEW: Handle all other errors instead of rethrowing
|
|
953
|
+
console.error('\nUnexpected error:')
|
|
954
|
+
console.error(error)
|
|
955
|
+
process.exit(2)
|
|
956
|
+
}),
|
|
957
|
+
),
|
|
958
|
+
NodeRuntime.runMain,
|
|
959
|
+
)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// ============================================================================
|
|
963
|
+
// HIGH FIX: H7 - Improved Config Validation
|
|
964
|
+
// ============================================================================
|
|
965
|
+
|
|
966
|
+
function validateConfigObject(
|
|
967
|
+
config: unknown,
|
|
968
|
+
resolvedPath: string,
|
|
969
|
+
): asserts config is PartialMdContextConfig {
|
|
970
|
+
// Check it's an object (H7: removed redundant null check)
|
|
971
|
+
if (
|
|
972
|
+
!config ||
|
|
973
|
+
typeof config !== 'object' ||
|
|
974
|
+
Array.isArray(config)
|
|
975
|
+
) {
|
|
976
|
+
console.error(
|
|
977
|
+
`\nError: Config file must export a default object or named "config" export`,
|
|
978
|
+
)
|
|
979
|
+
console.error(` File: ${resolvedPath}`)
|
|
980
|
+
process.exit(1)
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// H7: Validate structure
|
|
984
|
+
const validKeys = ['index', 'search', 'embeddings', 'summarization', 'output', 'paths']
|
|
985
|
+
const configKeys = Object.keys(config)
|
|
986
|
+
const hasValidKey = configKeys.some(key => validKeys.includes(key))
|
|
987
|
+
|
|
988
|
+
if (configKeys.length > 0 && !hasValidKey) {
|
|
989
|
+
console.error(`\nError: Config file has no recognized configuration keys`)
|
|
990
|
+
console.error(` File: ${resolvedPath}`)
|
|
991
|
+
console.error(` Found keys: ${configKeys.join(', ')}`)
|
|
992
|
+
console.error(` Expected at least one of: ${validKeys.join(', ')}`)
|
|
993
|
+
process.exit(1)
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// ============================================================================
|
|
998
|
+
// MEDIUM FIX: M1, M5 - Config Path Validation
|
|
999
|
+
// ============================================================================
|
|
1000
|
+
|
|
1001
|
+
const extractConfigPath = (
|
|
1002
|
+
argv: string[],
|
|
1003
|
+
): { configPath: string | undefined; filteredArgv: string[] } => {
|
|
1004
|
+
const filteredArgv: string[] = []
|
|
1005
|
+
let configPath: string | undefined
|
|
1006
|
+
|
|
1007
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1008
|
+
const arg = argv[i]
|
|
1009
|
+
// L1: Removed unnecessary undefined check
|
|
1010
|
+
|
|
1011
|
+
// M1: Validate empty paths
|
|
1012
|
+
if (arg.startsWith('--config=')) {
|
|
1013
|
+
const value = arg.slice('--config='.length)
|
|
1014
|
+
if (value.length === 0) {
|
|
1015
|
+
console.error('\nError: --config requires a path')
|
|
1016
|
+
console.error(' Usage: --config=path/to/config.js')
|
|
1017
|
+
process.exit(1)
|
|
1018
|
+
}
|
|
1019
|
+
configPath = value
|
|
1020
|
+
continue
|
|
1021
|
+
}
|
|
1022
|
+
if (arg.startsWith('-c=')) {
|
|
1023
|
+
const value = arg.slice('-c='.length)
|
|
1024
|
+
if (value.length === 0) {
|
|
1025
|
+
console.error('\nError: -c requires a path')
|
|
1026
|
+
console.error(' Usage: -c=path/to/config.js')
|
|
1027
|
+
process.exit(1)
|
|
1028
|
+
}
|
|
1029
|
+
configPath = value
|
|
1030
|
+
continue
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// M5: Better validation for flag-value pairs
|
|
1034
|
+
if (arg === '--config' || arg === '-c') {
|
|
1035
|
+
const nextArg = argv[i + 1]
|
|
1036
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
1037
|
+
console.error('\nError: --config requires a path')
|
|
1038
|
+
console.error(' Usage: --config path/to/config.js')
|
|
1039
|
+
process.exit(1)
|
|
1040
|
+
}
|
|
1041
|
+
if (nextArg.length === 0) {
|
|
1042
|
+
console.error('\nError: --config path cannot be empty')
|
|
1043
|
+
process.exit(1)
|
|
1044
|
+
}
|
|
1045
|
+
configPath = nextArg
|
|
1046
|
+
i++
|
|
1047
|
+
continue
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
filteredArgv.push(arg)
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return { configPath, filteredArgv }
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// ============================================================================
|
|
1057
|
+
// MEDIUM FIX: M3 - Handle All JS Extensions
|
|
1058
|
+
// ============================================================================
|
|
1059
|
+
|
|
1060
|
+
const needsAsyncLoading = (configPath: string | undefined): boolean => {
|
|
1061
|
+
if (!configPath) return false
|
|
1062
|
+
const ext = path.extname(configPath).toLowerCase()
|
|
1063
|
+
// Async load for all JS/TS variants, sync for JSON
|
|
1064
|
+
return ext !== '.json'
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// ============================================================================
|
|
1068
|
+
// MEDIUM FIX: M2 - Fix Type Annotation
|
|
1069
|
+
// ============================================================================
|
|
1070
|
+
|
|
1071
|
+
// OLD:
|
|
1072
|
+
function validateConfigFileExists(resolvedPath: string): asserts resolvedPath {
|
|
1073
|
+
// ...
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// NEW:
|
|
1077
|
+
function validateConfigFileExists(resolvedPath: string): void {
|
|
1078
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1079
|
+
console.error(`\nError: Config file not found: ${resolvedPath}`)
|
|
1080
|
+
process.exit(1)
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// ============================================================================
|
|
1085
|
+
// LOW FIX: L3 - Document Throw Behavior
|
|
1086
|
+
// ============================================================================
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Create a ConfigService Layer from a validated config object.
|
|
1090
|
+
*
|
|
1091
|
+
* @throws Will call process.exit(1) if config validation fails
|
|
1092
|
+
*/
|
|
1093
|
+
const createConfigLayerFromConfig = (
|
|
1094
|
+
fileConfig: PartialMdContextConfig,
|
|
1095
|
+
): Layer.Layer<ConfigService, never, never> => {
|
|
1096
|
+
try {
|
|
1097
|
+
const provider = createConfigProviderSync({
|
|
1098
|
+
fileConfig,
|
|
1099
|
+
skipEnv: false,
|
|
1100
|
+
})
|
|
1101
|
+
const configResult = Effect.runSync(
|
|
1102
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
1103
|
+
)
|
|
1104
|
+
return Layer.succeed(ConfigService, configResult)
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
console.error('\nError: Invalid configuration values')
|
|
1107
|
+
if (error instanceof Error) {
|
|
1108
|
+
console.error(` ${error.message}`)
|
|
1109
|
+
}
|
|
1110
|
+
process.exit(1)
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
---
|
|
1116
|
+
|
|
1117
|
+
## CONCLUSION
|
|
1118
|
+
|
|
1119
|
+
The main entry point has **significant issues** that need to be addressed:
|
|
1120
|
+
|
|
1121
|
+
1. **Unhandled promise rejections** (Critical)
|
|
1122
|
+
2. **Type safety violations** (High)
|
|
1123
|
+
3. **Incomplete error handling** (High)
|
|
1124
|
+
4. **Missing edge case validation** (Medium)
|
|
1125
|
+
|
|
1126
|
+
These issues are fixable with the changes outlined above. The async/await pattern in the IIFE is fundamentally correct, but needs better error handling. The config loading logic is sound but needs validation improvements.
|
|
1127
|
+
|
|
1128
|
+
**Estimated effort:** 2-3 hours for all fixes + comprehensive testing
|