mdcontext 0.1.0 → 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.
Files changed (251) hide show
  1. package/.changeset/config.json +9 -9
  2. package/.claude/settings.local.json +25 -0
  3. package/.github/workflows/claude-code-review.yml +44 -0
  4. package/.github/workflows/claude.yml +85 -0
  5. package/CONTRIBUTING.md +186 -0
  6. package/NOTES/NOTES +44 -0
  7. package/README.md +206 -3
  8. package/biome.json +1 -1
  9. package/dist/chunk-23UPXDNL.js +3044 -0
  10. package/dist/chunk-2W7MO2DL.js +1366 -0
  11. package/dist/chunk-3NUAZGMA.js +1689 -0
  12. package/dist/chunk-7TOWB2XB.js +366 -0
  13. package/dist/chunk-7XOTOADQ.js +3065 -0
  14. package/dist/chunk-AH2PDM2K.js +3042 -0
  15. package/dist/chunk-BNXWSZ63.js +3742 -0
  16. package/dist/chunk-BTL5DJVU.js +3222 -0
  17. package/dist/chunk-HDHYG7E4.js +104 -0
  18. package/dist/chunk-HLR4KZBP.js +3234 -0
  19. package/dist/chunk-IP3FRFEB.js +1045 -0
  20. package/dist/chunk-KHU56VDO.js +3042 -0
  21. package/dist/chunk-KRYIFLQR.js +85 -89
  22. package/dist/chunk-LBSDNLEM.js +287 -0
  23. package/dist/chunk-MNTQ7HCP.js +2643 -0
  24. package/dist/chunk-MUJELQQ6.js +1387 -0
  25. package/dist/chunk-MXJGMSLV.js +2199 -0
  26. package/dist/chunk-N6QJGC3Z.js +2636 -0
  27. package/dist/chunk-OBELGBPM.js +1713 -0
  28. package/dist/chunk-OT7R5XTA.js +3192 -0
  29. package/dist/chunk-P7X4RA2T.js +106 -0
  30. package/dist/chunk-PIDUQNC2.js +3185 -0
  31. package/dist/chunk-POGCDIH4.js +3187 -0
  32. package/dist/chunk-PSIEOQGZ.js +3043 -0
  33. package/dist/chunk-PVRT3IHA.js +3238 -0
  34. package/dist/chunk-QNN4TT23.js +1430 -0
  35. package/dist/chunk-RE3R45RJ.js +3042 -0
  36. package/dist/chunk-S7E6TFX6.js +718 -657
  37. package/dist/chunk-SG6GLU4U.js +1378 -0
  38. package/dist/chunk-SJCDV2ST.js +274 -0
  39. package/dist/chunk-SYE5XLF3.js +104 -0
  40. package/dist/chunk-T5VLYBZD.js +103 -0
  41. package/dist/chunk-TOQB7VWU.js +3238 -0
  42. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  43. package/dist/chunk-VVTGZNBT.js +1533 -1423
  44. package/dist/chunk-W7Q4RFEV.js +104 -0
  45. package/dist/chunk-XTYYVRLO.js +3190 -0
  46. package/dist/chunk-Y6MDYVJD.js +3063 -0
  47. package/dist/cli/main.js +4072 -629
  48. package/dist/index.d.ts +420 -33
  49. package/dist/index.js +8 -15
  50. package/dist/mcp/server.js +103 -7
  51. package/dist/schema-BAWSG7KY.js +22 -0
  52. package/dist/schema-E3QUPL26.js +20 -0
  53. package/dist/schema-EHL7WUT6.js +20 -0
  54. package/docs/019-USAGE.md +44 -5
  55. package/docs/020-current-implementation.md +8 -8
  56. package/docs/021-DOGFOODING-FINDINGS.md +1 -1
  57. package/docs/CONFIG.md +1123 -0
  58. package/docs/ERRORS.md +383 -0
  59. package/docs/summarization.md +320 -0
  60. package/justfile +40 -0
  61. package/package.json +39 -33
  62. package/research/INDEX.md +315 -0
  63. package/research/code-review/README.md +90 -0
  64. package/research/code-review/cli-error-handling-review.md +979 -0
  65. package/research/code-review/code-review-validation-report.md +464 -0
  66. package/research/code-review/main-ts-review.md +1128 -0
  67. package/research/config-docs/SUMMARY.md +357 -0
  68. package/research/config-docs/TEST-RESULTS.md +776 -0
  69. package/research/config-docs/TODO.md +542 -0
  70. package/research/config-docs/analysis.md +744 -0
  71. package/research/config-docs/fix-validation.md +502 -0
  72. package/research/config-docs/help-audit.md +264 -0
  73. package/research/config-docs/help-system-analysis.md +890 -0
  74. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  75. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  76. package/research/issue-review.md +603 -0
  77. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  78. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  79. package/research/llm-summarization/anthropic-2026.md +367 -0
  80. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  81. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  82. package/research/llm-summarization/openai-2026.md +473 -0
  83. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  84. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  85. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  86. package/research/llm-summarization/prototype-results.md +56 -0
  87. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  88. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  89. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  90. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  91. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  92. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  93. package/research/mdcontext-pudding/02-search.md +970 -0
  94. package/research/mdcontext-pudding/03-context.md +779 -0
  95. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  96. package/research/mdcontext-pudding/04-tree.md +704 -0
  97. package/research/mdcontext-pudding/05-config.md +1038 -0
  98. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  99. package/research/mdcontext-pudding/06-links.md +679 -0
  100. package/research/mdcontext-pudding/07-stats.md +693 -0
  101. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  102. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  103. package/research/mdcontext-pudding/README.md +168 -0
  104. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  105. package/research/research-quality-review.md +834 -0
  106. package/research/semantic-search/embedding-text-analysis.md +156 -0
  107. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  108. package/research/semantic-search/query-processing-analysis.md +207 -0
  109. package/research/semantic-search/root-cause-and-solution.md +114 -0
  110. package/research/semantic-search/threshold-validation-report.md +69 -0
  111. package/research/semantic-search/vector-search-analysis.md +63 -0
  112. package/research/test-path-issues.md +276 -0
  113. package/review/ALP-76/1-error-type-design.md +962 -0
  114. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  115. package/review/ALP-76/3-error-presentation.md +624 -0
  116. package/review/ALP-76/4-test-coverage.md +625 -0
  117. package/review/ALP-76/5-migration-completeness.md +440 -0
  118. package/review/ALP-76/6-effect-best-practices.md +755 -0
  119. package/scripts/apply-branch-protection.sh +47 -0
  120. package/scripts/branch-protection-templates.json +79 -0
  121. package/scripts/prototype-summarization.ts +346 -0
  122. package/scripts/rebuild-hnswlib.js +32 -37
  123. package/scripts/setup-branch-protection.sh +64 -0
  124. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  125. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  126. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  127. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  128. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  129. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  130. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  131. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  132. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  133. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  134. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  135. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  136. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  137. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  138. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  139. package/src/cli/argv-preprocessor.test.ts +2 -2
  140. package/src/cli/cli.test.ts +230 -33
  141. package/src/cli/commands/config-cmd.ts +642 -0
  142. package/src/cli/commands/context.ts +97 -9
  143. package/src/cli/commands/duplicates.ts +122 -0
  144. package/src/cli/commands/embeddings.ts +529 -0
  145. package/src/cli/commands/index-cmd.ts +210 -30
  146. package/src/cli/commands/index.ts +3 -0
  147. package/src/cli/commands/search.ts +894 -64
  148. package/src/cli/commands/stats.ts +3 -0
  149. package/src/cli/commands/tree.ts +26 -5
  150. package/src/cli/config-layer.ts +176 -0
  151. package/src/cli/error-handler.test.ts +235 -0
  152. package/src/cli/error-handler.ts +655 -0
  153. package/src/cli/flag-schemas.ts +66 -0
  154. package/src/cli/help.ts +209 -7
  155. package/src/cli/main.ts +348 -58
  156. package/src/cli/options.ts +10 -0
  157. package/src/cli/shared-error-handling.ts +199 -0
  158. package/src/cli/utils.ts +150 -17
  159. package/src/config/file-provider.test.ts +320 -0
  160. package/src/config/file-provider.ts +273 -0
  161. package/src/config/index.ts +72 -0
  162. package/src/config/integration.test.ts +667 -0
  163. package/src/config/precedence.test.ts +277 -0
  164. package/src/config/precedence.ts +451 -0
  165. package/src/config/schema.test.ts +414 -0
  166. package/src/config/schema.ts +603 -0
  167. package/src/config/service.test.ts +320 -0
  168. package/src/config/service.ts +243 -0
  169. package/src/config/testing.test.ts +264 -0
  170. package/src/config/testing.ts +110 -0
  171. package/src/core/types.ts +6 -33
  172. package/src/duplicates/detector.test.ts +183 -0
  173. package/src/duplicates/detector.ts +414 -0
  174. package/src/duplicates/index.ts +18 -0
  175. package/src/embeddings/embedding-namespace.test.ts +300 -0
  176. package/src/embeddings/embedding-namespace.ts +947 -0
  177. package/src/embeddings/heading-boost.test.ts +222 -0
  178. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  179. package/src/embeddings/hyde.test.ts +272 -0
  180. package/src/embeddings/hyde.ts +264 -0
  181. package/src/embeddings/index.ts +2 -0
  182. package/src/embeddings/openai-provider.ts +332 -83
  183. package/src/embeddings/pricing.json +22 -0
  184. package/src/embeddings/provider-constants.ts +204 -0
  185. package/src/embeddings/provider-errors.test.ts +967 -0
  186. package/src/embeddings/provider-errors.ts +565 -0
  187. package/src/embeddings/provider-factory.test.ts +240 -0
  188. package/src/embeddings/provider-factory.ts +225 -0
  189. package/src/embeddings/provider-integration.test.ts +788 -0
  190. package/src/embeddings/query-preprocessing.test.ts +187 -0
  191. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  192. package/src/embeddings/semantic-search.ts +780 -93
  193. package/src/embeddings/types.ts +293 -16
  194. package/src/embeddings/vector-store.ts +486 -77
  195. package/src/embeddings/voyage-provider.ts +313 -0
  196. package/src/errors/errors.test.ts +845 -0
  197. package/src/errors/index.ts +533 -0
  198. package/src/index/ignore-patterns.test.ts +354 -0
  199. package/src/index/ignore-patterns.ts +305 -0
  200. package/src/index/indexer.ts +286 -48
  201. package/src/index/storage.ts +94 -30
  202. package/src/index/types.ts +40 -2
  203. package/src/index/watcher.ts +67 -9
  204. package/src/index.ts +22 -0
  205. package/src/integration/search-keyword.test.ts +678 -0
  206. package/src/mcp/server.ts +135 -6
  207. package/src/parser/parser.ts +18 -19
  208. package/src/parser/section-filter.test.ts +277 -0
  209. package/src/parser/section-filter.ts +125 -3
  210. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  211. package/src/search/bm25-store.ts +366 -0
  212. package/src/search/cross-encoder.test.ts +253 -0
  213. package/src/search/cross-encoder.ts +406 -0
  214. package/src/search/fuzzy-search.test.ts +419 -0
  215. package/src/search/fuzzy-search.ts +273 -0
  216. package/src/search/hybrid-search.ts +448 -0
  217. package/src/search/path-matcher.test.ts +276 -0
  218. package/src/search/path-matcher.ts +33 -0
  219. package/src/search/searcher.test.ts +99 -1
  220. package/src/search/searcher.ts +189 -67
  221. package/src/search/wink-bm25.d.ts +30 -0
  222. package/src/summarization/cli-providers/claude.ts +202 -0
  223. package/src/summarization/cli-providers/detection.test.ts +273 -0
  224. package/src/summarization/cli-providers/detection.ts +118 -0
  225. package/src/summarization/cli-providers/index.ts +8 -0
  226. package/src/summarization/cost.test.ts +139 -0
  227. package/src/summarization/cost.ts +102 -0
  228. package/src/summarization/error-handler.test.ts +127 -0
  229. package/src/summarization/error-handler.ts +111 -0
  230. package/src/summarization/index.ts +102 -0
  231. package/src/summarization/pipeline.test.ts +498 -0
  232. package/src/summarization/pipeline.ts +231 -0
  233. package/src/summarization/prompts.test.ts +269 -0
  234. package/src/summarization/prompts.ts +133 -0
  235. package/src/summarization/provider-factory.test.ts +396 -0
  236. package/src/summarization/provider-factory.ts +178 -0
  237. package/src/summarization/types.ts +184 -0
  238. package/src/summarize/summarizer.ts +104 -35
  239. package/src/types/huggingface-transformers.d.ts +66 -0
  240. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  241. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  242. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  243. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +4 -4
  244. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -0
  245. package/tests/integration/embed-index.test.ts +712 -0
  246. package/tests/integration/search-context.test.ts +469 -0
  247. package/tests/integration/search-semantic.test.ts +522 -0
  248. package/vitest.config.ts +1 -6
  249. package/AGENTS.md +0 -46
  250. package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
  251. package/tests/fixtures/cli/.mdcontext/vectors.meta.json +0 -1264
@@ -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