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,620 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { countTokensApprox } from '../utils/tokens.js'
3
+ import { formatSummary } from './formatters.js'
4
+ import type { DocumentSummary } from './summarizer.js'
5
+
6
+ describe('token budget enforcement', () => {
7
+ describe('orphan rescue - children included when parent too large', () => {
8
+ it('rescues children when parent section is too large for budget', () => {
9
+ // Large parent that won't fit, but small children that would
10
+ const mockSummary: DocumentSummary = {
11
+ path: '/test/file.md',
12
+ title: 'Test',
13
+ originalTokens: 1000,
14
+ summaryTokens: 500,
15
+ compressionRatio: 0.5,
16
+ sections: [
17
+ {
18
+ heading: 'Large Parent',
19
+ level: 2,
20
+ originalTokens: 500,
21
+ summaryTokens: 200,
22
+ summary: 'Very large parent content. '.repeat(50), // ~1000 chars = ~250 tokens
23
+ children: [
24
+ {
25
+ heading: 'Small Child A',
26
+ level: 3,
27
+ originalTokens: 20,
28
+ summaryTokens: 10,
29
+ summary: 'Tiny child A.',
30
+ children: [],
31
+ hasCode: false,
32
+ hasList: false,
33
+ hasTable: false,
34
+ },
35
+ {
36
+ heading: 'Small Child B',
37
+ level: 3,
38
+ originalTokens: 20,
39
+ summaryTokens: 10,
40
+ summary: 'Tiny child B.',
41
+ children: [],
42
+ hasCode: false,
43
+ hasList: false,
44
+ hasTable: false,
45
+ },
46
+ ],
47
+ hasCode: false,
48
+ hasList: false,
49
+ hasTable: false,
50
+ },
51
+ ],
52
+ keyTopics: [],
53
+ }
54
+
55
+ // Budget that can't fit parent but can fit children
56
+ // Increased to account for enhanced truncation warning overhead
57
+ const output = formatSummary(mockSummary, { maxTokens: 200 })
58
+
59
+ // Children should be rescued and included even though parent was skipped
60
+ expect(output).toContain('Small Child A')
61
+ expect(output).toContain('Small Child B')
62
+ // Parent should NOT be included (too large)
63
+ expect(output).not.toContain('Large Parent')
64
+ expect(output).not.toContain('Very large parent content')
65
+ })
66
+
67
+ it('preserves hierarchy when parent fits but some children do not', () => {
68
+ const mockSummary: DocumentSummary = {
69
+ path: '/test/file.md',
70
+ title: 'Test',
71
+ originalTokens: 500,
72
+ summaryTokens: 200,
73
+ compressionRatio: 0.6,
74
+ sections: [
75
+ {
76
+ heading: 'Parent',
77
+ level: 2,
78
+ originalTokens: 100,
79
+ summaryTokens: 20,
80
+ summary: 'Short parent.',
81
+ children: [
82
+ {
83
+ heading: 'Child Fits',
84
+ level: 3,
85
+ originalTokens: 20,
86
+ summaryTokens: 10,
87
+ summary: 'Small.',
88
+ children: [],
89
+ hasCode: false,
90
+ hasList: false,
91
+ hasTable: false,
92
+ },
93
+ {
94
+ heading: 'Child Too Large',
95
+ level: 3,
96
+ originalTokens: 300,
97
+ summaryTokens: 150,
98
+ summary: 'Very long child. '.repeat(30),
99
+ children: [],
100
+ hasCode: false,
101
+ hasList: false,
102
+ hasTable: false,
103
+ },
104
+ ],
105
+ hasCode: false,
106
+ hasList: false,
107
+ hasTable: false,
108
+ },
109
+ ],
110
+ keyTopics: [],
111
+ }
112
+
113
+ // Budget increased to account for enhanced truncation warning overhead
114
+ const output = formatSummary(mockSummary, { maxTokens: 200 })
115
+
116
+ // Parent and first child should be included
117
+ expect(output).toContain('Parent')
118
+ expect(output).toContain('Child Fits')
119
+ // Large child should be truncated
120
+ expect(output).not.toContain('Child Too Large')
121
+ })
122
+ })
123
+
124
+ it('includes sections greedily up to budget', () => {
125
+ const mockSummary = {
126
+ path: '/test/file.md',
127
+ title: 'Test Document',
128
+ originalTokens: 1000,
129
+ summaryTokens: 500,
130
+ compressionRatio: 0.5,
131
+ sections: [
132
+ {
133
+ heading: 'Introduction',
134
+ level: 2,
135
+ originalTokens: 200,
136
+ summaryTokens: 100,
137
+ summary: 'Short intro.',
138
+ children: [
139
+ {
140
+ heading: 'Subsection A',
141
+ level: 3,
142
+ originalTokens: 50,
143
+ summaryTokens: 20,
144
+ summary: 'Small subsection A.',
145
+ children: [],
146
+ hasCode: false,
147
+ hasList: false,
148
+ hasTable: false,
149
+ },
150
+ {
151
+ heading: 'Subsection B',
152
+ level: 3,
153
+ originalTokens: 50,
154
+ summaryTokens: 20,
155
+ summary: 'Small subsection B.',
156
+ children: [],
157
+ hasCode: false,
158
+ hasList: false,
159
+ hasTable: false,
160
+ },
161
+ ],
162
+ hasCode: false,
163
+ hasList: false,
164
+ hasTable: false,
165
+ },
166
+ ],
167
+ keyTopics: ['topic1', 'topic2'],
168
+ }
169
+
170
+ // Budget should fit header + all sections with small content
171
+ // Increased to account for enhanced truncation warning overhead
172
+ const output = formatSummary(mockSummary, { maxTokens: 250 })
173
+
174
+ // All sections should be included since they're small
175
+ expect(output).toContain('Introduction')
176
+ expect(output).toContain('Subsection A')
177
+ expect(output).toContain('Subsection B')
178
+ })
179
+
180
+ it('stays within budget for long paths and titles', () => {
181
+ const mockSummary = {
182
+ path: '/very/long/path/to/deeply/nested/directory/structure/that/keeps/going/file.md',
183
+ title:
184
+ 'A Very Long Document Title That Uses Significantly More Tokens Than Expected',
185
+ originalTokens: 1000,
186
+ summaryTokens: 100,
187
+ compressionRatio: 0.9,
188
+ sections: [
189
+ {
190
+ heading: 'Section',
191
+ level: 2,
192
+ originalTokens: 100,
193
+ summaryTokens: 50,
194
+ summary: 'Some content.',
195
+ children: [],
196
+ hasCode: false,
197
+ hasList: false,
198
+ hasTable: false,
199
+ },
200
+ ],
201
+ keyTopics: [
202
+ 'topic1',
203
+ 'topic2',
204
+ 'topic3',
205
+ 'topic4',
206
+ 'topic5',
207
+ 'long-topic-name',
208
+ ],
209
+ }
210
+
211
+ const output = formatSummary(mockSummary, { maxTokens: 100 })
212
+ const actualTokens = countTokensApprox(output)
213
+
214
+ // Output should stay within budget
215
+ expect(actualTokens).toBeLessThanOrEqual(100)
216
+ })
217
+
218
+ it('drops topics when header overhead exceeds budget', () => {
219
+ const mockSummary = {
220
+ path: '/very/long/path/file.md',
221
+ title: 'Long Title Here',
222
+ originalTokens: 1000,
223
+ summaryTokens: 100,
224
+ compressionRatio: 0.9,
225
+ sections: [],
226
+ keyTopics: ['topic1', 'topic2', 'topic3', 'topic4'],
227
+ }
228
+
229
+ // Very tight budget - should drop topics to make room
230
+ const output = formatSummary(mockSummary, { maxTokens: 50 })
231
+
232
+ // With tight budget, topics may be dropped to fit
233
+ const actualTokens = countTokensApprox(output)
234
+ expect(actualTokens).toBeLessThanOrEqual(50)
235
+ })
236
+
237
+ describe('strict budget enforcement', () => {
238
+ it('never exceeds specified token budget when budget is achievable', () => {
239
+ // Test with realistic budgets - the minimum header for this document is ~60 tokens
240
+ // (long path + long title + token line + truncation warning)
241
+ // So we test budgets that should be achievable
242
+ const budgets = [75, 100, 150, 200, 300, 500]
243
+
244
+ for (const budget of budgets) {
245
+ const mockSummary: DocumentSummary = {
246
+ path: '/project/src/components/deeply/nested/component.tsx',
247
+ title: 'A Component With A Moderately Long Title',
248
+ originalTokens: 2000,
249
+ summaryTokens: 800,
250
+ compressionRatio: 0.6,
251
+ sections: [
252
+ {
253
+ heading: 'Overview',
254
+ level: 2,
255
+ originalTokens: 200,
256
+ summaryTokens: 80,
257
+ summary:
258
+ 'This component handles user authentication and session management with proper error handling.',
259
+ children: [
260
+ {
261
+ heading: 'Props',
262
+ level: 3,
263
+ originalTokens: 100,
264
+ summaryTokens: 40,
265
+ summary:
266
+ 'Accepts user object, onLogin callback, and configuration options.',
267
+ children: [],
268
+ hasCode: true,
269
+ hasList: false,
270
+ hasTable: false,
271
+ },
272
+ ],
273
+ hasCode: false,
274
+ hasList: false,
275
+ hasTable: false,
276
+ },
277
+ {
278
+ heading: 'Implementation',
279
+ level: 2,
280
+ originalTokens: 300,
281
+ summaryTokens: 120,
282
+ summary:
283
+ 'Uses React hooks for state management. Implements OAuth2 flow with PKCE.',
284
+ children: [],
285
+ hasCode: true,
286
+ hasList: true,
287
+ hasTable: false,
288
+ },
289
+ ],
290
+ keyTopics: ['react', 'authentication', 'oauth', 'hooks', 'security'],
291
+ }
292
+
293
+ const output = formatSummary(mockSummary, { maxTokens: budget })
294
+ const actualTokens = countTokensApprox(output)
295
+
296
+ expect(actualTokens).toBeLessThanOrEqual(budget)
297
+ }
298
+ })
299
+
300
+ it('handles tight budgets with short paths gracefully', () => {
301
+ // With a short path and title, we can achieve tighter budgets
302
+ // Minimum header for this doc is roughly:
303
+ // "# X\nPath: x\nTokens: XX (Y% reduction from Z)\n\n" ~= 20-25 tokens
304
+ const mockSummary: DocumentSummary = {
305
+ path: 'x',
306
+ title: 'X',
307
+ originalTokens: 100,
308
+ summaryTokens: 50,
309
+ compressionRatio: 0.5,
310
+ sections: [
311
+ {
312
+ heading: 'S',
313
+ level: 2,
314
+ originalTokens: 50,
315
+ summaryTokens: 25,
316
+ summary: 'Text.',
317
+ children: [],
318
+ hasCode: false,
319
+ hasList: false,
320
+ hasTable: false,
321
+ },
322
+ ],
323
+ keyTopics: [],
324
+ }
325
+
326
+ // Test with a budget that should definitely fit the minimal header
327
+ const output = formatSummary(mockSummary, { maxTokens: 40 })
328
+ const actualTokens = countTokensApprox(output)
329
+
330
+ expect(actualTokens).toBeLessThanOrEqual(40)
331
+ })
332
+
333
+ it('stays within budget even with code-heavy content', () => {
334
+ const mockSummary: DocumentSummary = {
335
+ path: '/src/utils/parser.ts',
336
+ title: 'Parser Utilities',
337
+ originalTokens: 1500,
338
+ summaryTokens: 600,
339
+ compressionRatio: 0.6,
340
+ sections: [
341
+ {
342
+ heading: 'Functions',
343
+ level: 2,
344
+ originalTokens: 500,
345
+ summaryTokens: 200,
346
+ summary:
347
+ '```typescript\nfunction parse(input: string): AST {\n const tokens = tokenize(input);\n return buildTree(tokens);\n}\n```',
348
+ children: [],
349
+ hasCode: true,
350
+ hasList: false,
351
+ hasTable: false,
352
+ },
353
+ ],
354
+ keyTopics: ['parser', 'ast', 'typescript'],
355
+ }
356
+
357
+ const output = formatSummary(mockSummary, { maxTokens: 100 })
358
+ const actualTokens = countTokensApprox(output)
359
+
360
+ expect(actualTokens).toBeLessThanOrEqual(100)
361
+ })
362
+ })
363
+
364
+ describe('deeply nested content', () => {
365
+ it('handles 3+ levels of nesting with orphan rescue', () => {
366
+ const mockSummary: DocumentSummary = {
367
+ path: '/test/file.md',
368
+ title: 'Deep',
369
+ originalTokens: 1000,
370
+ summaryTokens: 500,
371
+ compressionRatio: 0.5,
372
+ sections: [
373
+ {
374
+ heading: 'Large L1',
375
+ level: 2,
376
+ originalTokens: 400,
377
+ summaryTokens: 200,
378
+ summary: 'Large level 1 content. '.repeat(40),
379
+ children: [
380
+ {
381
+ heading: 'Large L2',
382
+ level: 3,
383
+ originalTokens: 200,
384
+ summaryTokens: 100,
385
+ summary: 'Large level 2 content. '.repeat(20),
386
+ children: [
387
+ {
388
+ heading: 'Small L3',
389
+ level: 4,
390
+ originalTokens: 20,
391
+ summaryTokens: 10,
392
+ summary: 'Small.',
393
+ children: [],
394
+ hasCode: false,
395
+ hasList: false,
396
+ hasTable: false,
397
+ },
398
+ ],
399
+ hasCode: false,
400
+ hasList: false,
401
+ hasTable: false,
402
+ },
403
+ ],
404
+ hasCode: false,
405
+ hasList: false,
406
+ hasTable: false,
407
+ },
408
+ ],
409
+ keyTopics: [],
410
+ }
411
+
412
+ // Increased budget to account for enhanced truncation warning overhead
413
+ const output = formatSummary(mockSummary, { maxTokens: 180 })
414
+
415
+ // Deepest child should still be rescued
416
+ expect(output).toContain('Small L3')
417
+ // Large ancestors should be skipped
418
+ expect(output).not.toContain('Large L1')
419
+ expect(output).not.toContain('Large L2')
420
+ // And stay within budget
421
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(180)
422
+ })
423
+
424
+ it('includes multiple siblings when all fit', () => {
425
+ const mockSummary: DocumentSummary = {
426
+ path: '/test/file.md',
427
+ title: 'Siblings',
428
+ originalTokens: 500,
429
+ summaryTokens: 200,
430
+ compressionRatio: 0.6,
431
+ sections: [
432
+ {
433
+ heading: 'A',
434
+ level: 2,
435
+ originalTokens: 30,
436
+ summaryTokens: 10,
437
+ summary: 'Content A.',
438
+ children: [],
439
+ hasCode: false,
440
+ hasList: false,
441
+ hasTable: false,
442
+ },
443
+ {
444
+ heading: 'B',
445
+ level: 2,
446
+ originalTokens: 30,
447
+ summaryTokens: 10,
448
+ summary: 'Content B.',
449
+ children: [],
450
+ hasCode: false,
451
+ hasList: false,
452
+ hasTable: false,
453
+ },
454
+ {
455
+ heading: 'C',
456
+ level: 2,
457
+ originalTokens: 30,
458
+ summaryTokens: 10,
459
+ summary: 'Content C.',
460
+ children: [],
461
+ hasCode: false,
462
+ hasList: false,
463
+ hasTable: false,
464
+ },
465
+ ],
466
+ keyTopics: [],
467
+ }
468
+
469
+ // Increased budget to account for enhanced truncation warning overhead
470
+ const output = formatSummary(mockSummary, { maxTokens: 200 })
471
+
472
+ // All small sections should fit
473
+ expect(output).toContain('A')
474
+ expect(output).toContain('B')
475
+ expect(output).toContain('C')
476
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(200)
477
+ })
478
+ })
479
+
480
+ describe('edge cases', () => {
481
+ it('handles empty sections array', () => {
482
+ const mockSummary: DocumentSummary = {
483
+ path: '/test/file.md',
484
+ title: 'Empty',
485
+ originalTokens: 100,
486
+ summaryTokens: 0,
487
+ compressionRatio: 1.0,
488
+ sections: [],
489
+ keyTopics: [],
490
+ }
491
+
492
+ const output = formatSummary(mockSummary, { maxTokens: 50 })
493
+ expect(output).toContain('# Empty')
494
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(50)
495
+ })
496
+
497
+ it('handles section with empty summary', () => {
498
+ const mockSummary: DocumentSummary = {
499
+ path: '/test/file.md',
500
+ title: 'Test',
501
+ originalTokens: 100,
502
+ summaryTokens: 10,
503
+ compressionRatio: 0.9,
504
+ sections: [
505
+ {
506
+ heading: 'Empty',
507
+ level: 2,
508
+ originalTokens: 10,
509
+ summaryTokens: 5,
510
+ summary: '',
511
+ children: [],
512
+ hasCode: false,
513
+ hasList: false,
514
+ hasTable: false,
515
+ },
516
+ ],
517
+ keyTopics: [],
518
+ }
519
+
520
+ // Use larger budget so section fits (increased for enhanced truncation warning overhead)
521
+ const output = formatSummary(mockSummary, { maxTokens: 150 })
522
+ expect(output).toContain('## Empty')
523
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(150)
524
+ })
525
+
526
+ it('handles unicode characters in path and title', () => {
527
+ const mockSummary: DocumentSummary = {
528
+ path: '/docs/日本語/ファイル.md',
529
+ title: '日本語のドキュメント',
530
+ originalTokens: 200,
531
+ summaryTokens: 50,
532
+ compressionRatio: 0.75,
533
+ sections: [
534
+ {
535
+ heading: 'セクション',
536
+ level: 2,
537
+ originalTokens: 50,
538
+ summaryTokens: 20,
539
+ summary: 'コンテンツ。',
540
+ children: [],
541
+ hasCode: false,
542
+ hasList: false,
543
+ hasTable: false,
544
+ },
545
+ ],
546
+ keyTopics: ['日本語'],
547
+ }
548
+
549
+ const output = formatSummary(mockSummary, { maxTokens: 100 })
550
+ expect(output).toContain('日本語のドキュメント')
551
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(100)
552
+ })
553
+
554
+ it('drops truncation warning when budget is very tight', () => {
555
+ const mockSummary: DocumentSummary = {
556
+ path: '/test/file.md',
557
+ title: 'Short Title',
558
+ originalTokens: 500,
559
+ summaryTokens: 200,
560
+ compressionRatio: 0.6,
561
+ sections: [
562
+ {
563
+ heading: 'Large Section',
564
+ level: 2,
565
+ originalTokens: 400,
566
+ summaryTokens: 150,
567
+ summary: 'Large content that will be truncated. '.repeat(20),
568
+ children: [],
569
+ hasCode: false,
570
+ hasList: false,
571
+ hasTable: false,
572
+ },
573
+ ],
574
+ keyTopics: [],
575
+ }
576
+
577
+ // Very tight budget that can fit header but not with truncation warning
578
+ // Increased to account for more conservative token counting
579
+ const output = formatSummary(mockSummary, { maxTokens: 35 })
580
+
581
+ // Should stay within budget
582
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(35)
583
+ // Should NOT contain truncation warning (dropped to fit) - lowercase in new format
584
+ expect(output).not.toContain('Truncated')
585
+ })
586
+
587
+ it('shows truncation warning when budget allows', () => {
588
+ const mockSummary: DocumentSummary = {
589
+ path: '/test/file.md',
590
+ title: 'Short Title',
591
+ originalTokens: 500,
592
+ summaryTokens: 200,
593
+ compressionRatio: 0.6,
594
+ sections: [
595
+ {
596
+ heading: 'Large Section',
597
+ level: 2,
598
+ originalTokens: 400,
599
+ summaryTokens: 150,
600
+ summary: 'Large content that will be truncated. '.repeat(20),
601
+ children: [],
602
+ hasCode: false,
603
+ hasList: false,
604
+ hasTable: false,
605
+ },
606
+ ],
607
+ keyTopics: [],
608
+ }
609
+
610
+ // Larger budget that can fit header AND truncation warning
611
+ // Increased to account for enhanced truncation warning with section lists
612
+ const output = formatSummary(mockSummary, { maxTokens: 150 })
613
+
614
+ // Should stay within budget
615
+ expect(countTokensApprox(output)).toBeLessThanOrEqual(150)
616
+ // Should contain truncation warning (enough room) - note: lowercase "Truncated" in new format
617
+ expect(output).toContain('Truncated')
618
+ })
619
+ })
620
+ })