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,480 @@
1
+ /**
2
+ * INDEX Command
3
+ *
4
+ * Index markdown files for fast searching.
5
+ */
6
+
7
+ import * as path from 'node:path'
8
+ import * as readline from 'node:readline'
9
+ import { Args, Command, Options } from '@effect/cli'
10
+ import { Console, Effect, Option } from 'effect'
11
+ import type {
12
+ BuildEmbeddingsResult,
13
+ EmbeddingEstimate,
14
+ } from '../../embeddings/semantic-search.js'
15
+ import {
16
+ buildEmbeddings,
17
+ checkPricingFreshness,
18
+ estimateEmbeddingCost,
19
+ getPricingDate,
20
+ } from '../../embeddings/semantic-search.js'
21
+ import { buildIndex } from '../../index/indexer.js'
22
+ import { watchDirectory } from '../../index/watcher.js'
23
+ import { forceOption, jsonOption, prettyOption } from '../options.js'
24
+ import {
25
+ createCostEstimateErrorHandler,
26
+ createEmbeddingErrorHandler,
27
+ } from '../shared-error-handling.js'
28
+ import { formatJson, hasEmbeddings } from '../utils.js'
29
+
30
+ const promptUser = (message: string): Promise<string> => {
31
+ return new Promise((resolve) => {
32
+ const rl = readline.createInterface({
33
+ input: process.stdin,
34
+ output: process.stdout,
35
+ })
36
+ rl.question(message, (answer) => {
37
+ rl.close()
38
+ resolve(answer.trim().toLowerCase())
39
+ })
40
+ })
41
+ }
42
+
43
+ export const indexCommand = Command.make(
44
+ 'index',
45
+ {
46
+ path: Args.directory({ name: 'path' }).pipe(
47
+ Args.withDescription('Directory to index'),
48
+ Args.withDefault('.'),
49
+ ),
50
+ embed: Options.boolean('embed').pipe(
51
+ Options.withAlias('e'),
52
+ Options.withDescription('Also build semantic embeddings'),
53
+ Options.withDefault(false),
54
+ ),
55
+ noEmbed: Options.boolean('no-embed').pipe(
56
+ Options.withDescription('Skip semantic search prompt'),
57
+ Options.withDefault(false),
58
+ ),
59
+ exclude: Options.text('exclude').pipe(
60
+ Options.withAlias('x'),
61
+ Options.withDescription(
62
+ 'Additional patterns to exclude (comma-separated). Patterns from .gitignore and .mdcontextignore are honored automatically.',
63
+ ),
64
+ Options.optional,
65
+ ),
66
+ noGitignore: Options.boolean('no-gitignore').pipe(
67
+ Options.withDescription('Ignore .gitignore file'),
68
+ Options.withDefault(false),
69
+ ),
70
+ provider: Options.choice('provider', [
71
+ 'openai',
72
+ 'ollama',
73
+ 'lm-studio',
74
+ 'openrouter',
75
+ 'voyage',
76
+ ]).pipe(
77
+ Options.withDescription(
78
+ 'Embedding provider: openai, ollama, lm-studio, openrouter, or voyage',
79
+ ),
80
+ Options.optional,
81
+ ),
82
+ providerBaseUrl: Options.text('provider-base-url').pipe(
83
+ Options.withDescription('Custom provider API base URL'),
84
+ Options.optional,
85
+ ),
86
+ providerModel: Options.text('provider-model').pipe(
87
+ Options.withDescription('Embedding model to use'),
88
+ Options.optional,
89
+ ),
90
+ hnswM: Options.integer('hnsw-m').pipe(
91
+ Options.withDescription(
92
+ 'HNSW M parameter: max connections per node. Higher = better recall, larger index. Recommended: 12 (speed), 16 (balanced, default), 24 (quality)',
93
+ ),
94
+ Options.optional,
95
+ ),
96
+ hnswEfConstruction: Options.integer('hnsw-ef-construction').pipe(
97
+ Options.withDescription(
98
+ 'HNSW efConstruction: construction-time search width. Higher = better quality, slower builds. Recommended: 128 (speed), 200 (balanced, default), 256 (quality)',
99
+ ),
100
+ Options.optional,
101
+ ),
102
+ timeout: Options.integer('timeout').pipe(
103
+ Options.withAlias('t'),
104
+ Options.withDescription(
105
+ 'Request timeout in milliseconds for embedding API calls (default: 30000)',
106
+ ),
107
+ Options.optional,
108
+ ),
109
+ watch: Options.boolean('watch').pipe(
110
+ Options.withAlias('w'),
111
+ Options.withDescription('Watch for changes'),
112
+ Options.withDefault(false),
113
+ ),
114
+ force: forceOption,
115
+ json: jsonOption,
116
+ pretty: prettyOption,
117
+ },
118
+ ({
119
+ path: dirPath,
120
+ embed,
121
+ noEmbed,
122
+ exclude,
123
+ noGitignore,
124
+ provider,
125
+ providerBaseUrl,
126
+ providerModel,
127
+ hnswM,
128
+ hnswEfConstruction,
129
+ timeout,
130
+ watch: watchMode,
131
+ force,
132
+ json,
133
+ pretty,
134
+ }) =>
135
+ Effect.gen(function* () {
136
+ const resolvedDir = path.resolve(dirPath)
137
+
138
+ // Parse exclude patterns - CLI adds to ignore files
139
+ // Note: buildIndex now honors .gitignore and .mdcontextignore by default
140
+ const cliExcludePatterns =
141
+ exclude._tag === 'Some'
142
+ ? exclude.value.split(',').map((p) => p.trim())
143
+ : undefined
144
+
145
+ if (watchMode) {
146
+ yield* Console.log(`Watching ${resolvedDir} for changes...`)
147
+ yield* Console.log('Press Ctrl+C to stop.')
148
+ yield* Console.log('')
149
+
150
+ const watcher = yield* watchDirectory(resolvedDir, {
151
+ force,
152
+ exclude: cliExcludePatterns,
153
+ honorGitignore: !noGitignore,
154
+ onIndex: (result) => {
155
+ if (json) {
156
+ console.log(formatJson(result, pretty))
157
+ } else {
158
+ console.log(
159
+ `Re-indexed ${result.documentsIndexed} documents (${result.duration}ms)`,
160
+ )
161
+ }
162
+ },
163
+ onError: (error) => {
164
+ console.error(`Watch error: ${error.message}`)
165
+ },
166
+ })
167
+
168
+ yield* Effect.async<never, never>(() => {
169
+ process.on('SIGINT', () => {
170
+ watcher.stop()
171
+ console.log('\nStopped watching.')
172
+ process.exit(0)
173
+ })
174
+ })
175
+ } else {
176
+ yield* Console.log(`Indexing ${resolvedDir}...`)
177
+
178
+ const result = yield* buildIndex(resolvedDir, {
179
+ force,
180
+ exclude: cliExcludePatterns,
181
+ honorGitignore: !noGitignore,
182
+ onProgress: (progress) => {
183
+ if (!json) {
184
+ const progressMsg = ` [${progress.current}/${progress.total}] ${progress.filePath}`
185
+ process.stdout.write(`\x1b[2K\r${progressMsg}`)
186
+ }
187
+ },
188
+ })
189
+
190
+ // Clear the progress line after indexing completes
191
+ if (!json) {
192
+ process.stdout.write('\x1b[2K\r')
193
+ }
194
+
195
+ if (!json) {
196
+ yield* Console.log('')
197
+ // Show totals, with "newly indexed" count if incremental
198
+ const newlyIndexed =
199
+ result.documentsIndexed < result.totalDocuments
200
+ ? ` (${result.documentsIndexed} updated)`
201
+ : ''
202
+ yield* Console.log(
203
+ `Indexed ${result.totalDocuments} documents${newlyIndexed}`,
204
+ )
205
+ yield* Console.log(` Sections: ${result.totalSections}`)
206
+ yield* Console.log(` Links: ${result.totalLinks}`)
207
+ yield* Console.log(` Duration: ${result.duration}ms`)
208
+
209
+ // Show skip summary if any files were skipped
210
+ if (result.skipped.total > 0) {
211
+ const skipParts: string[] = []
212
+ if (result.skipped.unchanged > 0) {
213
+ skipParts.push(`${result.skipped.unchanged} unchanged`)
214
+ }
215
+ if (result.skipped.hidden > 0) {
216
+ skipParts.push(`${result.skipped.hidden} hidden`)
217
+ }
218
+ if (result.skipped.excluded > 0) {
219
+ skipParts.push(`${result.skipped.excluded} excluded`)
220
+ }
221
+ yield* Console.log(` Skipped: ${skipParts.join(', ')}`)
222
+ }
223
+
224
+ if (result.errors.length > 0) {
225
+ yield* Console.log('')
226
+ yield* Console.log(`Errors (${result.errors.length}):`)
227
+ for (const error of result.errors) {
228
+ yield* Console.log(` ${error.path}: ${error.message}`)
229
+ }
230
+ }
231
+ }
232
+
233
+ // Check if we should prompt for semantic search
234
+ const embedsExist = yield* Effect.promise(() =>
235
+ hasEmbeddings(resolvedDir),
236
+ )
237
+
238
+ // Build embeddings if requested or after user prompt
239
+ if (embed) {
240
+ yield* Console.log('')
241
+
242
+ // Show cost estimate first - errors propagate to CLI boundary
243
+ const estimate = yield* estimateEmbeddingCost(resolvedDir, {
244
+ excludePatterns: cliExcludePatterns,
245
+ })
246
+
247
+ if (!json) {
248
+ yield* Console.log(`Found ${estimate.totalFiles} files to embed:`)
249
+ for (const dir of estimate.byDirectory) {
250
+ const costStr =
251
+ dir.estimatedCost < 0.001
252
+ ? '<$0.001'
253
+ : `~$${dir.estimatedCost.toFixed(4)}`
254
+ yield* Console.log(
255
+ ` ${dir.directory.padEnd(20)} ${String(dir.fileCount).padStart(3)} files ${costStr}`,
256
+ )
257
+ }
258
+ yield* Console.log('')
259
+ yield* Console.log(
260
+ `Total: ~${estimate.totalTokens.toLocaleString()} tokens, ~$${estimate.totalCost.toFixed(4)} (pricing as of ${getPricingDate()}), ~${estimate.estimatedTimeSeconds}s`,
261
+ )
262
+
263
+ // Check for stale pricing data
264
+ const stalenessWarning = checkPricingFreshness()
265
+ if (stalenessWarning) {
266
+ yield* Console.log(` Warning: ${stalenessWarning}`)
267
+ }
268
+ yield* Console.log('')
269
+ }
270
+
271
+ if (!force) {
272
+ yield* Console.log('Checking embeddings...')
273
+ } else {
274
+ yield* Console.log('Rebuilding embeddings (--force specified)...')
275
+ }
276
+
277
+ // Build provider config from CLI flags if specified
278
+ const cliTimeout = Option.getOrUndefined(timeout)
279
+ const providerConfig = Option.isSome(provider)
280
+ ? {
281
+ provider: provider.value as
282
+ | 'openai'
283
+ | 'ollama'
284
+ | 'lm-studio'
285
+ | 'openrouter'
286
+ | 'voyage',
287
+ baseURL: Option.getOrUndefined(providerBaseUrl),
288
+ model: Option.getOrUndefined(providerModel),
289
+ timeout: cliTimeout,
290
+ }
291
+ : cliTimeout !== undefined
292
+ ? { provider: 'openai' as const, timeout: cliTimeout }
293
+ : undefined
294
+
295
+ // Build HNSW options from CLI flags if specified
296
+ const hnswOptions =
297
+ Option.isSome(hnswM) || Option.isSome(hnswEfConstruction)
298
+ ? {
299
+ m: Option.getOrUndefined(hnswM),
300
+ efConstruction: Option.getOrUndefined(hnswEfConstruction),
301
+ }
302
+ : undefined
303
+
304
+ // Build embeddings - errors propagate to CLI boundary
305
+ const embedResult = yield* buildEmbeddings(resolvedDir, {
306
+ force,
307
+ excludePatterns: cliExcludePatterns,
308
+ providerConfig,
309
+ hnswOptions,
310
+ onFileProgress: (progress) => {
311
+ if (!json) {
312
+ const progressMsg = ` [${progress.fileIndex}/${progress.totalFiles}] ${progress.filePath} (${progress.sectionCount} sections)...`
313
+ process.stdout.write(`\x1b[2K\r${progressMsg}`)
314
+ }
315
+ },
316
+ onBatchProgress: (progress) => {
317
+ if (!json) {
318
+ const progressMsg = ` Embedding [${progress.processedSections}/${progress.totalSections}] sections (batch ${progress.batchIndex}/${progress.totalBatches})...`
319
+ process.stdout.write(`\x1b[2K\r${progressMsg}`)
320
+ }
321
+ },
322
+ })
323
+
324
+ if (!json) {
325
+ // Clear the progress line completely
326
+ process.stdout.write(`\r${' '.repeat(120)}\r`)
327
+ yield* Console.log('')
328
+
329
+ if (embedResult.cacheHit) {
330
+ // Cache hit - embeddings already exist
331
+ yield* Console.log(
332
+ `Embeddings already exist (${embedResult.existingVectors} vectors)`,
333
+ )
334
+ yield* Console.log(' Use --force to rebuild')
335
+ yield* Console.log('')
336
+ yield* Console.log(
337
+ `Skipped embedding generation (saved ~$${(embedResult.estimatedSavings ?? 0).toFixed(4)})`,
338
+ )
339
+ } else {
340
+ // New embeddings were created
341
+ yield* Console.log(
342
+ `Completed in ${(embedResult.duration / 1000).toFixed(1)}s`,
343
+ )
344
+ yield* Console.log(` Files: ${embedResult.filesProcessed}`)
345
+ yield* Console.log(` Sections: ${embedResult.sectionsEmbedded}`)
346
+ yield* Console.log(
347
+ ` Tokens: ${embedResult.tokensUsed.toLocaleString()}`,
348
+ )
349
+ yield* Console.log(` Cost: $${embedResult.cost.toFixed(6)}`)
350
+ }
351
+ }
352
+ } else if (!noEmbed && !embedsExist && !json) {
353
+ // Prompt user to enable semantic search
354
+ yield* Console.log('')
355
+ yield* Console.log(
356
+ 'Enable semantic search? This allows natural language queries like:',
357
+ )
358
+ yield* Console.log(
359
+ ' "how does authentication work" instead of exact keyword matches',
360
+ )
361
+ yield* Console.log('')
362
+
363
+ // Get cost estimate for the prompt
364
+ // Note: We gracefully handle errors here since this is optional information
365
+ // for the user prompt. IndexNotFoundError is expected if index doesn't exist.
366
+ const estimate = yield* estimateEmbeddingCost(resolvedDir).pipe(
367
+ Effect.map((r): EmbeddingEstimate | null => r),
368
+ Effect.catchTags(createCostEstimateErrorHandler()),
369
+ )
370
+
371
+ if (estimate) {
372
+ yield* Console.log(
373
+ `Cost: ~$${estimate.totalCost.toFixed(4)} for this corpus (~${estimate.estimatedTimeSeconds}s)`,
374
+ )
375
+ }
376
+ yield* Console.log('Requires an embedding provider. Options:')
377
+ yield* Console.log(
378
+ ' - OpenAI (cloud): Set OPENAI_API_KEY environment variable',
379
+ )
380
+ yield* Console.log(
381
+ ' - Ollama (free, local): Run "ollama serve" - no API key needed',
382
+ )
383
+ yield* Console.log(
384
+ ' - LM Studio (free, local): Start the server - no API key needed',
385
+ )
386
+ yield* Console.log(
387
+ ' - OpenRouter (cloud): Set OPENROUTER_API_KEY environment variable',
388
+ )
389
+ yield* Console.log('')
390
+ yield* Console.log('See CONFIG.md for detailed setup instructions.')
391
+ yield* Console.log('')
392
+
393
+ const answer = yield* Effect.promise(() =>
394
+ promptUser('Create semantic index? [y/N]: '),
395
+ )
396
+
397
+ if (answer === 'y' || answer === 'yes') {
398
+ // Check for API key (only required for cloud providers)
399
+ // Note: When no provider is configured, we default to OpenAI which needs a key
400
+ // Local providers (Ollama, LM Studio) don't need API keys
401
+ if (!process.env.OPENAI_API_KEY) {
402
+ yield* Console.log('')
403
+ yield* Console.log('No embedding provider configured.')
404
+ yield* Console.log('')
405
+ yield* Console.log('Choose a provider:')
406
+ yield* Console.log('')
407
+ yield* Console.log(' Cloud (requires API key):')
408
+ yield* Console.log(' export OPENAI_API_KEY=sk-...')
409
+ yield* Console.log(' export OPENROUTER_API_KEY=sk-...')
410
+ yield* Console.log('')
411
+ yield* Console.log(' Local (free, no API key needed):')
412
+ yield* Console.log(
413
+ ' Ollama: ollama serve && ollama pull nomic-embed-text',
414
+ )
415
+ yield* Console.log(' LM Studio: Start the server GUI')
416
+ yield* Console.log('')
417
+ yield* Console.log(
418
+ 'Then run: mdcontext index --embed [--provider <name>]',
419
+ )
420
+ yield* Console.log('See CONFIG.md for detailed setup.')
421
+ } else {
422
+ yield* Console.log('')
423
+ yield* Console.log('Building embeddings...')
424
+
425
+ // Build HNSW options from CLI flags if specified
426
+ const hnswOptionsPrompt =
427
+ Option.isSome(hnswM) || Option.isSome(hnswEfConstruction)
428
+ ? {
429
+ m: Option.getOrUndefined(hnswM),
430
+ efConstruction: Option.getOrUndefined(hnswEfConstruction),
431
+ }
432
+ : undefined
433
+
434
+ // Build provider config if timeout specified
435
+ const promptTimeout = Option.getOrUndefined(timeout)
436
+ const providerConfigPrompt =
437
+ promptTimeout !== undefined
438
+ ? { provider: 'openai' as const, timeout: promptTimeout }
439
+ : undefined
440
+
441
+ // Note: We gracefully handle errors here since embedding failure
442
+ // shouldn't block the main index operation. Errors are logged for debugging.
443
+ const embedResult = yield* buildEmbeddings(resolvedDir, {
444
+ force: false,
445
+ hnswOptions: hnswOptionsPrompt,
446
+ providerConfig: providerConfigPrompt,
447
+ onFileProgress: (progress) => {
448
+ console.log(
449
+ ` [${progress.fileIndex}/${progress.totalFiles}] ${progress.filePath}`,
450
+ )
451
+ },
452
+ }).pipe(
453
+ Effect.map((r): BuildEmbeddingsResult | null => r),
454
+ Effect.catchTags(createEmbeddingErrorHandler()),
455
+ )
456
+
457
+ if (embedResult) {
458
+ yield* Console.log('')
459
+ yield* Console.log(
460
+ `Completed in ${(embedResult.duration / 1000).toFixed(1)}s`,
461
+ )
462
+ yield* Console.log(` Files: ${embedResult.filesProcessed}`)
463
+ yield* Console.log(
464
+ ` Sections: ${embedResult.sectionsEmbedded}`,
465
+ )
466
+ yield* Console.log(
467
+ ` Tokens: ${embedResult.tokensUsed.toLocaleString()}`,
468
+ )
469
+ yield* Console.log(` Cost: $${embedResult.cost.toFixed(6)}`)
470
+ }
471
+ }
472
+ }
473
+ }
474
+
475
+ if (json) {
476
+ yield* Console.log(formatJson(result, pretty))
477
+ }
478
+ }
479
+ }),
480
+ ).pipe(Command.withDescription('Index markdown files'))
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Commands barrel export
3
+ *
4
+ * Re-exports all CLI commands for use in main.ts.
5
+ */
6
+
7
+ export { backlinksCommand } from './backlinks.js'
8
+ export { configCommand } from './config-cmd.js'
9
+ export { contextCommand } from './context.js'
10
+ export { duplicatesCommand } from './duplicates.js'
11
+ export { embeddingsCommand } from './embeddings.js'
12
+ export { indexCommand } from './index-cmd.js'
13
+ export { linksCommand } from './links.js'
14
+ export { searchCommand } from './search.js'
15
+ export { statsCommand } from './stats.js'
16
+ export { treeCommand } from './tree.js'
@@ -0,0 +1,52 @@
1
+ /**
2
+ * LINKS Command
3
+ *
4
+ * Show what a file links to (outgoing links).
5
+ */
6
+
7
+ import * as path from 'node:path'
8
+ import { Args, Command, Options } from '@effect/cli'
9
+ import { Console, Effect } from 'effect'
10
+ import { getOutgoingLinks } from '../../index/indexer.js'
11
+ import { jsonOption, prettyOption } from '../options.js'
12
+ import { formatJson } from '../utils.js'
13
+
14
+ export const linksCommand = Command.make(
15
+ 'links',
16
+ {
17
+ file: Args.file({ name: 'file' }).pipe(
18
+ Args.withDescription('Markdown file to analyze'),
19
+ ),
20
+ root: Options.directory('root').pipe(
21
+ Options.withAlias('r'),
22
+ Options.withDescription('Root directory for resolving relative links'),
23
+ Options.withDefault('.'),
24
+ ),
25
+ json: jsonOption,
26
+ pretty: prettyOption,
27
+ },
28
+ ({ file, root, json, pretty }) =>
29
+ Effect.gen(function* () {
30
+ const resolvedRoot = path.resolve(root)
31
+ const resolvedFile = path.resolve(file)
32
+ const relativePath = path.relative(resolvedRoot, resolvedFile)
33
+
34
+ const links = yield* getOutgoingLinks(resolvedRoot, resolvedFile)
35
+
36
+ if (json) {
37
+ yield* Console.log(formatJson({ file: relativePath, links }, pretty))
38
+ } else {
39
+ yield* Console.log(`Outgoing links from ${relativePath}:`)
40
+ yield* Console.log('')
41
+ if (links.length === 0) {
42
+ yield* Console.log(' (none)')
43
+ } else {
44
+ for (const link of links) {
45
+ yield* Console.log(` -> ${link}`)
46
+ }
47
+ }
48
+ yield* Console.log('')
49
+ yield* Console.log(`Total: ${links.length} links`)
50
+ }
51
+ }),
52
+ ).pipe(Command.withDescription('What does this link to?'))