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.
Files changed (337) hide show
  1. package/.changeset/README.md +28 -0
  2. package/.changeset/config.json +11 -0
  3. package/.claude/settings.local.json +25 -0
  4. package/.github/workflows/ci.yml +83 -0
  5. package/.github/workflows/claude-code-review.yml +44 -0
  6. package/.github/workflows/claude.yml +85 -0
  7. package/.github/workflows/release.yml +113 -0
  8. package/.tldrignore +112 -0
  9. package/BACKLOG.md +338 -0
  10. package/CONTRIBUTING.md +186 -0
  11. package/NOTES/NOTES +44 -0
  12. package/README.md +434 -11
  13. package/biome.json +36 -0
  14. package/cspell.config.yaml +14 -0
  15. package/dist/chunk-23UPXDNL.js +3044 -0
  16. package/dist/chunk-2W7MO2DL.js +1366 -0
  17. package/dist/chunk-3NUAZGMA.js +1689 -0
  18. package/dist/chunk-7TOWB2XB.js +366 -0
  19. package/dist/chunk-7XOTOADQ.js +3065 -0
  20. package/dist/chunk-AH2PDM2K.js +3042 -0
  21. package/dist/chunk-BNXWSZ63.js +3742 -0
  22. package/dist/chunk-BTL5DJVU.js +3222 -0
  23. package/dist/chunk-HDHYG7E4.js +104 -0
  24. package/dist/chunk-HLR4KZBP.js +3234 -0
  25. package/dist/chunk-IP3FRFEB.js +1045 -0
  26. package/dist/chunk-KHU56VDO.js +3042 -0
  27. package/dist/chunk-KRYIFLQR.js +88 -0
  28. package/dist/chunk-LBSDNLEM.js +287 -0
  29. package/dist/chunk-MNTQ7HCP.js +2643 -0
  30. package/dist/chunk-MUJELQQ6.js +1387 -0
  31. package/dist/chunk-MXJGMSLV.js +2199 -0
  32. package/dist/chunk-N6QJGC3Z.js +2636 -0
  33. package/dist/chunk-OBELGBPM.js +1713 -0
  34. package/dist/chunk-OT7R5XTA.js +3192 -0
  35. package/dist/chunk-P7X4RA2T.js +106 -0
  36. package/dist/chunk-PIDUQNC2.js +3185 -0
  37. package/dist/chunk-POGCDIH4.js +3187 -0
  38. package/dist/chunk-PSIEOQGZ.js +3043 -0
  39. package/dist/chunk-PVRT3IHA.js +3238 -0
  40. package/dist/chunk-QNN4TT23.js +1430 -0
  41. package/dist/chunk-RE3R45RJ.js +3042 -0
  42. package/dist/chunk-S7E6TFX6.js +803 -0
  43. package/dist/chunk-SG6GLU4U.js +1378 -0
  44. package/dist/chunk-SJCDV2ST.js +274 -0
  45. package/dist/chunk-SYE5XLF3.js +104 -0
  46. package/dist/chunk-T5VLYBZD.js +103 -0
  47. package/dist/chunk-TOQB7VWU.js +3238 -0
  48. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  49. package/dist/chunk-VVTGZNBT.js +1629 -0
  50. package/dist/chunk-W7Q4RFEV.js +104 -0
  51. package/dist/chunk-XTYYVRLO.js +3190 -0
  52. package/dist/chunk-Y6MDYVJD.js +3063 -0
  53. package/dist/cli/main.d.ts +1 -0
  54. package/dist/cli/main.js +5458 -0
  55. package/dist/index.d.ts +653 -0
  56. package/dist/index.js +79 -0
  57. package/dist/mcp/server.d.ts +1 -0
  58. package/dist/mcp/server.js +472 -0
  59. package/dist/schema-BAWSG7KY.js +22 -0
  60. package/dist/schema-E3QUPL26.js +20 -0
  61. package/dist/schema-EHL7WUT6.js +20 -0
  62. package/docs/019-USAGE.md +625 -0
  63. package/docs/020-current-implementation.md +364 -0
  64. package/docs/021-DOGFOODING-FINDINGS.md +175 -0
  65. package/docs/BACKLOG.md +80 -0
  66. package/docs/CONFIG.md +1123 -0
  67. package/docs/DESIGN.md +439 -0
  68. package/docs/ERRORS.md +383 -0
  69. package/docs/PROJECT.md +88 -0
  70. package/docs/ROADMAP.md +407 -0
  71. package/docs/summarization.md +320 -0
  72. package/docs/test-links.md +9 -0
  73. package/justfile +40 -0
  74. package/package.json +74 -9
  75. package/pnpm-workspace.yaml +5 -0
  76. package/research/INDEX.md +315 -0
  77. package/research/code-review/README.md +90 -0
  78. package/research/code-review/cli-error-handling-review.md +979 -0
  79. package/research/code-review/code-review-validation-report.md +464 -0
  80. package/research/code-review/main-ts-review.md +1128 -0
  81. package/research/config-analysis/01-current-implementation.md +470 -0
  82. package/research/config-analysis/02-strategy-recommendation.md +428 -0
  83. package/research/config-analysis/03-task-candidates.md +715 -0
  84. package/research/config-analysis/033-research-configuration-management.md +828 -0
  85. package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
  86. package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
  87. package/research/config-docs/SUMMARY.md +357 -0
  88. package/research/config-docs/TEST-RESULTS.md +776 -0
  89. package/research/config-docs/TODO.md +542 -0
  90. package/research/config-docs/analysis.md +744 -0
  91. package/research/config-docs/fix-validation.md +502 -0
  92. package/research/config-docs/help-audit.md +264 -0
  93. package/research/config-docs/help-system-analysis.md +890 -0
  94. package/research/dogfood/consolidated-tool-evaluation.md +373 -0
  95. package/research/dogfood/strategy-a/a-synthesis.md +184 -0
  96. package/research/dogfood/strategy-a/a1-docs.md +226 -0
  97. package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
  98. package/research/dogfood/strategy-a/a3-llm.md +164 -0
  99. package/research/dogfood/strategy-b/b-synthesis.md +228 -0
  100. package/research/dogfood/strategy-b/b1-architecture.md +207 -0
  101. package/research/dogfood/strategy-b/b2-gaps.md +258 -0
  102. package/research/dogfood/strategy-b/b3-workflows.md +250 -0
  103. package/research/dogfood/strategy-c/c-synthesis.md +451 -0
  104. package/research/dogfood/strategy-c/c1-explorer.md +192 -0
  105. package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
  106. package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
  107. package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
  108. package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
  109. package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
  110. package/research/effect-cli-error-handling.md +845 -0
  111. package/research/effect-errors-as-values.md +943 -0
  112. package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
  113. package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
  114. package/research/errors-task-analysis/embeddings-analysis.md +709 -0
  115. package/research/errors-task-analysis/index-search-analysis.md +812 -0
  116. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  117. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  118. package/research/issue-review.md +603 -0
  119. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  120. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  121. package/research/llm-summarization/anthropic-2026.md +367 -0
  122. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  123. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  124. package/research/llm-summarization/openai-2026.md +473 -0
  125. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  126. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  127. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  128. package/research/llm-summarization/prototype-results.md +56 -0
  129. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  130. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  131. package/research/mdcontext-error-analysis.md +521 -0
  132. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  133. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  134. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  135. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  136. package/research/mdcontext-pudding/02-search.md +970 -0
  137. package/research/mdcontext-pudding/03-context.md +779 -0
  138. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  139. package/research/mdcontext-pudding/04-tree.md +704 -0
  140. package/research/mdcontext-pudding/05-config.md +1038 -0
  141. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  142. package/research/mdcontext-pudding/06-links.md +679 -0
  143. package/research/mdcontext-pudding/07-stats.md +693 -0
  144. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  145. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  146. package/research/mdcontext-pudding/README.md +168 -0
  147. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  148. package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
  149. package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
  150. package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
  151. package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
  152. package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
  153. package/research/research-quality-review.md +834 -0
  154. package/research/semantic-search/002-research-embedding-models.md +490 -0
  155. package/research/semantic-search/003-research-rag-alternatives.md +523 -0
  156. package/research/semantic-search/004-research-vector-search.md +841 -0
  157. package/research/semantic-search/032-research-semantic-search.md +427 -0
  158. package/research/semantic-search/embedding-text-analysis.md +156 -0
  159. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  160. package/research/semantic-search/query-processing-analysis.md +207 -0
  161. package/research/semantic-search/root-cause-and-solution.md +114 -0
  162. package/research/semantic-search/threshold-validation-report.md +69 -0
  163. package/research/semantic-search/vector-search-analysis.md +63 -0
  164. package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
  165. package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
  166. package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
  167. package/research/task-management-2026/03-lightweight-file-based.md +567 -0
  168. package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
  169. package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
  170. package/research/task-management-2026/linear/02-api-integrations.md +930 -0
  171. package/research/task-management-2026/linear/03-ai-features.md +368 -0
  172. package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
  173. package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
  174. package/research/test-path-issues.md +276 -0
  175. package/review/ALP-76/1-error-type-design.md +962 -0
  176. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  177. package/review/ALP-76/3-error-presentation.md +624 -0
  178. package/review/ALP-76/4-test-coverage.md +625 -0
  179. package/review/ALP-76/5-migration-completeness.md +440 -0
  180. package/review/ALP-76/6-effect-best-practices.md +755 -0
  181. package/scripts/apply-branch-protection.sh +47 -0
  182. package/scripts/branch-protection-templates.json +79 -0
  183. package/scripts/prototype-summarization.ts +346 -0
  184. package/scripts/rebuild-hnswlib.js +58 -0
  185. package/scripts/setup-branch-protection.sh +64 -0
  186. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  187. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  188. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  189. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  190. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  191. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  192. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  193. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  194. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  195. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  196. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  197. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  198. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  199. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  200. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  201. package/src/cli/argv-preprocessor.test.ts +210 -0
  202. package/src/cli/argv-preprocessor.ts +202 -0
  203. package/src/cli/cli.test.ts +627 -0
  204. package/src/cli/commands/backlinks.ts +54 -0
  205. package/src/cli/commands/config-cmd.ts +642 -0
  206. package/src/cli/commands/context.ts +285 -0
  207. package/src/cli/commands/duplicates.ts +122 -0
  208. package/src/cli/commands/embeddings.ts +529 -0
  209. package/src/cli/commands/index-cmd.ts +480 -0
  210. package/src/cli/commands/index.ts +16 -0
  211. package/src/cli/commands/links.ts +52 -0
  212. package/src/cli/commands/search.ts +1281 -0
  213. package/src/cli/commands/stats.ts +149 -0
  214. package/src/cli/commands/tree.ts +128 -0
  215. package/src/cli/config-layer.ts +176 -0
  216. package/src/cli/error-handler.test.ts +235 -0
  217. package/src/cli/error-handler.ts +655 -0
  218. package/src/cli/flag-schemas.ts +341 -0
  219. package/src/cli/help.ts +588 -0
  220. package/src/cli/index.ts +9 -0
  221. package/src/cli/main.ts +435 -0
  222. package/src/cli/options.ts +41 -0
  223. package/src/cli/shared-error-handling.ts +199 -0
  224. package/src/cli/typo-suggester.test.ts +105 -0
  225. package/src/cli/typo-suggester.ts +130 -0
  226. package/src/cli/utils.ts +259 -0
  227. package/src/config/file-provider.test.ts +320 -0
  228. package/src/config/file-provider.ts +273 -0
  229. package/src/config/index.ts +72 -0
  230. package/src/config/integration.test.ts +667 -0
  231. package/src/config/precedence.test.ts +277 -0
  232. package/src/config/precedence.ts +451 -0
  233. package/src/config/schema.test.ts +414 -0
  234. package/src/config/schema.ts +603 -0
  235. package/src/config/service.test.ts +320 -0
  236. package/src/config/service.ts +243 -0
  237. package/src/config/testing.test.ts +264 -0
  238. package/src/config/testing.ts +110 -0
  239. package/src/core/index.ts +1 -0
  240. package/src/core/types.ts +113 -0
  241. package/src/duplicates/detector.test.ts +183 -0
  242. package/src/duplicates/detector.ts +414 -0
  243. package/src/duplicates/index.ts +18 -0
  244. package/src/embeddings/embedding-namespace.test.ts +300 -0
  245. package/src/embeddings/embedding-namespace.ts +947 -0
  246. package/src/embeddings/heading-boost.test.ts +222 -0
  247. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  248. package/src/embeddings/hyde.test.ts +272 -0
  249. package/src/embeddings/hyde.ts +264 -0
  250. package/src/embeddings/index.ts +10 -0
  251. package/src/embeddings/openai-provider.ts +414 -0
  252. package/src/embeddings/pricing.json +22 -0
  253. package/src/embeddings/provider-constants.ts +204 -0
  254. package/src/embeddings/provider-errors.test.ts +967 -0
  255. package/src/embeddings/provider-errors.ts +565 -0
  256. package/src/embeddings/provider-factory.test.ts +240 -0
  257. package/src/embeddings/provider-factory.ts +225 -0
  258. package/src/embeddings/provider-integration.test.ts +788 -0
  259. package/src/embeddings/query-preprocessing.test.ts +187 -0
  260. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  261. package/src/embeddings/semantic-search.ts +1270 -0
  262. package/src/embeddings/types.ts +359 -0
  263. package/src/embeddings/vector-store.ts +708 -0
  264. package/src/embeddings/voyage-provider.ts +313 -0
  265. package/src/errors/errors.test.ts +845 -0
  266. package/src/errors/index.ts +533 -0
  267. package/src/index/ignore-patterns.test.ts +354 -0
  268. package/src/index/ignore-patterns.ts +305 -0
  269. package/src/index/index.ts +4 -0
  270. package/src/index/indexer.ts +684 -0
  271. package/src/index/storage.ts +260 -0
  272. package/src/index/types.ts +147 -0
  273. package/src/index/watcher.ts +189 -0
  274. package/src/index.ts +30 -0
  275. package/src/integration/search-keyword.test.ts +678 -0
  276. package/src/mcp/server.ts +612 -0
  277. package/src/parser/index.ts +1 -0
  278. package/src/parser/parser.test.ts +291 -0
  279. package/src/parser/parser.ts +394 -0
  280. package/src/parser/section-filter.test.ts +277 -0
  281. package/src/parser/section-filter.ts +392 -0
  282. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  283. package/src/search/bm25-store.ts +366 -0
  284. package/src/search/cross-encoder.test.ts +253 -0
  285. package/src/search/cross-encoder.ts +406 -0
  286. package/src/search/fuzzy-search.test.ts +419 -0
  287. package/src/search/fuzzy-search.ts +273 -0
  288. package/src/search/hybrid-search.ts +448 -0
  289. package/src/search/path-matcher.test.ts +276 -0
  290. package/src/search/path-matcher.ts +33 -0
  291. package/src/search/query-parser.test.ts +260 -0
  292. package/src/search/query-parser.ts +319 -0
  293. package/src/search/searcher.test.ts +280 -0
  294. package/src/search/searcher.ts +724 -0
  295. package/src/search/wink-bm25.d.ts +30 -0
  296. package/src/summarization/cli-providers/claude.ts +202 -0
  297. package/src/summarization/cli-providers/detection.test.ts +273 -0
  298. package/src/summarization/cli-providers/detection.ts +118 -0
  299. package/src/summarization/cli-providers/index.ts +8 -0
  300. package/src/summarization/cost.test.ts +139 -0
  301. package/src/summarization/cost.ts +102 -0
  302. package/src/summarization/error-handler.test.ts +127 -0
  303. package/src/summarization/error-handler.ts +111 -0
  304. package/src/summarization/index.ts +102 -0
  305. package/src/summarization/pipeline.test.ts +498 -0
  306. package/src/summarization/pipeline.ts +231 -0
  307. package/src/summarization/prompts.test.ts +269 -0
  308. package/src/summarization/prompts.ts +133 -0
  309. package/src/summarization/provider-factory.test.ts +396 -0
  310. package/src/summarization/provider-factory.ts +178 -0
  311. package/src/summarization/types.ts +184 -0
  312. package/src/summarize/budget-bugs.test.ts +620 -0
  313. package/src/summarize/formatters.ts +419 -0
  314. package/src/summarize/index.ts +20 -0
  315. package/src/summarize/summarizer.test.ts +275 -0
  316. package/src/summarize/summarizer.ts +597 -0
  317. package/src/summarize/verify-bugs.test.ts +238 -0
  318. package/src/types/huggingface-transformers.d.ts +66 -0
  319. package/src/utils/index.ts +1 -0
  320. package/src/utils/tokens.test.ts +142 -0
  321. package/src/utils/tokens.ts +186 -0
  322. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  323. package/tests/fixtures/cli/.mdcontext/config.json +8 -0
  324. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  325. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  326. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
  327. package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
  328. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
  329. package/tests/fixtures/cli/README.md +9 -0
  330. package/tests/fixtures/cli/api-reference.md +11 -0
  331. package/tests/fixtures/cli/getting-started.md +11 -0
  332. package/tests/integration/embed-index.test.ts +712 -0
  333. package/tests/integration/search-context.test.ts +469 -0
  334. package/tests/integration/search-semantic.test.ts +522 -0
  335. package/tsconfig.json +26 -0
  336. package/vitest.config.ts +16 -0
  337. package/vitest.setup.ts +12 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * STATS Command
3
+ *
4
+ * Show index statistics.
5
+ */
6
+
7
+ import * as path from 'node:path'
8
+ import { Args, Command } from '@effect/cli'
9
+ import { Console, Effect } from 'effect'
10
+ import { getEmbeddingStats } from '../../embeddings/semantic-search.js'
11
+ import {
12
+ createStorage,
13
+ loadDocumentIndex,
14
+ loadSectionIndex,
15
+ } from '../../index/storage.js'
16
+ import { jsonOption, prettyOption } from '../options.js'
17
+ import { formatJson } from '../utils.js'
18
+
19
+ interface IndexStats {
20
+ documentCount: number
21
+ totalTokens: number
22
+ avgTokensPerDoc: number
23
+ totalSections: number
24
+ sectionsByLevel: Record<number, number>
25
+ tokenDistribution: {
26
+ min: number
27
+ max: number
28
+ median: number
29
+ }
30
+ }
31
+
32
+ export const statsCommand = Command.make(
33
+ 'stats',
34
+ {
35
+ path: Args.directory({ name: 'path' }).pipe(
36
+ Args.withDescription('Directory to show stats for'),
37
+ Args.withDefault('.'),
38
+ ),
39
+ json: jsonOption,
40
+ pretty: prettyOption,
41
+ },
42
+ ({ path: dirPath, json, pretty }) =>
43
+ Effect.gen(function* () {
44
+ const resolvedRoot = path.resolve(dirPath)
45
+ const storage = createStorage(resolvedRoot)
46
+
47
+ // Load document and section indexes
48
+ const docIndex = yield* loadDocumentIndex(storage)
49
+ const sectionIndex = yield* loadSectionIndex(storage)
50
+
51
+ // Handle case where index doesn't exist
52
+ if (!docIndex || !sectionIndex) {
53
+ if (json) {
54
+ yield* Console.log(formatJson({ error: 'No index found' }, pretty))
55
+ } else {
56
+ yield* Console.log('No index found.')
57
+ yield* Console.log("Run 'mdcontext index <path>' to create an index.")
58
+ }
59
+ return
60
+ }
61
+
62
+ // Calculate index stats
63
+ const docs = Object.values(docIndex.documents)
64
+ const sections = Object.values(sectionIndex.sections)
65
+
66
+ const tokenCounts = docs.map((d) => d.tokenCount).sort((a, b) => a - b)
67
+ const totalTokens = tokenCounts.reduce((sum, t) => sum + t, 0)
68
+
69
+ // Count sections by level
70
+ const sectionsByLevel: Record<number, number> = {}
71
+ for (const section of sections) {
72
+ sectionsByLevel[section.level] =
73
+ (sectionsByLevel[section.level] || 0) + 1
74
+ }
75
+
76
+ const indexStats: IndexStats = {
77
+ documentCount: docs.length,
78
+ totalTokens,
79
+ avgTokensPerDoc:
80
+ docs.length > 0 ? Math.round(totalTokens / docs.length) : 0,
81
+ totalSections: sections.length,
82
+ sectionsByLevel,
83
+ tokenDistribution: {
84
+ min: tokenCounts[0] || 0,
85
+ max: tokenCounts[tokenCounts.length - 1] || 0,
86
+ median: tokenCounts[Math.floor(tokenCounts.length / 2)] || 0,
87
+ },
88
+ }
89
+
90
+ // Get embedding stats
91
+ const embeddingStats = yield* getEmbeddingStats(resolvedRoot)
92
+
93
+ if (json) {
94
+ yield* Console.log(
95
+ formatJson({ ...indexStats, embeddings: embeddingStats }, pretty),
96
+ )
97
+ } else {
98
+ yield* Console.log('Index statistics:')
99
+ yield* Console.log('')
100
+ yield* Console.log(' Documents')
101
+ yield* Console.log(` Count: ${indexStats.documentCount}`)
102
+ yield* Console.log(
103
+ ` Tokens: ${indexStats.totalTokens.toLocaleString()}`,
104
+ )
105
+ yield* Console.log(` Avg/doc: ${indexStats.avgTokensPerDoc}`)
106
+ yield* Console.log('')
107
+ yield* Console.log(' Token distribution')
108
+ yield* Console.log(
109
+ ` Min: ${indexStats.tokenDistribution.min}`,
110
+ )
111
+ yield* Console.log(
112
+ ` Median: ${indexStats.tokenDistribution.median}`,
113
+ )
114
+ yield* Console.log(
115
+ ` Max: ${indexStats.tokenDistribution.max}`,
116
+ )
117
+ yield* Console.log('')
118
+ yield* Console.log(' Sections')
119
+ yield* Console.log(` Total: ${indexStats.totalSections}`)
120
+ // Show section depth breakdown
121
+ const levels = Object.keys(sectionsByLevel)
122
+ .map(Number)
123
+ .sort((a, b) => a - b)
124
+ for (const level of levels) {
125
+ yield* Console.log(
126
+ ` h${level}: ${sectionsByLevel[level]}`,
127
+ )
128
+ }
129
+ yield* Console.log('')
130
+ yield* Console.log(' Embeddings')
131
+ if (embeddingStats.hasEmbeddings) {
132
+ yield* Console.log(` Vectors: ${embeddingStats.count}`)
133
+ yield* Console.log(` Provider: ${embeddingStats.provider}`)
134
+ if (embeddingStats.model) {
135
+ yield* Console.log(` Model: ${embeddingStats.model}`)
136
+ }
137
+ yield* Console.log(` Dimensions: ${embeddingStats.dimensions}`)
138
+ yield* Console.log(
139
+ ` Cost: $${embeddingStats.totalCost.toFixed(6)}`,
140
+ )
141
+ } else {
142
+ yield* Console.log(' Not enabled')
143
+ yield* Console.log(
144
+ " Run 'mdcontext index --embed' to build embeddings.",
145
+ )
146
+ }
147
+ }
148
+ }),
149
+ ).pipe(Command.withDescription('Index statistics'))
@@ -0,0 +1,128 @@
1
+ /**
2
+ * TREE Command
3
+ *
4
+ * Show file tree or document outline.
5
+ */
6
+
7
+ import * as fs from 'node:fs'
8
+ import * as path from 'node:path'
9
+ import { Args, Command } from '@effect/cli'
10
+ import { Console, Effect } from 'effect'
11
+ import type { MdSection } from '../../core/types.js'
12
+ import { FileReadError, ParseError } from '../../errors/index.js'
13
+ import { parseFile } from '../../parser/parser.js'
14
+ import { jsonOption, prettyOption } from '../options.js'
15
+ import { formatJson, walkDirEffect } from '../utils.js'
16
+
17
+ export const treeCommand = Command.make(
18
+ 'tree',
19
+ {
20
+ pathArg: Args.text({ name: 'path' }).pipe(
21
+ Args.withDescription('Directory (shows files) or file (shows outline)'),
22
+ Args.withDefault('.'),
23
+ ),
24
+ json: jsonOption,
25
+ pretty: prettyOption,
26
+ },
27
+ ({ pathArg, json, pretty }) =>
28
+ Effect.gen(function* () {
29
+ const resolvedPath = path.resolve(pathArg)
30
+
31
+ // Auto-detect: file or directory
32
+ const stat = yield* Effect.try({
33
+ try: () => fs.statSync(resolvedPath),
34
+ catch: (e) =>
35
+ new FileReadError({
36
+ path: resolvedPath,
37
+ message: `Cannot access path: ${e instanceof Error ? e.message : String(e)}`,
38
+ cause: e,
39
+ }),
40
+ })
41
+
42
+ if (stat.isFile()) {
43
+ // Show document outline
44
+ const result = yield* parseFile(resolvedPath).pipe(
45
+ Effect.mapError((e) =>
46
+ e._tag === 'ParseError'
47
+ ? new ParseError({
48
+ message: e.message,
49
+ path: resolvedPath,
50
+ ...(e.line !== undefined && { line: e.line }),
51
+ ...(e.column !== undefined && { column: e.column }),
52
+ })
53
+ : new FileReadError({
54
+ path: e.path,
55
+ message: e.message,
56
+ }),
57
+ ),
58
+ )
59
+
60
+ const extractStructure = (
61
+ section: MdSection,
62
+ ): {
63
+ heading: string
64
+ level: number
65
+ tokens: number
66
+ children: unknown[]
67
+ } => ({
68
+ heading: section.heading,
69
+ level: section.level,
70
+ tokens: section.metadata.tokenCount,
71
+ children: section.children.map(extractStructure),
72
+ })
73
+
74
+ if (json) {
75
+ const structure = {
76
+ title: result.title,
77
+ path: result.path,
78
+ totalTokens: result.metadata.tokenCount,
79
+ sections: result.sections.map(extractStructure),
80
+ }
81
+ yield* Console.log(formatJson(structure, pretty))
82
+ } else {
83
+ yield* Console.log(`# ${result.title}`)
84
+ yield* Console.log(`Total tokens: ${result.metadata.tokenCount}`)
85
+ yield* Console.log('')
86
+
87
+ const printOutline = (
88
+ section: MdSection,
89
+ depth: number = 0,
90
+ ): Effect.Effect<void> =>
91
+ Effect.gen(function* () {
92
+ const indent = ' '.repeat(depth)
93
+ const marker = '#'.repeat(section.level)
94
+ yield* Console.log(
95
+ `${indent}${marker} ${section.heading} [${section.metadata.tokenCount} tokens]`,
96
+ )
97
+ for (const child of section.children) {
98
+ yield* printOutline(child, depth + 1)
99
+ }
100
+ })
101
+
102
+ for (const section of result.sections) {
103
+ yield* printOutline(section)
104
+ }
105
+ }
106
+ } else {
107
+ // Show file list
108
+ const files = yield* walkDirEffect(resolvedPath)
109
+
110
+ const tree = [...files].sort().map((f) => ({
111
+ path: f,
112
+ relativePath: path.relative(resolvedPath, f),
113
+ }))
114
+
115
+ if (json) {
116
+ yield* Console.log(formatJson(tree, pretty))
117
+ } else {
118
+ yield* Console.log(`Markdown files in ${resolvedPath}:`)
119
+ yield* Console.log('')
120
+ for (const file of tree) {
121
+ yield* Console.log(` ${file.relativePath}`)
122
+ }
123
+ yield* Console.log('')
124
+ yield* Console.log(`Total: ${tree.length} files`)
125
+ }
126
+ }
127
+ }),
128
+ ).pipe(Command.withDescription('Show files or document outline'))
@@ -0,0 +1,176 @@
1
+ /**
2
+ * CLI Configuration Layer
3
+ *
4
+ * Creates a configuration layer for use in CLI commands.
5
+ * Loads config with precedence: CLI flags > Environment > Config file > Defaults
6
+ */
7
+
8
+ import { Effect, Layer } from 'effect'
9
+ import {
10
+ type ConfigProviderOptions,
11
+ ConfigService,
12
+ ConfigServiceDefault,
13
+ createConfigProvider,
14
+ type MdContextConfig,
15
+ } from '../config/index.js'
16
+
17
+ /**
18
+ * Create a ConfigService layer from options.
19
+ *
20
+ * This loads configuration with the standard precedence chain:
21
+ * 1. CLI flags (highest priority)
22
+ * 2. Environment variables (MDCONTEXT_*)
23
+ * 3. Config file (if found)
24
+ * 4. Defaults
25
+ *
26
+ * @param options - Configuration provider options
27
+ * @returns A Layer that provides ConfigService
28
+ */
29
+ export const makeCliConfigLayer = (
30
+ options: ConfigProviderOptions = {},
31
+ ): Effect.Effect<Layer.Layer<ConfigService, never, never>, never, never> =>
32
+ Effect.gen(function* () {
33
+ // Create the config provider with precedence chain
34
+ const providerResult = yield* createConfigProvider(options).pipe(
35
+ Effect.catchAll(() =>
36
+ // If config loading fails, use empty provider (defaults will apply)
37
+ Effect.succeed(null),
38
+ ),
39
+ )
40
+
41
+ if (!providerResult) {
42
+ // Fall back to default config if provider creation failed
43
+ return ConfigServiceDefault
44
+ }
45
+
46
+ // Load the config using the provider
47
+ const configResult = yield* Effect.gen(function* () {
48
+ // Import the schema to load config
49
+ const { MdContextConfig: MdContextConfigSchema } = yield* Effect.promise(
50
+ async () => import('../config/schema.js'),
51
+ )
52
+ return yield* MdContextConfigSchema
53
+ }).pipe(
54
+ Effect.withConfigProvider(providerResult),
55
+ Effect.catchAll(() => Effect.succeed(null)),
56
+ )
57
+
58
+ if (!configResult) {
59
+ // Fall back to default config if loading failed
60
+ return ConfigServiceDefault
61
+ }
62
+
63
+ // Create a layer with the loaded config
64
+ return Layer.succeed(ConfigService, configResult)
65
+ })
66
+
67
+ /**
68
+ * Create the default CLI configuration layer.
69
+ *
70
+ * This loads configuration from:
71
+ * - Environment variables (MDCONTEXT_*)
72
+ * - Config file (mdcontext.config.ts/json)
73
+ * - Built-in defaults
74
+ *
75
+ * No CLI flags are applied at this level - commands handle their own flag overrides.
76
+ */
77
+ export const defaultCliConfigLayer: Effect.Effect<
78
+ Layer.Layer<ConfigService, never, never>,
79
+ never,
80
+ never
81
+ > = makeCliConfigLayer({
82
+ workingDir: process.cwd(),
83
+ })
84
+
85
+ /**
86
+ * Synchronously create a default config layer.
87
+ *
88
+ * For use in cases where async config loading isn't possible.
89
+ * Uses only environment variables and defaults.
90
+ */
91
+ export const defaultCliConfigLayerSync: Layer.Layer<
92
+ ConfigService,
93
+ never,
94
+ never
95
+ > = ConfigServiceDefault
96
+
97
+ /**
98
+ * Get config value with CLI flag override.
99
+ *
100
+ * Helper function for commands to get a config value, preferring
101
+ * an explicit CLI flag value if provided.
102
+ *
103
+ * @param cliValue - Value from CLI flag (may be undefined)
104
+ * @param configValue - Value from config
105
+ * @returns The CLI value if provided, otherwise the config value
106
+ */
107
+ export const withCliOverride = <T>(
108
+ cliValue: T | undefined,
109
+ configValue: T,
110
+ ): T => {
111
+ return cliValue !== undefined ? cliValue : configValue
112
+ }
113
+
114
+ /**
115
+ * Extract relevant config sections for a command.
116
+ */
117
+ export type SearchConfigValues = {
118
+ defaultLimit: number
119
+ maxLimit: number
120
+ minSimilarity: number
121
+ includeSnippets: boolean
122
+ snippetLength: number
123
+ }
124
+
125
+ export type IndexConfigValues = {
126
+ maxDepth: number
127
+ excludePatterns: readonly string[]
128
+ fileExtensions: readonly string[]
129
+ followSymlinks: boolean
130
+ indexDir: string
131
+ }
132
+
133
+ export type OutputConfigValues = {
134
+ format: 'text' | 'json'
135
+ color: boolean
136
+ prettyJson: boolean
137
+ verbose: boolean
138
+ debug: boolean
139
+ }
140
+
141
+ /**
142
+ * Extract search config from full config.
143
+ */
144
+ export const getSearchConfig = (
145
+ config: MdContextConfig,
146
+ ): SearchConfigValues => ({
147
+ defaultLimit: config.search.defaultLimit,
148
+ maxLimit: config.search.maxLimit,
149
+ minSimilarity: config.search.minSimilarity,
150
+ includeSnippets: config.search.includeSnippets,
151
+ snippetLength: config.search.snippetLength,
152
+ })
153
+
154
+ /**
155
+ * Extract index config from full config.
156
+ */
157
+ export const getIndexConfig = (config: MdContextConfig): IndexConfigValues => ({
158
+ maxDepth: config.index.maxDepth,
159
+ excludePatterns: config.index.excludePatterns,
160
+ fileExtensions: config.index.fileExtensions,
161
+ followSymlinks: config.index.followSymlinks,
162
+ indexDir: config.index.indexDir,
163
+ })
164
+
165
+ /**
166
+ * Extract output config from full config.
167
+ */
168
+ export const getOutputConfig = (
169
+ config: MdContextConfig,
170
+ ): OutputConfigValues => ({
171
+ format: config.output.format,
172
+ color: config.output.color,
173
+ prettyJson: config.output.prettyJson,
174
+ verbose: config.output.verbose,
175
+ debug: config.output.debug,
176
+ })
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Unit tests for CLI error handler
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
+ import { formatEffectCliError } from './error-handler.js'
7
+
8
+ describe('formatEffectCliError', () => {
9
+ let originalArgv: string[]
10
+
11
+ beforeEach(() => {
12
+ originalArgv = process.argv
13
+ })
14
+
15
+ afterEach(() => {
16
+ process.argv = originalArgv
17
+ })
18
+
19
+ describe('mode validation errors with suggestions', () => {
20
+ it('suggests "semantic" for "semantics"', () => {
21
+ process.argv = ['node', 'cli', 'search', '--mode', 'semantics', 'test']
22
+
23
+ const error = {
24
+ _tag: 'ValidationError',
25
+ error: {
26
+ _tag: 'Paragraph',
27
+ value: {
28
+ _tag: 'Text',
29
+ value:
30
+ 'Expected one of the following cases: hybrid, semantic, keyword',
31
+ },
32
+ },
33
+ }
34
+
35
+ const result = formatEffectCliError(error)
36
+ expect(result).toContain("Did you mean '--mode semantic'?")
37
+ })
38
+
39
+ it('suggests "keyword" for "keywords"', () => {
40
+ process.argv = ['node', 'cli', 'search', '--mode', 'keywords', 'test']
41
+
42
+ const error = {
43
+ _tag: 'ValidationError',
44
+ error: {
45
+ _tag: 'Paragraph',
46
+ value: {
47
+ _tag: 'Text',
48
+ value:
49
+ 'Expected one of the following cases: hybrid, semantic, keyword',
50
+ },
51
+ },
52
+ }
53
+
54
+ const result = formatEffectCliError(error)
55
+ expect(result).toContain("Did you mean '--mode keyword'?")
56
+ })
57
+
58
+ it('suggests "hybrid" for "hybrit"', () => {
59
+ process.argv = ['node', 'cli', 'search', '--mode', 'hybrit', 'test']
60
+
61
+ const error = {
62
+ _tag: 'ValidationError',
63
+ error: {
64
+ _tag: 'Paragraph',
65
+ value: {
66
+ _tag: 'Text',
67
+ value:
68
+ 'Expected one of the following cases: hybrid, semantic, keyword',
69
+ },
70
+ },
71
+ }
72
+
73
+ const result = formatEffectCliError(error)
74
+ expect(result).toContain("Did you mean '--mode hybrid'?")
75
+ })
76
+
77
+ it('suggests "semantic" for "semant"', () => {
78
+ process.argv = ['node', 'cli', 'search', '--mode', 'semant', 'test']
79
+
80
+ const error = {
81
+ _tag: 'ValidationError',
82
+ error: {
83
+ _tag: 'Paragraph',
84
+ value: {
85
+ _tag: 'Text',
86
+ value:
87
+ 'Expected one of the following cases: hybrid, semantic, keyword',
88
+ },
89
+ },
90
+ }
91
+
92
+ const result = formatEffectCliError(error)
93
+ expect(result).toContain("Did you mean '--mode semantic'?")
94
+ })
95
+
96
+ it('suggests "keyword" for "keywordd"', () => {
97
+ process.argv = ['node', 'cli', 'search', '--mode', 'keywordd', 'test']
98
+
99
+ const error = {
100
+ _tag: 'ValidationError',
101
+ error: {
102
+ _tag: 'Paragraph',
103
+ value: {
104
+ _tag: 'Text',
105
+ value:
106
+ 'Expected one of the following cases: hybrid, semantic, keyword',
107
+ },
108
+ },
109
+ }
110
+
111
+ const result = formatEffectCliError(error)
112
+ expect(result).toContain("Did you mean '--mode keyword'?")
113
+ })
114
+
115
+ it('does not suggest for typos too far off', () => {
116
+ process.argv = ['node', 'cli', 'search', '--mode', 'xyz', 'test']
117
+
118
+ const error = {
119
+ _tag: 'ValidationError',
120
+ error: {
121
+ _tag: 'Paragraph',
122
+ value: {
123
+ _tag: 'Text',
124
+ value:
125
+ 'Expected one of the following cases: hybrid, semantic, keyword',
126
+ },
127
+ },
128
+ }
129
+
130
+ const result = formatEffectCliError(error)
131
+ expect(result).not.toContain('Did you mean')
132
+ })
133
+
134
+ it('handles --mode=value syntax', () => {
135
+ process.argv = ['node', 'cli', 'search', '--mode=semantics', 'test']
136
+
137
+ const error = {
138
+ _tag: 'ValidationError',
139
+ error: {
140
+ _tag: 'Paragraph',
141
+ value: {
142
+ _tag: 'Text',
143
+ value:
144
+ 'Expected one of the following cases: hybrid, semantic, keyword',
145
+ },
146
+ },
147
+ }
148
+
149
+ const result = formatEffectCliError(error)
150
+ expect(result).toContain("Did you mean '--mode semantic'?")
151
+ })
152
+
153
+ it('handles -m short flag', () => {
154
+ process.argv = ['node', 'cli', 'search', '-m', 'semantics', 'test']
155
+
156
+ const error = {
157
+ _tag: 'ValidationError',
158
+ error: {
159
+ _tag: 'Paragraph',
160
+ value: {
161
+ _tag: 'Text',
162
+ value:
163
+ 'Expected one of the following cases: hybrid, semantic, keyword',
164
+ },
165
+ },
166
+ }
167
+
168
+ const result = formatEffectCliError(error)
169
+ expect(result).toContain("Did you mean '--mode semantic'?")
170
+ })
171
+
172
+ it('does not suggest for non-mode validation errors', () => {
173
+ process.argv = ['node', 'cli', 'search', 'test']
174
+
175
+ const error = {
176
+ _tag: 'ValidationError',
177
+ error: {
178
+ _tag: 'Paragraph',
179
+ value: {
180
+ _tag: 'Text',
181
+ value: 'Some other validation error',
182
+ },
183
+ },
184
+ }
185
+
186
+ const result = formatEffectCliError(error)
187
+ expect(result).toBe('Some other validation error')
188
+ expect(result).not.toContain('Did you mean')
189
+ })
190
+ })
191
+
192
+ describe('other error types', () => {
193
+ it('handles MissingValue errors', () => {
194
+ process.argv = ['node', 'cli', 'search']
195
+
196
+ const error = {
197
+ _tag: 'MissingValue',
198
+ error: {
199
+ _tag: 'Paragraph',
200
+ value: {
201
+ _tag: 'Text',
202
+ value: 'Missing required argument',
203
+ },
204
+ },
205
+ }
206
+
207
+ const result = formatEffectCliError(error)
208
+ expect(result).toBe('Missing required argument')
209
+ })
210
+
211
+ it('handles InvalidValue errors', () => {
212
+ process.argv = ['node', 'cli', 'search']
213
+
214
+ const error = {
215
+ _tag: 'InvalidValue',
216
+ error: {
217
+ _tag: 'Paragraph',
218
+ value: {
219
+ _tag: 'Text',
220
+ value: 'Invalid value provided',
221
+ },
222
+ },
223
+ }
224
+
225
+ const result = formatEffectCliError(error)
226
+ expect(result).toBe('Invalid value provided')
227
+ })
228
+
229
+ it('handles unknown error types', () => {
230
+ const error = { message: 'Unknown error' }
231
+ const result = formatEffectCliError(error)
232
+ expect(result).toBe('[object Object]')
233
+ })
234
+ })
235
+ })