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,1430 @@
1
+ // src/utils/tokens.ts
2
+ import { Effect } from "effect";
3
+
4
+ var encoder = null;
5
+ var getEncoder = Effect.gen(function* () {
6
+ if (encoder === null) {
7
+ const { get_encoding } = yield* Effect.promise(() => import("tiktoken"));
8
+ encoder = get_encoding("cl100k_base");
9
+ }
10
+ return encoder;
11
+ });
12
+ var countTokens = (text) =>
13
+ Effect.gen(function* () {
14
+ const enc = yield* getEncoder;
15
+ const tokens = enc.encode(text);
16
+ return tokens.length;
17
+ });
18
+ var countTokensApprox = (text) => {
19
+ if (text.length === 0) return 0;
20
+ const cjkPattern =
21
+ /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af\u3400-\u4dbf]/g;
22
+ const cjkMatches = text.match(cjkPattern) || [];
23
+ const cjkCount = cjkMatches.length;
24
+ const emojiPattern =
25
+ /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{2300}-\u{23FF}\u{2190}-\u{21FF}\u{25A0}-\u{25FF}\u{2B00}-\u{2BFF}]/gu;
26
+ const emojiMatches = text.match(emojiPattern) || [];
27
+ const emojiCount = emojiMatches.length;
28
+ const variationSelectorPattern = /[\uFE0E\uFE0F]/g;
29
+ const variationMatches = text.match(variationSelectorPattern) || [];
30
+ const variationCount = variationMatches.length;
31
+ let workingText = text;
32
+ const codeBlockMatches = text.match(/```[\s\S]*?```/g) || [];
33
+ let codeBlockTokens = 0;
34
+ for (const block of codeBlockMatches) {
35
+ const hasLang = /^```\w+/.test(block);
36
+ const overhead = hasLang ? 6 : 4;
37
+ const content = block.replace(/^```\w*\n?/, "").replace(/\n?```$/, "");
38
+ const contentNewlines = (content.match(/\n/g) || []).length;
39
+ const contentTokens = content.length > 0 ? content.length / 2.5 : 0;
40
+ codeBlockTokens += Math.max(
41
+ overhead,
42
+ overhead + contentNewlines + contentTokens,
43
+ );
44
+ workingText = workingText.replace(block, "");
45
+ }
46
+ const inlineCodeMatches = workingText.match(/`[^`]+`/g) || [];
47
+ let inlineCodeTokens = 0;
48
+ for (const match of inlineCodeMatches) {
49
+ const content = match.slice(1, -1);
50
+ inlineCodeTokens += 2 + content.length / 2.5;
51
+ workingText = workingText.replace(match, "");
52
+ }
53
+ const pathMatches = workingText.match(/(?:\/[\w.-]+)+/g) || [];
54
+ let pathTokens = 0;
55
+ for (const match of pathMatches) {
56
+ const slashCount = (match.match(/\//g) || []).length;
57
+ const contentLength = match.length - slashCount;
58
+ pathTokens += slashCount + contentLength / 3.5;
59
+ workingText = workingText.replace(match, "");
60
+ }
61
+ const punctuationMatches =
62
+ workingText.match(/[!?,.:;'"()[\]{}@#$%^&*+=|\\<>~\-/]/g) || [];
63
+ const punctuationCount = punctuationMatches.length;
64
+ const proseNewlines = (workingText.match(/\n/g) || []).length;
65
+ const proseLength = Math.max(
66
+ 0,
67
+ workingText.length -
68
+ proseNewlines -
69
+ cjkCount -
70
+ emojiCount -
71
+ variationCount -
72
+ punctuationCount,
73
+ );
74
+ const proseTokens = proseLength / 3.5;
75
+ const proseNewlineTokens = proseNewlines * 1;
76
+ const punctuationBonus = punctuationCount * 0.8;
77
+ const cjkTokens = cjkCount * 1.2;
78
+ const emojiTokens = emojiCount * 2.5;
79
+ const variationTokens = variationCount * 1;
80
+ const estimate =
81
+ proseTokens +
82
+ proseNewlineTokens +
83
+ codeBlockTokens +
84
+ inlineCodeTokens +
85
+ pathTokens +
86
+ punctuationBonus +
87
+ cjkTokens +
88
+ emojiTokens +
89
+ variationTokens;
90
+ return Math.ceil(estimate * 1.1);
91
+ };
92
+ var countWords = (text) => {
93
+ const trimmed = text.trim();
94
+ if (trimmed.length === 0) return 0;
95
+ return trimmed.split(/\s+/).length;
96
+ };
97
+ var freeEncoder = () => {
98
+ if (encoder !== null) {
99
+ encoder.free();
100
+ encoder = null;
101
+ }
102
+ };
103
+
104
+ // src/parser/parser.ts
105
+ import * as crypto from "crypto";
106
+ // src/errors/index.ts
107
+ import { Data, Effect as Effect2 } from "effect";
108
+ import matter from "gray-matter";
109
+ import remarkGfm from "remark-gfm";
110
+ import remarkParse from "remark-parse";
111
+ import { unified } from "unified";
112
+ import { visit } from "unist-util-visit";
113
+
114
+ var ErrorCode = {
115
+ // File system errors (E1xx)
116
+ FILE_READ: "E100",
117
+ FILE_WRITE: "E101",
118
+ DIRECTORY_CREATE: "E102",
119
+ DIRECTORY_WALK: "E103",
120
+ // Parse errors (E2xx)
121
+ PARSE: "E200",
122
+ // API/authentication errors (E3xx)
123
+ API_KEY_MISSING: "E300",
124
+ API_KEY_INVALID: "E301",
125
+ EMBEDDING_RATE_LIMIT: "E310",
126
+ EMBEDDING_QUOTA: "E311",
127
+ EMBEDDING_NETWORK: "E312",
128
+ EMBEDDING_MODEL: "E313",
129
+ EMBEDDING_UNKNOWN: "E319",
130
+ // Index errors (E4xx)
131
+ INDEX_NOT_FOUND: "E400",
132
+ INDEX_CORRUPTED: "E401",
133
+ INDEX_BUILD: "E402",
134
+ // Search errors (E5xx)
135
+ DOCUMENT_NOT_FOUND: "E500",
136
+ EMBEDDINGS_NOT_FOUND: "E501",
137
+ // Vector store errors (E6xx)
138
+ VECTOR_STORE: "E600",
139
+ // Config errors (E7xx)
140
+ CONFIG: "E700",
141
+ // Watch errors (E8xx)
142
+ WATCH: "E800",
143
+ // CLI errors (E9xx)
144
+ CLI_VALIDATION: "E900",
145
+ };
146
+ var FileReadError = class extends Data.TaggedError("FileReadError") {
147
+ get code() {
148
+ return ErrorCode.FILE_READ;
149
+ }
150
+ };
151
+ var FileWriteError = class extends Data.TaggedError("FileWriteError") {
152
+ get code() {
153
+ return ErrorCode.FILE_WRITE;
154
+ }
155
+ };
156
+ var DirectoryCreateError = class extends Data.TaggedError(
157
+ "DirectoryCreateError",
158
+ ) {
159
+ get code() {
160
+ return ErrorCode.DIRECTORY_CREATE;
161
+ }
162
+ };
163
+ var DirectoryWalkError = class extends Data.TaggedError("DirectoryWalkError") {
164
+ get code() {
165
+ return ErrorCode.DIRECTORY_WALK;
166
+ }
167
+ };
168
+ var ParseError = class extends Data.TaggedError("ParseError") {
169
+ get code() {
170
+ return ErrorCode.PARSE;
171
+ }
172
+ };
173
+ var ApiKeyMissingError = class extends Data.TaggedError("ApiKeyMissingError") {
174
+ get code() {
175
+ return ErrorCode.API_KEY_MISSING;
176
+ }
177
+ get message() {
178
+ return `${this.envVar} not set`;
179
+ }
180
+ };
181
+ var ApiKeyInvalidError = class extends Data.TaggedError("ApiKeyInvalidError") {
182
+ get code() {
183
+ return ErrorCode.API_KEY_INVALID;
184
+ }
185
+ get message() {
186
+ return this.details ?? `Invalid API key for ${this.provider}`;
187
+ }
188
+ };
189
+ var EmbeddingError = class extends Data.TaggedError("EmbeddingError") {
190
+ get code() {
191
+ switch (this.reason) {
192
+ case "RateLimit":
193
+ return ErrorCode.EMBEDDING_RATE_LIMIT;
194
+ case "QuotaExceeded":
195
+ return ErrorCode.EMBEDDING_QUOTA;
196
+ case "Network":
197
+ return ErrorCode.EMBEDDING_NETWORK;
198
+ case "ModelError":
199
+ return ErrorCode.EMBEDDING_MODEL;
200
+ default:
201
+ return ErrorCode.EMBEDDING_UNKNOWN;
202
+ }
203
+ }
204
+ };
205
+ var IndexNotFoundError = class extends Data.TaggedError("IndexNotFoundError") {
206
+ get code() {
207
+ return ErrorCode.INDEX_NOT_FOUND;
208
+ }
209
+ get message() {
210
+ return `Index not found at ${this.path}`;
211
+ }
212
+ };
213
+ var IndexCorruptedError = class extends Data.TaggedError(
214
+ "IndexCorruptedError",
215
+ ) {
216
+ get code() {
217
+ return ErrorCode.INDEX_CORRUPTED;
218
+ }
219
+ get message() {
220
+ return `Index corrupted at ${this.path}: ${this.reason}`;
221
+ }
222
+ };
223
+ var IndexBuildError = class extends Data.TaggedError("IndexBuildError") {
224
+ get code() {
225
+ return ErrorCode.INDEX_BUILD;
226
+ }
227
+ };
228
+ var DocumentNotFoundError = class extends Data.TaggedError(
229
+ "DocumentNotFoundError",
230
+ ) {
231
+ get code() {
232
+ return ErrorCode.DOCUMENT_NOT_FOUND;
233
+ }
234
+ get message() {
235
+ return `Document not found in index: ${this.path}`;
236
+ }
237
+ };
238
+ var ConfigError = class extends Data.TaggedError("ConfigError") {
239
+ get code() {
240
+ return ErrorCode.CONFIG;
241
+ }
242
+ };
243
+ var VectorStoreError = class extends Data.TaggedError("VectorStoreError") {
244
+ get code() {
245
+ return ErrorCode.VECTOR_STORE;
246
+ }
247
+ };
248
+ var EmbeddingsNotFoundError = class extends Data.TaggedError(
249
+ "EmbeddingsNotFoundError",
250
+ ) {
251
+ get code() {
252
+ return ErrorCode.EMBEDDINGS_NOT_FOUND;
253
+ }
254
+ get message() {
255
+ return `Embeddings not found at ${this.path}. Run 'mdcontext index --embed' first.`;
256
+ }
257
+ };
258
+ var WatchError = class extends Data.TaggedError("WatchError") {
259
+ get code() {
260
+ return ErrorCode.WATCH;
261
+ }
262
+ };
263
+ var CliValidationError = class extends Data.TaggedError("CliValidationError") {
264
+ get code() {
265
+ return ErrorCode.CLI_VALIDATION;
266
+ }
267
+ };
268
+
269
+ // src/parser/parser.ts
270
+ var processor = unified().use(remarkParse).use(remarkGfm);
271
+ var generateId = (input) => {
272
+ return crypto.createHash("md5").update(input).digest("hex").slice(0, 12);
273
+ };
274
+ var slugify = (text) => {
275
+ return text
276
+ .toLowerCase()
277
+ .replace(/[^\w\s-]/g, "")
278
+ .replace(/\s+/g, "-")
279
+ .replace(/-+/g, "-")
280
+ .trim();
281
+ };
282
+ var isInternalLink = (href) => {
283
+ if (href.startsWith("http://") || href.startsWith("https://")) return false;
284
+ if (href.startsWith("mailto:")) return false;
285
+ if (href.startsWith("#")) return true;
286
+ if (href.endsWith(".md") || href.includes(".md#")) return true;
287
+ return !href.includes("://");
288
+ };
289
+ var extractPlainText = (node) => {
290
+ const texts = [];
291
+ visit(node, "text", (textNode) => {
292
+ texts.push(textNode.value);
293
+ });
294
+ return texts.join(" ");
295
+ };
296
+ var getNodeEndLine = (node) => {
297
+ return node?.position?.end?.line ?? 0;
298
+ };
299
+ var getNodeStartLine = (node) => {
300
+ return node?.position?.start?.line ?? 0;
301
+ };
302
+ var extractRawSections = (tree) => {
303
+ const sections = [];
304
+ const headings = [];
305
+ tree.children.forEach((node, index) => {
306
+ if (node.type === "heading") {
307
+ const heading = node;
308
+ headings.push({
309
+ heading: extractPlainText(heading),
310
+ level: heading.depth,
311
+ line: getNodeStartLine(node),
312
+ index,
313
+ });
314
+ }
315
+ });
316
+ headings.forEach((h, i) => {
317
+ const nextHeading = headings[i + 1];
318
+ const endIndex = nextHeading ? nextHeading.index : tree.children.length;
319
+ const contentNodes = tree.children.slice(h.index + 1, endIndex);
320
+ const lastContentNode = contentNodes[contentNodes.length - 1];
321
+ const endLine = lastContentNode ? getNodeEndLine(lastContentNode) : h.line;
322
+ sections.push({
323
+ heading: h.heading,
324
+ level: h.level,
325
+ startLine: h.line,
326
+ endLine,
327
+ contentStartLine: h.line + 1,
328
+ contentNodes,
329
+ });
330
+ });
331
+ return sections;
332
+ };
333
+ var buildSectionHierarchy = (rawSections, docId, lines) => {
334
+ const result = [];
335
+ const stack = [];
336
+ for (const raw of rawSections) {
337
+ const contentLines = lines.slice(raw.startLine - 1, raw.endLine);
338
+ const content = contentLines.join("\n");
339
+ const plainText = extractSectionPlainText(raw.contentNodes);
340
+ const hasCode = raw.contentNodes.some((n) => n.type === "code");
341
+ const hasList = raw.contentNodes.some((n) => n.type === "list");
342
+ const hasTable = raw.contentNodes.some((n) => n.type === "table");
343
+ const section = {
344
+ id: `${docId}-${slugify(raw.heading)}`,
345
+ heading: raw.heading,
346
+ level: raw.level,
347
+ content,
348
+ plainText,
349
+ startLine: raw.startLine,
350
+ endLine: raw.endLine,
351
+ children: [],
352
+ metadata: {
353
+ wordCount: countWords(plainText),
354
+ tokenCount: countTokensApprox(content),
355
+ hasCode,
356
+ hasList,
357
+ hasTable,
358
+ },
359
+ };
360
+ while (stack.length > 0 && stack[stack.length - 1].level >= raw.level) {
361
+ stack.pop();
362
+ }
363
+ if (stack.length === 0) {
364
+ result.push(section);
365
+ } else {
366
+ const parent = stack[stack.length - 1];
367
+ parent.section.children.push(section);
368
+ }
369
+ stack.push({ section, level: raw.level });
370
+ }
371
+ return result;
372
+ };
373
+ var extractSectionPlainText = (nodes) => {
374
+ const texts = [];
375
+ for (const node of nodes) {
376
+ if ("value" in node && typeof node.value === "string") {
377
+ texts.push(node.value);
378
+ } else if ("children" in node) {
379
+ texts.push(extractPlainText(node));
380
+ }
381
+ }
382
+ return texts.join(" ");
383
+ };
384
+ var countAllSections = (sections) => {
385
+ let count = 0;
386
+ for (const section of sections) {
387
+ count += 1;
388
+ count += countAllSections(section.children);
389
+ }
390
+ return count;
391
+ };
392
+ var extractLinks = (tree, docId) => {
393
+ const links = [];
394
+ let currentSectionId = docId;
395
+ visit(tree, (node) => {
396
+ if (node.type === "heading") {
397
+ currentSectionId = `${docId}-${slugify(extractPlainText(node))}`;
398
+ }
399
+ if (node.type === "link") {
400
+ const link = node;
401
+ const internal = isInternalLink(link.url);
402
+ links.push({
403
+ type: internal ? "internal" : "external",
404
+ href: link.url,
405
+ text: extractPlainText(link),
406
+ sectionId: currentSectionId,
407
+ line: getNodeStartLine(node),
408
+ });
409
+ }
410
+ if (node.type === "image") {
411
+ const img = node;
412
+ links.push({
413
+ type: "image",
414
+ href: img.url,
415
+ text: img.alt ?? "",
416
+ sectionId: currentSectionId,
417
+ line: getNodeStartLine(node),
418
+ });
419
+ }
420
+ });
421
+ return links;
422
+ };
423
+ var extractCodeBlocks = (tree, docId) => {
424
+ const codeBlocks = [];
425
+ let currentSectionId = docId;
426
+ visit(tree, (node) => {
427
+ if (node.type === "heading") {
428
+ currentSectionId = `${docId}-${slugify(extractPlainText(node))}`;
429
+ }
430
+ if (node.type === "code") {
431
+ const code = node;
432
+ codeBlocks.push({
433
+ language: code.lang ?? null,
434
+ content: code.value,
435
+ sectionId: currentSectionId,
436
+ startLine: getNodeStartLine(node),
437
+ endLine: getNodeEndLine(node),
438
+ });
439
+ }
440
+ });
441
+ return codeBlocks;
442
+ };
443
+ var parse = (content, options = {}) =>
444
+ Effect2.gen(function* () {
445
+ const path5 = options.path ?? "unknown";
446
+ const docId = generateId(path5);
447
+ const now = /* @__PURE__ */ new Date();
448
+ let frontmatter = {};
449
+ let markdownContent = content;
450
+ try {
451
+ const parsed = matter(content);
452
+ frontmatter = parsed.data;
453
+ markdownContent = parsed.content;
454
+ } catch (error) {
455
+ const msg = error instanceof Error ? error.message : String(error);
456
+ console.warn(
457
+ `Warning: Malformed frontmatter in ${path5}, skipping: ${msg.split("\n")[0]}`,
458
+ );
459
+ }
460
+ const tree = processor.parse(markdownContent);
461
+ const lines = markdownContent.split("\n");
462
+ const rawSections = extractRawSections(tree);
463
+ const sections = buildSectionHierarchy(rawSections, docId, lines);
464
+ const links = extractLinks(tree, docId);
465
+ const codeBlocks = extractCodeBlocks(tree, docId);
466
+ const firstH1 = sections.find((s) => s.level === 1);
467
+ const title =
468
+ firstH1?.heading ??
469
+ (typeof frontmatter.title === "string" ? frontmatter.title : null) ??
470
+ path5.split("/").pop()?.replace(/\.md$/, "") ??
471
+ "Untitled";
472
+ const totalContent = sections.map((s) => s.content).join("\n");
473
+ const metadata = {
474
+ wordCount: countWords(totalContent),
475
+ tokenCount: countTokensApprox(content),
476
+ headingCount: countAllSections(sections),
477
+ linkCount: links.length,
478
+ codeBlockCount: codeBlocks.length,
479
+ lastModified: options.lastModified ?? now,
480
+ indexedAt: now,
481
+ };
482
+ const document = {
483
+ id: docId,
484
+ path: path5,
485
+ title,
486
+ frontmatter,
487
+ sections,
488
+ links,
489
+ codeBlocks,
490
+ metadata,
491
+ };
492
+ return document;
493
+ });
494
+ var parseFile = (filePath) =>
495
+ Effect2.gen(function* () {
496
+ const fs5 = yield* Effect2.promise(() => import("fs/promises"));
497
+ const [content, stats] = yield* Effect2.tryPromise({
498
+ try: () =>
499
+ Promise.all([fs5.readFile(filePath, "utf-8"), fs5.stat(filePath)]),
500
+ catch: (error) =>
501
+ new FileReadError({
502
+ path: filePath,
503
+ message: error instanceof Error ? error.message : "Unknown error",
504
+ cause: error,
505
+ }),
506
+ });
507
+ return yield* parse(content, {
508
+ path: filePath,
509
+ lastModified: stats.mtime,
510
+ });
511
+ });
512
+
513
+ // src/index/types.ts
514
+ var INDEX_DIR = ".mdcontext";
515
+ var INDEX_VERSION = 1;
516
+ var getIndexPaths = (rootPath) => ({
517
+ root: `${rootPath}/${INDEX_DIR}`,
518
+ config: `${rootPath}/${INDEX_DIR}/config.json`,
519
+ documents: `${rootPath}/${INDEX_DIR}/indexes/documents.json`,
520
+ sections: `${rootPath}/${INDEX_DIR}/indexes/sections.json`,
521
+ links: `${rootPath}/${INDEX_DIR}/indexes/links.json`,
522
+ cache: `${rootPath}/${INDEX_DIR}/cache`,
523
+ parsed: `${rootPath}/${INDEX_DIR}/cache/parsed`,
524
+ });
525
+
526
+ // src/index/storage.ts
527
+ import * as crypto2 from "crypto";
528
+ import { Effect as Effect3 } from "effect";
529
+ import * as fs from "fs/promises";
530
+ import * as path from "path";
531
+
532
+ var ensureDir = (dirPath) =>
533
+ Effect3.tryPromise({
534
+ try: () => fs.mkdir(dirPath, { recursive: true }),
535
+ catch: (e) =>
536
+ new DirectoryCreateError({
537
+ path: dirPath,
538
+ message: e instanceof Error ? e.message : String(e),
539
+ cause: e,
540
+ }),
541
+ }).pipe(Effect3.map(() => void 0));
542
+ var readJsonFile = (filePath) =>
543
+ Effect3.gen(function* () {
544
+ const contentResult = yield* Effect3.tryPromise({
545
+ try: () => fs.readFile(filePath, "utf-8"),
546
+ catch: (e) => {
547
+ if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
548
+ return { notFound: true };
549
+ }
550
+ return new FileReadError({
551
+ path: filePath,
552
+ message: e instanceof Error ? e.message : String(e),
553
+ cause: e,
554
+ });
555
+ },
556
+ }).pipe(
557
+ Effect3.map((content) =>
558
+ typeof content === "string" ? { content } : content,
559
+ ),
560
+ // Note: catchAll here filters out "file not found" as expected case (returns null),
561
+ // while other errors are re-thrown to propagate as typed FileReadError
562
+ Effect3.catchAll((e) =>
563
+ e && "notFound" in e
564
+ ? Effect3.succeed({ notFound: true })
565
+ : Effect3.fail(e),
566
+ ),
567
+ );
568
+ if ("notFound" in contentResult) {
569
+ return null;
570
+ }
571
+ return yield* Effect3.try({
572
+ try: () => JSON.parse(contentResult.content),
573
+ catch: (e) =>
574
+ new IndexCorruptedError({
575
+ path: filePath,
576
+ reason: "InvalidJson",
577
+ details: e instanceof Error ? e.message : String(e),
578
+ }),
579
+ });
580
+ });
581
+ var writeJsonFile = (filePath, data) =>
582
+ Effect3.gen(function* () {
583
+ const dir = path.dirname(filePath);
584
+ yield* ensureDir(dir);
585
+ yield* Effect3.tryPromise({
586
+ try: () => fs.writeFile(filePath, JSON.stringify(data, null, 2)),
587
+ catch: (e) =>
588
+ new FileWriteError({
589
+ path: filePath,
590
+ message: e instanceof Error ? e.message : String(e),
591
+ cause: e,
592
+ }),
593
+ });
594
+ });
595
+ var computeHash = (content) => {
596
+ return crypto2
597
+ .createHash("sha256")
598
+ .update(content)
599
+ .digest("hex")
600
+ .slice(0, 16);
601
+ };
602
+ var createStorage = (rootPath) => ({
603
+ rootPath: path.resolve(rootPath),
604
+ paths: getIndexPaths(path.resolve(rootPath)),
605
+ });
606
+ var initializeIndex = (storage) =>
607
+ Effect3.gen(function* () {
608
+ yield* ensureDir(storage.paths.root);
609
+ yield* ensureDir(storage.paths.parsed);
610
+ yield* ensureDir(path.dirname(storage.paths.documents));
611
+ const existingConfig = yield* loadConfig(storage);
612
+ if (!existingConfig) {
613
+ const config = {
614
+ version: INDEX_VERSION,
615
+ rootPath: storage.rootPath,
616
+ include: ["**/*.md", "**/*.mdx"],
617
+ exclude: ["**/node_modules/**", "**/.*/**"],
618
+ createdAt: /* @__PURE__ */ new Date().toISOString(),
619
+ updatedAt: /* @__PURE__ */ new Date().toISOString(),
620
+ };
621
+ yield* saveConfig(storage, config);
622
+ }
623
+ });
624
+ var loadConfig = (storage) => readJsonFile(storage.paths.config);
625
+ var saveConfig = (storage, config) =>
626
+ writeJsonFile(storage.paths.config, {
627
+ ...config,
628
+ updatedAt: /* @__PURE__ */ new Date().toISOString(),
629
+ });
630
+ var loadDocumentIndex = (storage) => readJsonFile(storage.paths.documents);
631
+ var saveDocumentIndex = (storage, index) =>
632
+ writeJsonFile(storage.paths.documents, index);
633
+ var createEmptyDocumentIndex = (rootPath) => ({
634
+ version: INDEX_VERSION,
635
+ rootPath,
636
+ documents: {},
637
+ });
638
+ var loadSectionIndex = (storage) => readJsonFile(storage.paths.sections);
639
+ var saveSectionIndex = (storage, index) =>
640
+ writeJsonFile(storage.paths.sections, index);
641
+ var createEmptySectionIndex = () => ({
642
+ version: INDEX_VERSION,
643
+ sections: {},
644
+ byHeading: {},
645
+ byDocument: {},
646
+ });
647
+ var loadLinkIndex = (storage) => readJsonFile(storage.paths.links);
648
+ var saveLinkIndex = (storage, index) =>
649
+ writeJsonFile(storage.paths.links, index);
650
+ var createEmptyLinkIndex = () => ({
651
+ version: INDEX_VERSION,
652
+ forward: {},
653
+ backward: {},
654
+ broken: [],
655
+ });
656
+ var indexExists = (storage) =>
657
+ Effect3.tryPromise({
658
+ try: async () => {
659
+ try {
660
+ await fs.access(storage.paths.config);
661
+ return true;
662
+ } catch {
663
+ return false;
664
+ }
665
+ },
666
+ catch: (e) =>
667
+ new FileReadError({
668
+ path: storage.paths.config,
669
+ message: e instanceof Error ? e.message : String(e),
670
+ cause: e,
671
+ }),
672
+ });
673
+
674
+ import { Effect as Effect4, Effect as Effect6 } from "effect";
675
+ // src/index/indexer.ts
676
+ import * as fs4 from "fs/promises";
677
+
678
+ // src/index/ignore-patterns.ts
679
+ import * as fs2 from "fs/promises";
680
+ import ignore from "ignore";
681
+ import * as path4 from "path";
682
+ import * as path2 from "path";
683
+
684
+ var DEFAULT_IGNORE_PATTERNS = ["node_modules", ".git", "dist", "build"];
685
+ var tryReadIgnoreFile = (filePath) =>
686
+ Effect4.tryPromise({
687
+ try: () => fs2.readFile(filePath, "utf-8"),
688
+ catch: () => "",
689
+ }).pipe(Effect4.catchAll(() => Effect4.succeed("")));
690
+ var countPatterns = (content) => {
691
+ if (!content.trim()) return 0;
692
+ return content.split("\n").filter((line) => {
693
+ const trimmed = line.trim();
694
+ return trimmed.length > 0 && !trimmed.startsWith("#");
695
+ }).length;
696
+ };
697
+ var createIgnoreFilter = (options) =>
698
+ Effect4.gen(function* () {
699
+ const {
700
+ rootPath,
701
+ cliPatterns = [],
702
+ honorGitignore = true,
703
+ honorMdcontextignore = true,
704
+ } = options;
705
+ const ig = ignore();
706
+ const sources = [];
707
+ let patternCount = 0;
708
+ ig.add(DEFAULT_IGNORE_PATTERNS);
709
+ patternCount += DEFAULT_IGNORE_PATTERNS.length;
710
+ if (honorGitignore) {
711
+ const gitignorePath = path2.join(rootPath, ".gitignore");
712
+ const gitignoreContent = yield* tryReadIgnoreFile(gitignorePath);
713
+ if (gitignoreContent.trim()) {
714
+ ig.add(gitignoreContent);
715
+ const count = countPatterns(gitignoreContent);
716
+ patternCount += count;
717
+ sources.push(".gitignore");
718
+ }
719
+ }
720
+ if (honorMdcontextignore) {
721
+ const mdcontextignorePath = path2.join(rootPath, ".mdcontextignore");
722
+ const mdcontextignoreContent =
723
+ yield* tryReadIgnoreFile(mdcontextignorePath);
724
+ if (mdcontextignoreContent.trim()) {
725
+ ig.add(mdcontextignoreContent);
726
+ const count = countPatterns(mdcontextignoreContent);
727
+ patternCount += count;
728
+ sources.push(".mdcontextignore");
729
+ }
730
+ }
731
+ if (cliPatterns.length > 0) {
732
+ ig.add(cliPatterns);
733
+ patternCount += cliPatterns.length;
734
+ sources.push("CLI/config");
735
+ }
736
+ return {
737
+ filter: ig,
738
+ sources,
739
+ patternCount,
740
+ };
741
+ });
742
+ var shouldIgnore = (relativePath, filter) => {
743
+ const normalized = relativePath.replace(/^\//, "");
744
+ return filter.ignores(normalized);
745
+ };
746
+ var getChokidarIgnorePatterns = (options) =>
747
+ Effect4.gen(function* () {
748
+ const {
749
+ rootPath,
750
+ cliPatterns = [],
751
+ honorGitignore = true,
752
+ honorMdcontextignore = true,
753
+ } = options;
754
+ const patterns = [];
755
+ patterns.push(/(^|[/\\])\./.source);
756
+ for (const p of DEFAULT_IGNORE_PATTERNS) {
757
+ patterns.push(`**/${p}/**`);
758
+ }
759
+ if (honorGitignore) {
760
+ const gitignorePath = path2.join(rootPath, ".gitignore");
761
+ const content = yield* tryReadIgnoreFile(gitignorePath);
762
+ if (content.trim()) {
763
+ const parsed = parseIgnoreFile(content);
764
+ for (const p of parsed) {
765
+ patterns.push(convertToGlob(p));
766
+ }
767
+ }
768
+ }
769
+ if (honorMdcontextignore) {
770
+ const mdcontextignorePath = path2.join(rootPath, ".mdcontextignore");
771
+ const content = yield* tryReadIgnoreFile(mdcontextignorePath);
772
+ if (content.trim()) {
773
+ const parsed = parseIgnoreFile(content);
774
+ for (const p of parsed) {
775
+ patterns.push(convertToGlob(p));
776
+ }
777
+ }
778
+ }
779
+ for (const p of cliPatterns) {
780
+ patterns.push(convertToGlob(p));
781
+ }
782
+ return patterns;
783
+ });
784
+ var parseIgnoreFile = (content) => {
785
+ return content
786
+ .split("\n")
787
+ .map((line) => line.trim())
788
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
789
+ };
790
+ var convertToGlob = (pattern) => {
791
+ if (pattern.startsWith("!")) {
792
+ return pattern;
793
+ }
794
+ if (pattern.includes("*") || pattern.includes("/")) {
795
+ return pattern.startsWith("/") ? pattern.slice(1) : `**/${pattern}`;
796
+ }
797
+ return `**/${pattern}/**`;
798
+ };
799
+
800
+ import { Effect as Effect5 } from "effect";
801
+ // src/search/bm25-store.ts
802
+ import * as fs3 from "fs/promises";
803
+ import * as path3 from "path";
804
+ import bm25 from "wink-bm25-text-search";
805
+
806
+ var tokenize = (text) => {
807
+ return text
808
+ .toLowerCase()
809
+ .split(/\W+/)
810
+ .filter((token) => token.length > 2);
811
+ };
812
+ var createBM25Store = (rootPath) => {
813
+ const resolvedRoot = path3.resolve(rootPath);
814
+ const indexPath = path3.join(resolvedRoot, INDEX_DIR, "bm25.json");
815
+ const metadataPath = path3.join(resolvedRoot, INDEX_DIR, "bm25.meta.json");
816
+ const sectionMap = /* @__PURE__ */ new Map();
817
+ let documentCount = 0;
818
+ let consolidated = false;
819
+ let lastUpdated = /* @__PURE__ */ new Date().toISOString();
820
+ let engine = bm25();
821
+ engine.defineConfig({
822
+ fldWeights: {
823
+ heading: 2,
824
+ content: 1,
825
+ },
826
+ });
827
+ engine.definePrepTasks([tokenize]);
828
+ return {
829
+ add(docs) {
830
+ return Effect5.sync(() => {
831
+ for (const doc of docs) {
832
+ const idx = documentCount++;
833
+ sectionMap.set(idx, {
834
+ sectionId: doc.sectionId,
835
+ documentPath: doc.documentPath,
836
+ heading: doc.heading,
837
+ });
838
+ engine.addDoc(
839
+ {
840
+ heading: doc.heading,
841
+ content: doc.content,
842
+ },
843
+ idx,
844
+ );
845
+ }
846
+ consolidated = false;
847
+ lastUpdated = /* @__PURE__ */ new Date().toISOString();
848
+ });
849
+ },
850
+ consolidate() {
851
+ return Effect5.sync(() => {
852
+ if (!consolidated && documentCount > 0) {
853
+ engine.consolidate();
854
+ consolidated = true;
855
+ }
856
+ });
857
+ },
858
+ search(query, limit = 10) {
859
+ return Effect5.sync(() => {
860
+ if (!consolidated || documentCount === 0) {
861
+ return [];
862
+ }
863
+ const results = engine.search(query, limit);
864
+ return results.map(([idx, score], rank) => {
865
+ const info = sectionMap.get(idx);
866
+ return {
867
+ sectionId: info?.sectionId ?? "",
868
+ documentPath: info?.documentPath ?? "",
869
+ heading: info?.heading ?? "",
870
+ score,
871
+ rank: rank + 1,
872
+ };
873
+ });
874
+ });
875
+ },
876
+ save() {
877
+ return Effect5.gen(function* () {
878
+ const jsonModel = engine.exportJSON();
879
+ const sectionMapArray = Array.from(sectionMap.entries());
880
+ const data = {
881
+ engine: jsonModel,
882
+ sectionMap: sectionMapArray,
883
+ };
884
+ const metadata = {
885
+ version: 1,
886
+ count: documentCount,
887
+ lastUpdated,
888
+ };
889
+ yield* Effect5.tryPromise({
890
+ try: async () => {
891
+ await fs3.writeFile(indexPath, JSON.stringify(data), "utf-8");
892
+ await fs3.writeFile(
893
+ metadataPath,
894
+ JSON.stringify(metadata, null, 2),
895
+ "utf-8",
896
+ );
897
+ },
898
+ catch: (e) =>
899
+ new FileWriteError({
900
+ path: indexPath,
901
+ message: `Failed to save BM25 index: ${e instanceof Error ? e.message : String(e)}`,
902
+ }),
903
+ });
904
+ });
905
+ },
906
+ load() {
907
+ return Effect5.gen(function* () {
908
+ const exists = yield* Effect5.promise(async () => {
909
+ try {
910
+ await fs3.access(indexPath);
911
+ return true;
912
+ } catch {
913
+ return false;
914
+ }
915
+ });
916
+ if (!exists) {
917
+ return false;
918
+ }
919
+ const [dataStr, metaStr] = yield* Effect5.tryPromise({
920
+ try: async () => {
921
+ const data2 = await fs3.readFile(indexPath, "utf-8");
922
+ const meta = await fs3.readFile(metadataPath, "utf-8");
923
+ return [data2, meta];
924
+ },
925
+ catch: (e) =>
926
+ new FileReadError({
927
+ path: indexPath,
928
+ message: `Failed to load BM25 index: ${e instanceof Error ? e.message : String(e)}`,
929
+ }),
930
+ });
931
+ const data = JSON.parse(dataStr);
932
+ const metadata = JSON.parse(metaStr);
933
+ engine = bm25();
934
+ engine.importJSON(data.engine);
935
+ engine.definePrepTasks([tokenize]);
936
+ sectionMap.clear();
937
+ for (const [idx, info] of data.sectionMap) {
938
+ sectionMap.set(idx, info);
939
+ }
940
+ documentCount = metadata.count;
941
+ lastUpdated = metadata.lastUpdated;
942
+ consolidated = true;
943
+ return true;
944
+ });
945
+ },
946
+ getStats() {
947
+ return {
948
+ count: documentCount,
949
+ lastUpdated,
950
+ };
951
+ },
952
+ isConsolidated() {
953
+ return consolidated;
954
+ },
955
+ clear() {
956
+ engine = bm25();
957
+ engine.defineConfig({
958
+ fldWeights: {
959
+ heading: 2,
960
+ content: 1,
961
+ },
962
+ });
963
+ engine.definePrepTasks([tokenize]);
964
+ sectionMap.clear();
965
+ documentCount = 0;
966
+ consolidated = false;
967
+ lastUpdated = /* @__PURE__ */ new Date().toISOString();
968
+ },
969
+ };
970
+ };
971
+ var bm25Search = (rootPath, query, limit = 10) =>
972
+ Effect5.gen(function* () {
973
+ const store = createBM25Store(rootPath);
974
+ const loaded = yield* store.load();
975
+ if (!loaded) {
976
+ return [];
977
+ }
978
+ return yield* store.search(query, limit);
979
+ });
980
+ var bm25IndexExists = (rootPath) =>
981
+ Effect5.promise(async () => {
982
+ const resolvedRoot = path3.resolve(rootPath);
983
+ const indexPath = path3.join(resolvedRoot, INDEX_DIR, "bm25.json");
984
+ try {
985
+ await fs3.access(indexPath);
986
+ return true;
987
+ } catch {
988
+ return false;
989
+ }
990
+ });
991
+
992
+ // src/index/indexer.ts
993
+ var isMarkdownFile = (filename) =>
994
+ filename.endsWith(".md") || filename.endsWith(".mdx");
995
+ var walkDirectory = async (dir, rootPath, filter) => {
996
+ const files = [];
997
+ let hiddenCount = 0;
998
+ let excludedCount = 0;
999
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1000
+ for (const entry of entries) {
1001
+ const fullPath = path4.join(dir, entry.name);
1002
+ const relativePath = path4.relative(rootPath, fullPath);
1003
+ if (entry.name.startsWith(".")) {
1004
+ if (entry.isDirectory()) {
1005
+ hiddenCount++;
1006
+ }
1007
+ continue;
1008
+ }
1009
+ if (shouldIgnore(relativePath, filter)) {
1010
+ if (entry.isDirectory()) {
1011
+ excludedCount++;
1012
+ } else {
1013
+ excludedCount++;
1014
+ }
1015
+ continue;
1016
+ }
1017
+ if (entry.isDirectory()) {
1018
+ const subResult = await walkDirectory(fullPath, rootPath, filter);
1019
+ files.push(...subResult.files);
1020
+ hiddenCount += subResult.skipped.hidden;
1021
+ excludedCount += subResult.skipped.excluded;
1022
+ } else if (entry.isFile() && isMarkdownFile(entry.name)) {
1023
+ files.push(fullPath);
1024
+ }
1025
+ }
1026
+ return { files, skipped: { hidden: hiddenCount, excluded: excludedCount } };
1027
+ };
1028
+ var flattenSections = (sections, docId, docPath) => {
1029
+ const result = [];
1030
+ const traverse = (section) => {
1031
+ result.push({
1032
+ id: section.id,
1033
+ documentId: docId,
1034
+ documentPath: docPath,
1035
+ heading: section.heading,
1036
+ level: section.level,
1037
+ startLine: section.startLine,
1038
+ endLine: section.endLine,
1039
+ tokenCount: section.metadata.tokenCount,
1040
+ hasCode: section.metadata.hasCode,
1041
+ hasList: section.metadata.hasList,
1042
+ hasTable: section.metadata.hasTable,
1043
+ });
1044
+ for (const child of section.children) {
1045
+ traverse(child);
1046
+ }
1047
+ };
1048
+ for (const section of sections) {
1049
+ traverse(section);
1050
+ }
1051
+ return result;
1052
+ };
1053
+ var resolveInternalLink = (href, fromPath, rootPath) => {
1054
+ if (href.startsWith("#")) {
1055
+ return fromPath;
1056
+ }
1057
+ if (href.startsWith("http://") || href.startsWith("https://")) {
1058
+ return null;
1059
+ }
1060
+ const linkPath = href.split("#")[0] ?? "";
1061
+ if (!linkPath) return null;
1062
+ const fromDir = path4.dirname(fromPath);
1063
+ const resolved = path4.resolve(fromDir, linkPath);
1064
+ if (!resolved.startsWith(rootPath)) {
1065
+ return null;
1066
+ }
1067
+ return path4.relative(rootPath, resolved);
1068
+ };
1069
+ var buildIndex = (rootPath, options = {}) =>
1070
+ Effect6.gen(function* () {
1071
+ const startTime = Date.now();
1072
+ const storage = createStorage(rootPath);
1073
+ const errors = [];
1074
+ yield* initializeIndex(storage);
1075
+ const existingDocIndex = yield* loadDocumentIndex(storage);
1076
+ const docIndex =
1077
+ options.force || !existingDocIndex
1078
+ ? createEmptyDocumentIndex(storage.rootPath)
1079
+ : existingDocIndex;
1080
+ const existingSectionIndex = yield* loadSectionIndex(storage);
1081
+ const existingLinkIndex = yield* loadLinkIndex(storage);
1082
+ const sectionIndex = existingSectionIndex ?? createEmptySectionIndex();
1083
+ const linkIndex = existingLinkIndex ?? createEmptyLinkIndex();
1084
+ const ignoreResult = yield* createIgnoreFilter({
1085
+ rootPath: storage.rootPath,
1086
+ cliPatterns: options.exclude,
1087
+ honorGitignore: options.honorGitignore ?? true,
1088
+ honorMdcontextignore: options.honorMdcontextignore ?? true,
1089
+ });
1090
+ const walkResult = yield* Effect6.tryPromise({
1091
+ try: () =>
1092
+ walkDirectory(storage.rootPath, storage.rootPath, ignoreResult.filter),
1093
+ catch: (e) =>
1094
+ new DirectoryWalkError({
1095
+ path: storage.rootPath,
1096
+ message: `Failed to traverse directory: ${e instanceof Error ? e.message : String(e)}`,
1097
+ cause: e,
1098
+ }),
1099
+ });
1100
+ const { files, skipped: walkSkipped } = walkResult;
1101
+ let documentsIndexed = 0;
1102
+ let sectionsIndexed = 0;
1103
+ let linksIndexed = 0;
1104
+ let unchangedCount = 0;
1105
+ const mutableDocuments = {
1106
+ ...docIndex.documents,
1107
+ };
1108
+ const mutableSections = {
1109
+ ...sectionIndex.sections,
1110
+ };
1111
+ const mutableByHeading = Object.fromEntries(
1112
+ Object.entries(sectionIndex.byHeading).map(([k, v]) => [k, [...v]]),
1113
+ );
1114
+ const mutableByDocument = Object.fromEntries(
1115
+ Object.entries(sectionIndex.byDocument).map(([k, v]) => [k, [...v]]),
1116
+ );
1117
+ const mutableForward = Object.fromEntries(
1118
+ Object.entries(linkIndex.forward).map(([k, v]) => [k, [...v]]),
1119
+ );
1120
+ const mutableBackward = Object.fromEntries(
1121
+ Object.entries(linkIndex.backward).map(([k, v]) => [k, [...v]]),
1122
+ );
1123
+ const brokenLinks = [...linkIndex.broken];
1124
+ for (const filePath of files) {
1125
+ const relativePath = path4.relative(storage.rootPath, filePath);
1126
+ const processFile = Effect6.gen(function* () {
1127
+ const [content, stats] = yield* Effect6.promise(() =>
1128
+ Promise.all([fs4.readFile(filePath, "utf-8"), fs4.stat(filePath)]),
1129
+ );
1130
+ const hash = computeHash(content);
1131
+ const existingEntry = mutableDocuments[relativePath];
1132
+ if (
1133
+ !options.force &&
1134
+ existingEntry &&
1135
+ existingEntry.hash === hash &&
1136
+ existingEntry.mtime === stats.mtime.getTime()
1137
+ ) {
1138
+ unchangedCount++;
1139
+ return;
1140
+ }
1141
+ const doc = yield* parse(content, {
1142
+ path: relativePath,
1143
+ lastModified: stats.mtime,
1144
+ }).pipe(
1145
+ Effect6.mapError(
1146
+ (e) =>
1147
+ new ParseError({
1148
+ message: e.message,
1149
+ path: relativePath,
1150
+ ...(e.line !== void 0 && { line: e.line }),
1151
+ ...(e.column !== void 0 && { column: e.column }),
1152
+ }),
1153
+ ),
1154
+ );
1155
+ if (existingEntry) {
1156
+ const oldSectionIds = mutableByDocument[existingEntry.id] ?? [];
1157
+ for (const sectionId of oldSectionIds) {
1158
+ const oldSection = mutableSections[sectionId];
1159
+ if (oldSection) {
1160
+ const headingKey = oldSection.heading.toLowerCase();
1161
+ const headingList = mutableByHeading[headingKey];
1162
+ if (headingList) {
1163
+ const idx = headingList.indexOf(sectionId);
1164
+ if (idx !== -1) headingList.splice(idx, 1);
1165
+ }
1166
+ }
1167
+ delete mutableSections[sectionId];
1168
+ }
1169
+ delete mutableByDocument[existingEntry.id];
1170
+ delete mutableForward[relativePath];
1171
+ }
1172
+ mutableDocuments[relativePath] = {
1173
+ id: doc.id,
1174
+ path: relativePath,
1175
+ title: doc.title,
1176
+ mtime: stats.mtime.getTime(),
1177
+ hash,
1178
+ tokenCount: doc.metadata.tokenCount,
1179
+ sectionCount: doc.metadata.headingCount,
1180
+ };
1181
+ documentsIndexed++;
1182
+ const sections = flattenSections(doc.sections, doc.id, relativePath);
1183
+ mutableByDocument[doc.id] = [];
1184
+ for (const section of sections) {
1185
+ mutableSections[section.id] = section;
1186
+ mutableByDocument[doc.id]?.push(section.id);
1187
+ const headingKey = section.heading.toLowerCase();
1188
+ if (!mutableByHeading[headingKey]) {
1189
+ mutableByHeading[headingKey] = [];
1190
+ }
1191
+ mutableByHeading[headingKey]?.push(section.id);
1192
+ sectionsIndexed++;
1193
+ }
1194
+ const internalLinks = doc.links.filter((l) => l.type === "internal");
1195
+ const outgoingLinks = [];
1196
+ for (const link of internalLinks) {
1197
+ const target = resolveInternalLink(
1198
+ link.href,
1199
+ filePath,
1200
+ storage.rootPath,
1201
+ );
1202
+ if (target) {
1203
+ outgoingLinks.push(target);
1204
+ if (!mutableBackward[target]) {
1205
+ mutableBackward[target] = [];
1206
+ }
1207
+ if (!mutableBackward[target]?.includes(relativePath)) {
1208
+ mutableBackward[target]?.push(relativePath);
1209
+ }
1210
+ linksIndexed++;
1211
+ }
1212
+ }
1213
+ mutableForward[relativePath] = outgoingLinks;
1214
+ }).pipe(
1215
+ // Note: catchAll is intentional for batch file processing.
1216
+ // Individual file failures should be collected in errors array
1217
+ // rather than stopping the entire index build operation.
1218
+ Effect6.catchAll((error) => {
1219
+ const message =
1220
+ "message" in error && typeof error.message === "string"
1221
+ ? error.message
1222
+ : String(error);
1223
+ errors.push({
1224
+ path: relativePath,
1225
+ message,
1226
+ });
1227
+ return Effect6.void;
1228
+ }),
1229
+ );
1230
+ yield* processFile;
1231
+ }
1232
+ for (const [_from, targets] of Object.entries(mutableForward)) {
1233
+ for (const target of targets) {
1234
+ if (!mutableDocuments[target] && !brokenLinks.includes(target)) {
1235
+ brokenLinks.push(target);
1236
+ }
1237
+ }
1238
+ }
1239
+ yield* saveDocumentIndex(storage, {
1240
+ version: docIndex.version,
1241
+ rootPath: storage.rootPath,
1242
+ documents: mutableDocuments,
1243
+ });
1244
+ yield* saveSectionIndex(storage, {
1245
+ version: sectionIndex.version,
1246
+ sections: mutableSections,
1247
+ byHeading: mutableByHeading,
1248
+ byDocument: mutableByDocument,
1249
+ });
1250
+ yield* saveLinkIndex(storage, {
1251
+ version: linkIndex.version,
1252
+ forward: mutableForward,
1253
+ backward: mutableBackward,
1254
+ broken: brokenLinks,
1255
+ });
1256
+ const duration = Date.now() - startTime;
1257
+ const totalLinks = Object.values(mutableForward).reduce(
1258
+ (sum, links) => sum + links.length,
1259
+ 0,
1260
+ );
1261
+ const skipped = {
1262
+ unchanged: unchangedCount,
1263
+ excluded: walkSkipped.excluded,
1264
+ hidden: walkSkipped.hidden,
1265
+ total: unchangedCount + walkSkipped.excluded + walkSkipped.hidden,
1266
+ };
1267
+ return {
1268
+ documentsIndexed,
1269
+ sectionsIndexed,
1270
+ linksIndexed,
1271
+ totalDocuments: Object.keys(mutableDocuments).length,
1272
+ totalSections: Object.keys(mutableSections).length,
1273
+ totalLinks,
1274
+ duration,
1275
+ errors,
1276
+ skipped,
1277
+ };
1278
+ });
1279
+ var getOutgoingLinks = (rootPath, filePath) =>
1280
+ Effect6.gen(function* () {
1281
+ const storage = createStorage(rootPath);
1282
+ const linkIndex = yield* loadLinkIndex(storage);
1283
+ if (!linkIndex) {
1284
+ return [];
1285
+ }
1286
+ const relativePath = path4.relative(
1287
+ storage.rootPath,
1288
+ path4.resolve(filePath),
1289
+ );
1290
+ return linkIndex.forward[relativePath] ?? [];
1291
+ });
1292
+ var getIncomingLinks = (rootPath, filePath) =>
1293
+ Effect6.gen(function* () {
1294
+ const storage = createStorage(rootPath);
1295
+ const linkIndex = yield* loadLinkIndex(storage);
1296
+ if (!linkIndex) {
1297
+ return [];
1298
+ }
1299
+ const relativePath = path4.relative(
1300
+ storage.rootPath,
1301
+ path4.resolve(filePath),
1302
+ );
1303
+ return linkIndex.backward[relativePath] ?? [];
1304
+ });
1305
+ var getBrokenLinks = (rootPath) =>
1306
+ Effect6.gen(function* () {
1307
+ const storage = createStorage(rootPath);
1308
+ const linkIndex = yield* loadLinkIndex(storage);
1309
+ if (!linkIndex) {
1310
+ return [];
1311
+ }
1312
+ return linkIndex.broken;
1313
+ });
1314
+ var buildBM25Index = (rootPath, options = {}) =>
1315
+ Effect6.gen(function* () {
1316
+ const startTime = Date.now();
1317
+ const storage = createStorage(rootPath);
1318
+ const docIndex = yield* loadDocumentIndex(storage);
1319
+ const sectionIndex = yield* loadSectionIndex(storage);
1320
+ if (!docIndex || !sectionIndex) {
1321
+ return { sectionsIndexed: 0, duration: 0 };
1322
+ }
1323
+ const bm25Store = createBM25Store(storage.rootPath);
1324
+ if (!options.force) {
1325
+ const loaded = yield* bm25Store.load();
1326
+ if (loaded) {
1327
+ const stats = bm25Store.getStats();
1328
+ if (stats.count > 0) {
1329
+ return { sectionsIndexed: 0, duration: Date.now() - startTime };
1330
+ }
1331
+ }
1332
+ }
1333
+ bm25Store.clear();
1334
+ const sectionsByDoc = /* @__PURE__ */ new Map();
1335
+ for (const section of Object.values(sectionIndex.sections)) {
1336
+ if (section.tokenCount < 10) continue;
1337
+ const existing = sectionsByDoc.get(section.documentPath);
1338
+ if (existing) {
1339
+ existing.push(section);
1340
+ } else {
1341
+ sectionsByDoc.set(section.documentPath, [section]);
1342
+ }
1343
+ }
1344
+ const totalDocs = sectionsByDoc.size;
1345
+ let processedDocs = 0;
1346
+ let sectionsIndexed = 0;
1347
+ for (const [docPath, sections] of sectionsByDoc) {
1348
+ const filePath = path4.join(storage.rootPath, docPath);
1349
+ const fileContentResult = yield* Effect6.promise(() =>
1350
+ fs4.readFile(filePath, "utf-8"),
1351
+ ).pipe(
1352
+ Effect6.map((content) => ({ ok: true, content })),
1353
+ Effect6.catchAll(() => Effect6.succeed({ ok: false, content: "" })),
1354
+ );
1355
+ if (!fileContentResult.ok) continue;
1356
+ const lines = fileContentResult.content.split("\n");
1357
+ const docs = [];
1358
+ for (const section of sections) {
1359
+ const content = lines
1360
+ .slice(section.startLine - 1, section.endLine)
1361
+ .join("\n");
1362
+ docs.push({
1363
+ id: section.id,
1364
+ sectionId: section.id,
1365
+ documentPath: section.documentPath,
1366
+ heading: section.heading,
1367
+ content,
1368
+ });
1369
+ sectionsIndexed++;
1370
+ }
1371
+ yield* bm25Store.add(docs);
1372
+ processedDocs++;
1373
+ if (options.onProgress) {
1374
+ options.onProgress({ current: processedDocs, total: totalDocs });
1375
+ }
1376
+ }
1377
+ yield* bm25Store.consolidate();
1378
+ yield* bm25Store.save();
1379
+ return {
1380
+ sectionsIndexed,
1381
+ duration: Date.now() - startTime,
1382
+ };
1383
+ });
1384
+
1385
+ export {
1386
+ FileReadError,
1387
+ DirectoryWalkError,
1388
+ ParseError,
1389
+ ApiKeyMissingError,
1390
+ ApiKeyInvalidError,
1391
+ EmbeddingError,
1392
+ IndexNotFoundError,
1393
+ ConfigError,
1394
+ VectorStoreError,
1395
+ EmbeddingsNotFoundError,
1396
+ WatchError,
1397
+ CliValidationError,
1398
+ countTokens,
1399
+ countTokensApprox,
1400
+ countWords,
1401
+ freeEncoder,
1402
+ parse,
1403
+ parseFile,
1404
+ getChokidarIgnorePatterns,
1405
+ INDEX_DIR,
1406
+ INDEX_VERSION,
1407
+ getIndexPaths,
1408
+ computeHash,
1409
+ createStorage,
1410
+ initializeIndex,
1411
+ loadConfig,
1412
+ saveConfig,
1413
+ loadDocumentIndex,
1414
+ saveDocumentIndex,
1415
+ createEmptyDocumentIndex,
1416
+ loadSectionIndex,
1417
+ saveSectionIndex,
1418
+ createEmptySectionIndex,
1419
+ loadLinkIndex,
1420
+ saveLinkIndex,
1421
+ createEmptyLinkIndex,
1422
+ indexExists,
1423
+ bm25Search,
1424
+ bm25IndexExists,
1425
+ buildIndex,
1426
+ getOutgoingLinks,
1427
+ getIncomingLinks,
1428
+ getBrokenLinks,
1429
+ buildBM25Index,
1430
+ };